In this post, we will build a voice control smart light with Azure IoT hub. All hardwares we need are shown in the picture below, a Raspberry Pi, 3 LEDs, 3 220Ω resistances, a breadboard, some DuPont lines, and Amazon Echo Dot.
![All hardwares we need]()
Don’t worry if you do not have one of them or even any of them, we still can make things work with simulated device, and I will explain how to do that and the end of this post.
Before we begin this awesome job, let’s make it clear how does the entire flow of the information go from your voice to the light turn off or on status. Firstly, Amazon Echo Dot records your voice and send it to Amazon Cloud, and Amazon Cloud transforms your voice to command. Then Amazon Cloud send the command to Azure IoT Hub Server side, and Azure IoT Hub Server pass it to Azure IoT Hub Client side. In this post the client is a Raspberry Pi. Finally, Raspberry Pi turns off or on the LED via GPIO pins.
First of all, you need to setup Azure IoT Hub. Azure IoT Hub provides FREE plan, so you needn’t pay for it now. Follow the step in https://github.com/Azure/azure-iot-sdks/blob/master/doc/setup_iothub.md. Notice, if you want to use Azure IoT Hub free plan, you need change Pricing and scale tier from S1 – Standard to Free. The free plan has a limitation of 8000 messages per day, but it’s enough for our experiment, right?
After setup Azure IoT Hub, we need create a device in our hub. You can follow the steps in https://github.com/Azure/azure-iot-sdks/blob/e1c8c6df558823f21bd94875d940cdb864b490b8/doc/manage_iot_hub.md to create your device. Remember the name of device your created, we’ll need it in the later steps.
Let’s deploy Azure IoT Hub to server side. Choose a server that supports Node, for example, I choose Azure web app. Azure web app is not necessary for Azure IoT Hub, if you already have a web hosting with Node support, just use it.
I use express generator to create a node website quickly, you may also use tools you like. You can install express generator by simply run
npm install -g generator-express
if it doesn’t work try run is as admin
sudo npm install -g generator-express
Enter local Azure IoT Hub server development root path, run
yo express
Choose the below options:
? Would you like to create a new directory for your project? Yes
? Enter directory name {appname}
? Select a version to install: MVC
? Select a view engine to use: Jade
? Select a css preprocessor to use (Sass Requires Ruby): None
? Select a database to use: None
? Select a build tool to use: Grunt
After that, you can see a folder named {appname}
in your current path, enter it and add two lines in package.json in dependencies field:
"azure-iothub": "^1.0.18","azure-event-hubs": "^0.0.4","uuid": "^2.0.3"
Then run npm install
to apply the changes. Run npm start
, and open http://127.0.0.1:3000, you can see a web page shows Generator-Express MVC.
![Generator Express default homepage]()
You have completed local develop envirnmnet, great job! now let’s write the core code of the server side.
Go to controllers folder in app folder, you can see a file named home.js, right? It’s the router of the website. Let’s add a path nanmed /api/smarthome
with these code:
router.get('/api/smarthome', function(req, res, next) {
res.header('Content-Type', 'application/json');
res.header('Access-Control-Allow-Origin', '*');
res.render('json', {
json: {message: 'foo'},
layout: false
});
});
And go back to app folder, and go to views folder, create a file named json.jade, and write this line code in it:
!=JSON.stringify(json)
Now restart your website, and visit http://127.0.0.1:3000/api/smarthome. You can press Ctrl+C to stop the server, and run npm start
again.
![Smart Home API Router]()
You can see a JSON in the page, right? Congratulations! You have created an API router in your website!
Next, we need to make it a real API. Go to models folder under app folder, create a file named iot-hub.js. In this file, we need use Azure IoT Hub module.
'use strict';
var IoTHubClient = require('azure-iothub').Client;
var Message = require('azure-iot-common').Message;
var targetDevice = '[Target Device Name]';
var connectionString = '[Connection String]';
var iotHubClient = IoTHubClient.fromConnectionString(connectionString);
function sendC2DMessage(msg, targetDevice) {
targetDevice = targetDevice || 'Chrome';
iotHubClient.open(function (err) {
if (err) {
console.error('Could not connect: ' + err.message);
} else {
console.log('Client connected');
// Create a message and send it to the IoT Hub
var data = JSON.stringify({ message : msg });
var message = new Message(data);
console.log('Sending message: ' + message.getData());
iotHubClient.send(targetDevice, message);
}
});
}
module.exports = sendC2DMessage;
Replace [Target Device Name]
with your device name that you created of your hub, we did it at the top of this post, remember? And replace [Connection String]
with the device connection string. The connection string is something like
HostName=xxx.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=...
Now we have the IoT Hub model for our web and we can use it in our app. Create a new model named smarthome-turnonoff.js, and write a function to handle turn on off command:
function TurnOnOff(applianceId, requestType) {
var sendC2DMessage = require('./iot-hub.js')
new sendC2DMessage({
applianceId: applianceId,
request: requestType
}, '[Target Device Name]');
var result = {
applianceId: applianceId,
request: requestType
};
return result;
}
module.exports = TurnOnOff;
Also, remember to change [Target Device Name]
to your own.
In home.js under controllers folder, change the /api/smarthome
router to this:
router.get('/api/smarthome2', function(req, res, next) {
res.header('Content-Type', 'application/json');
res.header('Access-Control-Allow-Origin', '*');
switch(req.query.request) {
case 'TurnOnRequest' :
res.render('json', {
json: new TurnOnOff(req.query.applianceId, 'TurnOn'),
layout: false
});
break;
case 'TurnOffRequest' :
res.render('json', {
json: new TurnOnOff(req.query.applianceId, 'TurnOff'),
layout: false
});
break;
}
});
And and var TurnOnOff = require('../models/smarthome-turnonoff.js');
at the top of the file.
Restart your website, and visit http://127.0.0.1:3000/api/smarthome?request=TurnOnRequest&applianceId=34f8d140-0704-4d2a-b449-bf2c458afa0a, you can see a JSON show the id and request your sent, go to Azure portal, open IoT Hub dashboard, you can see you have already sent a message in overview tab.
![IoT Hub Dashboard]()
Now let’s setup device side, Raspberry Pi.
First, We need setup Azure IoT Hub develop environment. Clone Azure IoT SDK repo from https://github.com/Azure/azure-iot-sdks. Cause the repo contains submodules, remember to clone with –recursive command like this:
git clone --recursive https://github.com/Azure/azure-iot-sdks.git
Follow the step in https://github.com/Azure/azure-iot-sdks/blob/e1c8c6df558823f21bd94875d940cdb864b490b8/doc/get_started/python-devbox-setup.md.
After setup develop environment completely, create a file name iothub.py in your Raspberry Pi. Remember copy iothub_client.so to the same path of the file you just create. In the file, write these code:
import iothub_client
from iothub_client import *
from time import sleep
import json
timeout = 241000
minimum_polling_time = 9
receive_context = 0
connection_string = '[Connection String]'
def receive_message(message, counter):
buffer = message.get_bytearray()
size = len(buffer)
message = json.loads(buffer[:size].decode('utf-8')).get('message')
print("Received Data: <%s>" % message)
return IoTHubMessageDispositionResult.ACCEPTED
def iothub_init():
iotHubClient = IoTHubClient(connection_string, IoTHubTransportProvider.HTTP)
iotHubClient.set_option("timeout", timeout)
iotHubClient.set_option("MinimumPollingTime", minimum_polling_time)
iotHubClient.set_message_callback(receive_message, receive_context)
while True:
sleep(10)
if __name__ == '__main__':
iotHubClient = iothub_init()
Again, replace [Connection String]
with your own. At this place, the connection string is for device, so it should looks like
HostName=xxx.azure-devices.net;DeviceId=xxx;SharedAccessKey=...'
See? you can find a DeviceId parameter. You can find it under Devices tab in Azure portal.
![Device Connection String]()
Let’s run it, and visit our website again. You can see the request has already be received by Raspberry Pi!
![Terminal]()
Then we need make Raspberry Pi control the LEDs. Here’s how I connect LEDs and resistances, DuPont lines on the breadboard.
![LED board]()
The blue line is GND known as ground, and the other three lines, white, gray and purple is light control. We need connect them to the GPIO pins on Raspberry Pi. I have drawn a picture to explain how to connect the lines to make you clear.
![RPi GPIO]()
Now we need a python module call RPi.GPIO, you can download it from https://pypi.python.org/pypi/RPi.GPIO. After download RPi.GPIO, extend it and run setup.py.
Let’s create a script to test LED control via GPIO. Create a file called led.py, and write these code into it:
import RPi.GPIO as GPIO
from time import sleep
GPIO.setmode(GPIO.BCM)
GPIO.cleanup(17)
GPIO.cleanup(27)
GPIO.cleanup(22)
GPIO.setup(17, GPIO.OUT)
GPIO.setup(27, GPIO.OUT)
GPIO.setup(22, GPIO.OUT)
GPIO.output(17, False)
GPIO.output(27, False)
GPIO.output(22, False)
while True:
GPIO.output(17, True)
sleep(1)
GPIO.output(17, False)
GPIO.output(27, True)
sleep(1)
GPIO.output(27, False)
GPIO.output(22, True)
sleep(1)
GPIO.output(22, False)
Now you should see the LEDs on and off one by one.
Great! You can control the hardware with code! Cool, uh?
Next let’s combine the two script we wrote, iothub.py and led.py. Name the new with a cool thing, for example, smartlight.py. Write these code into it:
import iothub_client
from iothub_client import *
from time import sleep
import json
import RPi.GPIO as GPIO
green_led_id = 'db87ffe4-5d5d-4af7-bb70-da8a43beac90'
red_led_id = '1266ab90-b23d-4e0f-83d0-ec162284952f'
yellow_led_id = '7b38a9f2-c9f4-42cf-bb63-59147eb685b4'
led_gpio = {green_led_id: 22, red_led_id: 27, yellow_led_id: 17}
GPIO.setmode(GPIO.BCM)
GPIO.setup(led_gpio[green_led_id], GPIO.OUT)
GPIO.setup(led_gpio[red_led_id], GPIO.OUT)
GPIO.setup(led_gpio[yellow_led_id], GPIO.OUT)
GPIO.output(led_gpio[green_led_id], False)
GPIO.output(led_gpio[red_led_id], False)
GPIO.output(led_gpio[yellow_led_id], False)
timeout = 241000
minimum_polling_time = 9
receive_context = 0
connection_string = '[Connection String]'
def receive_message(message, counter):
buffer = message.get_bytearray()
size = len(buffer)
message = json.loads(buffer[:size].decode('utf-8')).get('message')
print("ID: %snRequest: %s" % (message['applianceId'], message['request']))
if message['request'] == 'TurnOn':
GPIO.output(led_gpio[message['applianceId']], True)
elif message['request'] == 'TurnOff':
GPIO.output(led_gpio[message['applianceId']], False)
return IoTHubMessageDispositionResult.ACCEPTED
def iothub_init():
iotHubClient = IoTHubClient(connection_string, IoTHubTransportProvider.AMQP)
# iotHubClient.set_option("timeout", timeout)
# iotHubClient.set_option("MinimumPollingTime", minimum_polling_time)
iotHubClient.set_message_callback(receive_message, receive_context)
while True:
sleep(10)
if __name__ == '__main__':
iotHubClient = iothub_init()
Replace '[Connection String]'
with your own. Am I tooooo nagging? If so, sorry about that.
OK, magic things will happen! Run the script in Raspberry Pi, restart your website, visit http://127.0.0.1:3000/api/smarthome2?request=TurnOnRequest&applianceId=1266ab90-b23d-4e0f-83d0-ec162284952f.
See what! The red LED is on, right! It is amazing! Now you can control the LED from HTTP request!
Of course, this is not a complete smart light, we should make control interface more friendly. Next we will add Amazon Echo Dot in!
You can learn how to create a Alexa Smart Home Skill from https://developer.amazon.com/public/community/post/Tx4WG410EHXIYQ/Five-Steps-Before-Developing-a-Smart-Home-Skill.
To add LEDs into your Alexa console, we need use Alexa Smart Home Skill discovery request. And to control the LEDs, we need control request.
Here’s the smart home skill Lambda function I wrote:
var https = require('https');
var REMOTE_CLOUD_BASE_PATH = '/api/smarthome';
var REMOTE_CLOUD_HOSTNAME = '[Cloud Hostname]';
exports.handler = function(event, context) {
log('Input', event);
try{
switch (event.header.namespace) {
case 'Alexa.ConnectedHome.Discovery':
handleDiscovery(event, context);
break;
case 'Alexa.ConnectedHome.Control':
handleControl(event, context);
break;
default:
log('Err', 'No supported namespace: ' + event.header.namespace);
context.fail('Something went wrong');
break;
}
}
catch(e) {
log('error', e);
}
};
function handleDiscovery(event, context) {
log('Discovery', event);
var basePath = '';
basePath = REMOTE_CLOUD_BASE_PATH + '?request=Discovery';
var options = {
hostname: REMOTE_CLOUD_HOSTNAME,
port: 443,
path: basePath,
headers: {
accept: 'application/json'
}
};
log('Discovery', options);
var serverError = function (e) {
log('Error', e.message);
context.fail(generateControlError(event.header.name,'DEPENDENT_SERVICE_UNAVAILABLE','Unable to connect to server'));
};
var callback = function(response) {
log('Discovery Get', response);
var str = '';
response.on('data', function(chunk) {
str += chunk.toString('utf-8');
log('Discovery Data', str);
});
response.on('end', function() {
log('Result', str);
var result = JSON.parse(str);
context.succeed(result);
log('Result', result);
});
response.on('error', serverError);
};
https.get(options, callback).on('error', serverError).end();
log('Discovery Got', 'Got');
}
function handleControl(event, context) {
if (event.header.namespace === 'Alexa.ConnectedHome.Control') {
/**
* Retrieve the appliance id and accessToken from the incoming message.
*/
var applianceId = event.payload.appliance.applianceId;
var accessToken = event.payload.accessToken.trim();
log('applianceId', applianceId);
var basePath = '';
basePath = REMOTE_CLOUD_BASE_PATH + '?applianceId=' + applianceId +'&request=' + event.header.name;
var options = {
hostname: REMOTE_CLOUD_HOSTNAME,
port: 443,
path: basePath,
headers: {
accept: '*/*'
}
};
var serverError = function (e) {
log('Error', e.message);
context.fail(generateControlError(event.header.name,'DEPENDENT_SERVICE_UNAVAILABLE','Unable to connect to server'));
};
var callback = function(response) {
var str = '';
response.on('data', function(chunk) {
str += chunk.toString('utf-8');
});
response.on('end', function() {
log('done with result');
var headers = {
namespace: 'Alexa.ConnectedHome.Control',
name: event.header.name.replace('Request', 'Confirmation'),
payloadVersion: '1'
};
var payloads = {
success: true
};
var result = {
header: headers,
payload: payloads
};
log('Done with result', result);
context.succeed(result);
});
response.on('error', serverError);
};
https.get(options, callback)
.on('error', serverError).end();
}
}
function log(title, msg) {
console.log('*************** ' + title + ' *************');
console.log(msg);
console.log('*************** ' + title + ' End*************');
}
function generateControlError(name, code, description) {
var headers = {
namespace: 'Alexa.ConnectedHome.Control',
name: name,
payloadVersion: '1'
};
var payload = {
exception: {
code: code,
description: description
}
};
var result = {
header: headers,
payload: payload
};
return result;
}
Replace [Cloud Hostname]
with your own. That hostname is just of the node website you developed in local. Do not use 127.0.0.1, it doesn’t work for Lambda, you need publish it to the Internet. I use an SSL connection between Lambda and Azure web app, if you use Azure, you can just use .azurewebsites.net, Azure supports SSL. If you use your own domain, you need an SSL certificate. You can get a free SSL certificate for you domain from https://ssl.md.
Echo Dot cannot work correctly with our smart light currently, because Echo Dot cannot understand the callback message we give from the website, also, we have done nothing about the discovery command.
OK, let’s make some change with our website. Now go to app/models of your website, create 3 new files named smarthome-discovery.js, smarthome-adddevice.js and smarthome-removedevice.js.
In smarthome-discovery.js, write these code:
function Discovery() {
var headers = {
namespace: 'Alexa.ConnectedHome.Discovery',
name: 'DiscoverAppliancesResponse',
payloadVersion: '1'
};
var payloads = {
discoveredAppliances: appliances
};
var result = {
header: headers,
payload: payloads
};
return result;
}
module.exports = Discovery;
In smarthome-adddevice.js, write these code:
function AddDevice(manufacturerName, modelName, version, friendlyName, friendlyDescription, actions) {
if (!friendlyName) {
return {success: false};
}
friendlyName = 'Azure ' + friendlyName;
var uuid = require('uuid');
var applianceId = uuid.v4();
manufacturerName = manufacturerName || friendlyName.replace(/s+/g, '');
modelName = modelName || manufacturerName;
version = version || '1.0';
friendlyDescription = friendlyDescription || 'No Description';
actions = actions || ['turnOn', 'turnOff'];
var appliance = {
applianceId: applianceId,
manufacturerName: manufacturerName,
modelName: modelName,
version: version,
friendlyName: friendlyName,
friendlyDescription: friendlyDescription,
isReachable: true,
actions: actions,
status: 'TurnOff'
}
appliances.push(appliance);
return {success: true};
}
module.exports = AddDevice;
In smarthome-removedevice.js, write these code:
function RemoveDevice(applianceId) {
var index = -1;
console.log(appliances)
appliances.forEach(function(appliance) {
console.log(appliance.applianceId)
console.log(applianceId)
console.log(appliance.applianceId === applianceId)
index++;
if (appliance.applianceId === applianceId) {
appliances.splice(index, 1);
removed = true;
return {success: true};
}
});
return {success: false};
}
module.exports = RemoveDevice;
Also, edit home.js in app/controllers folder, add these 3 models on the top of the script:
var Discovery = require('../models/smarthome-discovery.js'),
AddDevice = require('../models/smarthome-adddevice.js'),
RemoveDevice = require('../models/smarthome-removedevice.js');
Modify smarthome-turnonoff.js to make Echo Dot understand we have accepted the request it sent:
function TurnOnOff(applianceId, requestType) {
var headers = {
namespace: 'Alexa.ConnectedHome.Control',
name: requestType + 'Confirmation',
payloadVersion: '1'
};
var payloads = {
success: true
};
var result = {
header: headers,
payload: payloads
};
var sendC2DMessage = require('./iot-hub.js')
new sendC2DMessage({
applianceId: applianceId,
request: requestType
}, 'Alexa');
appliances.forEach(function(appliance) {
if (appliance.applianceId === applianceId) {
appliance.status = requestType;
}
});
return result;
}
module.exports = TurnOnOff;
module.exports = TurnOnOff;
And modify /api/smarthome
router to
router.get('/api/smarthome', function(req, res, next) {
res.header('Content-Type', 'application/json');
res.header('Access-Control-Allow-Origin', '*');
switch(req.query.request) {
case 'Discovery' :
res.render('json', {
json: new Discovery(),
layout: false
});
break;
case 'AddDevice' :
res.render('json', {
json: new AddDevice(
req.query.manufacturerName,
req.query.modelName,
req.query.version,
req.query.friendlyName,
req.query.friendlyDescription,
req.query.actions
),
layout: false
});
break;
case 'RemoveDevice' :
res.render('json', {
json: new RemoveDevice(req.query.applianceId),
layout: false
});
break;
case 'TurnOnRequest' :
res.render('json', {
json: new TurnOnOff(req.query.applianceId, 'TurnOn'),
layout: false
});
break;
case 'TurnOffRequest' :
res.render('json', {
json: new TurnOnOff(req.query.applianceId, 'TurnOff'),
layout: false
});
break;
}
});
We use a global variable called appliances to store devices we add, you can use a database to replace it. So we need declare appliances in app.js in the root path of the website:
appliances = [];
Do not use var
to declare it as we need it be global.
All things are ready! Let’s test it!
First, visit your website to add a device from https:///api/smarthome?request=AddDevice&friendlyName=Test+Device, and you’ll see {"success":true}
. Go to Alexa dashboard, enter Smart Home tab, click Discovery devices link, or just say Discover to your Echo Dot. After about 20 seconds, you can find the device you just added.
![Test Device]()
Now let add 3 new devices with the above step, and visit https:///api/smarthome?request=Discovery to find out their appliance ids.
![Appliance Id]()
After find out those ids, change the python script in you Raspberry Pi, replace db87ffe4-5d5d-4af7-bb70-da8a43beac90
, 1266ab90-b23d-4e0f-83d0-ec162284952f
and 7b38a9f2-c9f4-42cf-bb63-59147eb685b4
.
Now, let Echo Dot forget the Test Device, run python script on Raspberry Pi, recover devices again, and ask Alexa to turn on Azure Red Light.
![Alexa LED]()
If you do not have a Raspberry Pi, you can download a Chrome extension called Azure Smart Light Simulator I wrote from https://github.com/Sneezry/Azure-Smart-Light-Simulator.
![Light Simulator]()
Have fun to build your own smart home devices with Azure IoT!