This project describes the design and fabrication of a home automated indoor gadget "Smart Socket" which is simply an AC mains power socket extension that integrates with the Amazon Alexa voice service for voice interraction and control of the device. Typically the mains socket extension has four sockets, with an enabled alexa voice device like the Amazon Echo Dot, you gain the ability to use your voice to control the states (ON/OFF) of these sockets. On top of that, there exists the fifth socket which implemented dimming capability. This dimming capability can serve to power your incandescent side bed lamp in your room. And with your alexa enabled device, you can easily use your voice to control the brightness by simply saying "Alexa ask smart socket to set dimmer to thirty percent". And there you'll observe the light as it smoothly increases or decreases its brightness, instead of a sudden change of brightness that could trigger seizure. Check the video below to have a full idea of what this alexa voice enabled device can do.
Basically, this project consist of four major components as described by the diagram below.
- The Alexa skill service
- The MQTT server
- Persisitence server (Redis)
- Smart socket
The Alexa skill
The Alexa skill consists of two components ; The Skill Interface and the skill service. The skill Interface is what is responsible for processing a user's spoken words and delivers a payload of request to the skill service. The skill service on the other hand, which is hosted on cloud, handles request from the alexa skill interface. The skill interface configuration is developed on the amazons developer portal and communicates with the skill server. When a user speaks into an alexa voice enabled device, the speech is converted to text and sent to the skill interface for processing using already defined utterances (Spoken words with similar intentions). Once the skill interface is done with the user's translation, a request is pushed to its skill service to handle the actions of the request and also returns a response back to the skill interface which in turn is translated back into voice for the user. The Skill interface consist of confiugartion on how to process and handle spoken words from users. Basically the skill interface consits of utterances grouped into intents. Intents are actions to be handled by the skill service, when an utterance is invoked the intent under which the utternace is grouped is sent to the skill server and the skill server must implement a handle of the intent.
In this project, we defined seven intents in the skill interface to be handled by the skill service written in node.js.
- ChangeSocketNameIntent - used to assign custom device name to a socket number e.g from the four sockets, we can say let socket 1 (generic naming convention of sockets) become fridge. so we can easily say "Alexa ask smart socket to turn on the fridge" instead of trying to remeber which socket your fridge is connected to.
- SwitchNamedSocketOnIntent - this intent is responsible for turning on socket number that has been mapped to an appliance's name i.e "Fan".
- SwitchNamedSocketOffIntent - this intent is responsible for turning off socket number that has been mapped to an appliance's name i.e "Lamp".
- SwitchSocketIntent - this intent is responsible for turning off and on of generic numbered socket names ie. "Alexa ask smart socket to turn socket one on"
- DimLampIntent - used for setting the dimmer level
- DimmerLevelIntent - this intent is used to query the current dimmer level in percent.
- SocketStateIntent - used to query the states of all four sockets.
Creating an alexa skill is quite straight forward, all you need is an Amazon developer account, then you are good to go.
Creating the smart socket skillThis is where things started getting interesting for me. To create the smart socket skill with our define intents as defined earlier.
Step 1
Login to the amazon developer console, with your amazon developer's account and choose "ALEXA" from the list of tabs on the console and select get started on the alexa skill kit (ASK).
Step 2
Click the add a new skill button to begin with creating a new skill:
Step 3
Select create custom interraction model in skill type and enter the skill name and its invocation name, click save to continue. And next would be, configuring the interraction model as i have specified ealier.
Step 4
Building the interaction model, this is a JSON format where you configure your utterances, intents and slot. I found the use of model builder to be very useful and quick. It comes with a beautiful and easy to use user interface. So for the Intents mentioned earlier this is where we need to define those utterances that would trigger a particular intent.
In the builder, from my configuration, my ChangeSocketNameIntent has what are called slots. Slots are elegant way of passing variables in user spoken words. for instance the sample utterance in the ChangeSocketNameIntent is "change socket {number} to {device}. This could simply mean, in usage, "Change socket one to lamp".
Step 5
And here comes the step where you would need to specify where you are going to be hosting your Skill service. As seen in the image below, options are given to either use Lambda function, or you specify a Https url to where your skill service is hosted. In this project i decided to use an external source to host the skill service written in node.JS.
The skill service implemented the the intent handlers for the intents from the skill interface configuration that holds our utterances. Writing the skill service using the alexa kit was good, but i needed to do my development and test on my local machine and also deploy on Heroku. So i came across JOVO, a voice application framework that makes writing voice applications for alexa much simpler, and also already provided in the framework, database managemt api to manage local file db persistence and dynamoDB. To cover it all up the framework provides a snippet of how to test on your skill on your local machine.
Here is a sample intent handling snippet;
'SwitchNamedSocketOnIntent': function(device){
}else{
app.tell('Sorry there seems to be no socket mapped to that name');
}
});
},
The MQTT BrokerWhat the skill service does when it receives an intent to turn the light on or off for example, is to find a way to communicate to the smart socket the intention of turning on or off a specific socket number. One of the ways to archieve this, is via MQTT protocol. MQTT protocol works as publish and subscribe kind of protocol. Messages are being published with a topic to a MQTT broker (server) and subscribers listens for incoming messages from the topic they have subscribed to.
For this project, the skill service and smart socket implemented this communication protocol. MQTT is effiecient as it can work where there is network latency. The Node server skill server leveraged on mqtt.js. this can be installed using npm.
$ npm install --save mqtt
The MQTT broker used in this project is mosquitto. it is a public broker which you can use for your test if you wish not to host one on your local machine. The snippet below shows how to connect, subscribe to a topic.
var subClient = mqtt.connect('mqtt://test.mosquitto.org');
subClient.on('connect', function () {
subClient.subscribe('smartsocket/state/shadow');
subClient.publish('smartsocket/state/shadow', 'Hello mqtt');
});
subClient.on('message', function (topic, message) {
console.log(message.toString());
if(topic === 'smartsocket/state/shadow'){
saveSocketStates(message.toString());
}
});
The use of mqtt in this design is for the skill service to be able to send (publish) messages to a topic that the smart socket has also subsribed to. When the smart socket receives these messages, which is in Json format, it parses the json object to determine what to do. On the other hand the smart socket constantly publishes its ON/OFF states to the Broker. the skill service as well listens or subscribes to this topic and saves received messages in an in memory database Redis.
Redis is an in memory database, this means the database uses the ram for its persistence which makes it relatively fast to save and retrive information. Redis majorly is used for queueing and for temporary storage of data. But it is used here just to demonstrate a full funtionality of this project. Redis is available on windows and linux. After installing redis and started on my machine, redis node js client installed from npm is used to persist data into the redis database.
$npm install --save redis
To run the service locally on my machine i used ngrok.com . i used ngrok because the amazon skill interface that needed to communicate with the skill service only works on https and hence ngrok coming to the rescue.
Run:
$ngrok http 3000
This simply binds the port to a public https url that can be supplied to allow skill interface to interract with the skill service hosted on my local machine.
Smart socket designThis is the most critical part of this project. The smart socket comprises of two main components an Arduino Pro Mini and Node-MCU ESP8266. The Node-MCU is responsible for providing the wifi capabilty.
The schematic below elaborates on the smart socket main components.
The schematic does not include the dimmer, but the pin has been described inthe image above. The image below gives a close up of how the dimmer looks like.
The Function of the dimmer is to allow us be able to control the brightness of an incandescence lamp. not recommended for inductive load.
The only task by the ESP8266 is wifi connectivity and the implementation of the mqtt protocol. While the Arduino Pro mini major task is to respond to commands from the ESP. The commands include:
- Turn of or on socket 1
- Turn of or on socket 2
- Turn of or on socket 3
- Turn of or on socket 4
- Set dimmer to a level.
And to also publish its current state. The code for both Microcontroller are given below.
For the ESP8266:
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
WiFiClient espClient;
PubSubClient client(espClient);
const char* ssid = "Andym";
const char* password = "ctronix1";
const char* mqtt_server = "test.mosquitto.org";
long lastMsg = 0;
char msg[50], socketStates[60];
int value = 0;
int CONNECTION_STATUS = 2;
String stateData;
void setup() {
pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
pinMode(CONNECTION_STATUS, OUTPUT);
Serial.begin(9600);
setupWifi(); // begins wifi setup to connect to an access point
client.setServer(mqtt_server, 1883); // setup broker server and port
client.setCallback(callback); // mqtt subscription callback
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
readSocketStates();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
client.publish("smartsocket/state/shadow", socketStates);
}
}
void callback(char* topic, byte* payload, unsigned int length) {
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
void setupWifi() {
delay(10);
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
digitalWrite(CONNECTION_STATUS, HIGH);
delay(500);
Serial.print(".");
digitalWrite(CONNECTION_STATUS, LOW);
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
digitalWrite(CONNECTION_STATUS, LOW);
Serial.println(WiFi.localIP());
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect("SmartSocket")) {
Serial.println("connected");
// Once connected, publish an announcement...
client.publish("outTopic", "hello world");
// ... and resubscribe
client.subscribe("smartsocket/state/control");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void readSocketStates(){
while(Serial.available() > 0){
char data = Serial.read();
stateData += data;
if(data == '\n'){
StaticJsonBuffer<200> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(stateData);
if(!root.success()){
//Serial.println("parseObject() failed");
stateData = "";
return;
}
stateData.toCharArray(socketStates, stateData.length());
stateData = "";
//Serial.println(socketStates);
}
}
}
For the Arduino Pro Mini:
#include <SoftwareSerial.h>
#include <ArduinoJson.h>
#include "TimerOne.h"
SoftwareSerial swSer(8, 9); // RX, TX
String inData;
long lastMsg = 0;
int counter = 0;
const int SOCKET_ONE = 10;
const int SOCKET_TWO = 11;
const int SOCKET_THREE = 12;
const int SOCKET_FOUR = 13;
const int MAX_DELAY = 40;
int AC_LOAD = 3;
const boolean OFF;
const boolean ON;
boolean dimmerState;
boolean zcFlag = false;
int dim = MAX_DELAY; //dim delay
void setup() {
Serial.begin(9600);
//swSer.begin(9600);
pinMode(SOCKET_ONE, OUTPUT);
pinMode(SOCKET_TWO, OUTPUT);
pinMode(SOCKET_THREE, OUTPUT);
pinMode(SOCKET_FOUR, OUTPUT);
pinMode(AC_LOAD, OUTPUT); // Set AC Load pin as output
attachInterrupt(0, zero_crosss_int, RISING); // Choose the zero cross interrupt on interrupt pin 0 (D2)
Timer1.initialize(125); // initialize the timer 1 with 125us because we need 80steps to delay 10ms (1/2 period of 50Hz)
Timer1.attachInterrupt(timerOneCallBack); // attach interrupt callback function
}
void loop() {
receiveCommand();
long now = millis();
if (now - lastMsg > 2000) {
lastMsg = now;
pushSocketState();
}
}
void receiveCommand(){
while (Serial.available() > 0) {
char recieved = Serial.read();
inData += recieved;
Serial.write(recieved);
if (recieved == '\n'){
StaticJsonBuffer<200> jsonBuffer;
Serial.print("Arduino Received: ");
Serial.print("De-serialised ");
Serial.print(inData);
JsonObject& root = jsonBuffer.parseObject(inData);
if (!root.success()) {
Serial.println("parseObject() failed");
inData = "";
return;
}
inData = ""; // Clear recieved buffer
int value = root["value"];
int socket = root["socket"];
Serial.print("Command ");
Serial.println(value);
Serial.print("Socket");
Serial.println(socket);
parseCommand(socket, value);
}
}
}
void parseCommand(int socket, int value){
switch(socket){
case 1:{
digitalWrite(SOCKET_ONE, value==1? HIGH : LOW);
break;
}
case 2:{
digitalWrite(SOCKET_TWO, value==1? HIGH : LOW);
break;
}
case 3:{
digitalWrite(SOCKET_THREE, value==1? HIGH : LOW);
break;
}
case 4:{
digitalWrite(SOCKET_FOUR, value==1? HIGH : LOW);
break;
}
case 20:{
int percent = (value*60)/100;
if(dim < percent){
smoothIncrease(percent);
}
if(dim > percent){
smoothDecrease(percent);
}
Serial.print(percent);
break;
}
}
}
void zero_crosss_int() //function to be fired at the zero crossing to dim the light
{
zcFlag = true;
}
void timerOneCallBack(){
if(zcFlag) {
counter++;
}
if(counter >= dim){
digitalWrite(AC_LOAD, HIGH); // Fire the TRIAC
delayMicroseconds(30); // triac On propogation delay
digitalWrite(AC_LOAD, LOW); // No longer trigger the TRIAC (the next zero crossing will swith it off) TRIAC
counter = 0;
zcFlag = false;
}
}
void pushSocketState(){
StaticJsonBuffer<100> jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["one"] = digitalRead(SOCKET_ONE);
root["two"] = digitalRead(SOCKET_TWO);
root["three"] = digitalRead(SOCKET_THREE);
root["four"] = digitalRead(SOCKET_FOUR);
root["dim"] = dim;
root.printTo(Serial);
Serial.println();
}
void smoothIncrease(int value){
while(dim <= value){
dim ++;
delay(10);
}
}
void smoothDecrease(int value){
while(dim >= value){
dim --;
delay(10);
}
}
Putting it together:
Testing switching socket number:
Thanks
Comments