Home Automation: using Alexa, ESP32, Blynk, and AWS IoT

Atanu Dasgupta
7 min readNov 28, 2020

--

As time is progressing we have various useful technologies available for IoT and home automation. In this article, we will get into details of how to use mixed technology stack to control hot water geyser at home from Alexa and also from the mobile application. Here is the video link.

Alexa provides us a way to develop custom applications with endless possibilities. In this example, we also would use Blynk to provide a mobile interface for Geyser control and monitoring usage. Users would have the same capability using Alexa or Blynk app to control or monitor the geyser. We also use AWS-IoT service and utilize MQTT to publish and subscribe to shadow topics. AWS-IoT provides a virtual device replica called “Shadow” which represents the device state as desired and reported in a JSON document. The format looks like this.

Shadow state:

{
"desired": {
"onOroff": 0,
"duration": 10
},
"reported": {
"onOroff": 0,
"duration": 10,
"remaining": 0,
"average": 14.96
}
}

The desired state is updated by Alexa and the reported state is updated by the micro-controller. For example, if we switch on the Geyser, we would update the value of “onOrOff” to 1, in the desired state. This would then trigger a delta topic in MQTT, which our ESP32 MQTT client would subscribe to. As a result, the micro-controller gets the state change and turns on the relay which in turn would switch on the geyser. The reported state is updated by ESP32 as a response.

The design is loosely coupled, so you can modify your esp32 code in Arduino IDE and for Alexa, you may use Visual Studio. I am using AWS free tier which is sufficient for this application and there are unlikely to be any changes on your credit card.

As soon as the Geyser switches on, the ESP32 would also communicate to the Blynk cloud and which would then turn on the LED widget on the app.

On Alexa we have an interaction model captured in a JSON document, with intents and slots mentioned. When the user provides a variable input such, duration the Geyser should be on, it is captured using a slot in dialog interaction. For skill implementation, we deploy a lambda function which gets triggered by Alexa skill arn. Typically we start from Alexa by invoking the launchRequest, followed by any of the intents. We also use Alexa timers we notify the user as Geyser remaining time reduces so that he knows that hot water is getting ready.

Let’s go over some code snippets to understand the implementation.

  1. For Alexa interaction, we use the interaction model, intents, and skills.

The following JSON code snippets show how the intents are defined. In this example, we provide the ability for the user to change the duration for geyser based on slots. For example, the user may choose to switch on the geyser for 15 minutes by saying “change duration to fifteen minutes” . Here is fifteen is the slot value.

More samples you have Alexa AI model gets trained better to respond to you.

“changeDurationIntent”,

“slots”: [{

“name”: “time”,

“type”: “AMAZON.NUMBER”,

“samples”: [“yes duration is {time} minutes”],

“multipleValues”: {“enabled”: false}}],

“samples”: [“change duration to {time} minutes”,

“Switch my Geyser for {time} minutes”,

“Change Geyser duration to {time}”]},

2. Here is the corresponding Lambda function which implements this intent.

sHandler.prototype.updateshadow = (val, duration=DEFAULT_MIN) => {

return new Promise((resolve, reject) => {

var payloadObj={ “state”:{ “desired”:{“onOroff”:val,“duration”:duration}}};

var paramsUpdate = {

“thingName” : config.IOT_THING_NAME,

“payload” : JSON.stringify(payloadObj)};

iotData.updateThingShadow(paramsUpdate, (err,data) => {

if (err) {console.log(“Unable to update =>”, JSON.stringify(err))

return reject(“Unable to update !”);}

console.log(“Shadow Updated, “, JSON.stringify(data));resolve(data);});});}

3. In the above NodeJS code, we are updating the device shadow with the desired duration. We have the default minimum of 10 minutes just in case the user does not provide duration. The was shadow arn is used to update and access the IoT shadow.

Error handling becomes very important for such applications and we need to make sure all boundary conditions are covered.

As soon as the shadow is updated, MQTT would generate a delta message on the topic which our ESP32 MQTT client is listening to and that is how we are able provide the required duration to the micro-controller. You will see later how the Blynk code will also modify the duration in the mobile app, as soon the MQTT message is received by ESP32 client.

const changeDurationIntentHandler = {

var duration = handlerInput.requestEnvelope.request.intent.slots.time.value;

if (duration <DEFAULT_MIN || duration > DEFAULT_MAX )

duration=DEFAULT_MIN;

const speechText = `Your Geyser duration is set to “${duration}” minutes`;

timerItem.duration=”PT”+duration+”M”;

var val=0; // you could first enquire current state instead of switching on the geyser

return shadowops.onOroffshadow().then((data) => {

if (data === 1) val=1;

return shadowops.updateshadow(val,parseInt(duration,10)).then((data) => {

timerItem.duration=”PT”+duration+”M”;

// check if Geyser is already on.

if (val ===1)return handlerInput.responseBuilder

.addDelegateDirective({name: ‘TimerStartIntent’,confirmationStatus: ‘NONE’,slots: {}}).speak(speechText).getResponse();

else return handlerInput.responseBuilder.speak(speechText).getResponse();})

.catch((err) => { console.log(“Error while updating duration”, err);

speechText = “Failed to update duration ! “

return handlerInput.responseBuilder.speak(speechText).getResponse();})})},};

In the code below, you would see how the user would be prompted whether to set a timer if the geyser is already on so that he gets reminded. This is quite a useful feature in Alexa that we can leverage. The TimerStartIntent is again a custom code, which sets timer based on the duration set. Herewe check if we have permission to set timer.

const TimerStartIntentHandler = {canHandle(handlerInput) {

const request = handlerInput.requestEnvelope.request;

const intent = request.intent;

if ( intent.confirmationStatus === ‘DENIED’||Alexa.getIntentName(handlerInput.requestEnvelope) === ‘NoIntent’ ){handlerInput.responseBuilder.speak(‘Ok, I will not set the timer, please remember to take bath !’);

}

else {

const { permissions } = handlerInput.requestEnvelope.context.System.user;

if (!permissions) {console.log(‘Inside yes or No’);

return handlerInput.responseBuilder.speak(“This skill needs permission to access your timers.”)

.addDirective({type: “Connections.SendRequest”,name: “AskFor”,payload: {

“@type”: “AskForPermissionsConsentRequest”,“@version”: “1”,

“permissionScope”: “alexa::alerts:timers:skill:readwrite”},token: ,‘shouldEndSession’: true}).getResponse()

.catch((err) => {

console.log(“Error while getting permission”, err);

const speechText = “Failed to prompt for permission ! “

return handlerInput.responseBuilder.speak(speechText).getResponse();})

} else {

console.log( ‘Inside timer 1’);

//handle default

const duration = moment.duration(timerItem.duration),

minutes = (duration.minutes() > 0) ? `${duration.minutes()} ${(duration.minutes() === 1) ? “minute” : “minutes”} ` : “”,

const options = {headers: {“Authorization”: `Bearer ${Alexa.getApiAccessToken(handlerInput.requestEnvelope)}`,

“Content-Type”: “application/json”}};

// code for delete timer.

await axios.delete(‘https://api.amazonalexa.com/v1/alerts/timers', options)

.then((response) => {console.log( ‘ Your timer is successfully deleted’);})

.catch(error => {

console.log(error);

handlerInput.responseBuilder.speak(‘Could not delete timer!’);});

await axios.post(‘https://api.amazonalexa.com/v1/alerts/timers', timerItem, options)

.then((response) => {

handlerInput.responseBuilder.speak(`Your timer is set for ${hours} ${minutes} ${seconds}.<audio src=”soundbank://soundlibrary/weather/rain/rain_03"/>`);

We also delete any existing timers if we are changing the duration.

4. Now for the same use-case let's move to the ESP32 code and understand the micro-controller responds to the MQTT request for the above request.

In ESP32 , we subscribe to delta messages from MQTT and determine what sort of request is this. If there is a change in duration we would update the shadow reported state and also update the Blynk app with the correct duration.

// msqtt subscribe to delta

void mqttCallback (String &topic, String &payload) {
StaticJsonDocument<1024> doc;

// Test if parsing succeeds.

DeserializationError error = deserializeJson(doc, payload);
//serializeJsonPretty(doc, Serial);
int found = payload.indexOf ( ‘onOroff’);

if ( found == -1) statusValue=-1;
else statusValue=doc[“state”][“onOroff”];

recdDuration=doc[“state”][“duration”];
if ( recdDuration == NULL ) recdDuration=DEFAULT_MIN;
msgReceived=true; fromBlynk=false;}

On receiving duration message we call receivedDuration() method from the loop() in the arduino code.

In the receivedDuration method we use the the flag “fromBlynk” to determine whether the request came from Blynk app or from Alexa.

void receivedDuration()
{
StaticJsonDocument<1024> jsonDoc;
char jsonBuffer[1024];

JsonObject stateObj = jsonDoc.createNestedObject(“state”);
JsonObject reportedObj = stateObj.createNestedObject(“reported”);

if (fromBlynk) {
JsonObject desiredObj = stateObj.createNestedObject(“desired”);
desiredObj[“duration”] = recdDuration;
}
else
{
pinValue2=recdDuration;
Blynk.virtualWrite (V24, pinValue2);
Blynk.virtualWrite(V23,pinValue2);
}
reportedObj[“duration”] = recdDuration;

serializeJson(jsonDoc, jsonBuffer);

client.publish(AWS_IOT_TOPIC, jsonBuffer);
}

The below function is used when we alter duration from Blynk app, we also make sure the duration is updated in the shadow using the receivedDuration() method.

Here is how the Blynk dashboard looks like

BLYNK_WRITE(V23) // step
{
pinValue2 = param.asInt(); // assigning incoming value from pin V23 to a variable

incrementPinValue2=0;
recdDuration = pinValue2;

Blynk.virtualWrite(V24,pinValue2);
fromBlynk=true;
receivedDuration();

}

We need to use the certificate, private key and root CA to ensure secure MQTT connection to AWS from ESP32.

In order to flash the firmware on ESP32 later, its good to set it up for OTA updates so that once the ESP32 is installed into electrical connections in your home, it's lot easier to update the firmware over WiFi.

The key complexity lies in the following areas while building a useful application for home automation.

a) Designing the Alexa interaction model and conversations

b) Ensuring the error handling and failure aspects are taken care of , such as power outages, WiFi disconnects, so that your ESP32 knows how to recover and is fairly resilient.

c) Ensuring the shadow state and Blynk app is always in sync with each other, for all possible use cases.

Any questions and comments are welcome!

You can refer to Github for source code.

--

--

Atanu Dasgupta
Atanu Dasgupta

Written by Atanu Dasgupta

technology enthusiast with passion for learning

Responses (2)