TOC
You don’t need a big device to connect to Azure IoT Hub
When you are looking for IoT devices to connect to Azure IoT Hub most are mini computers or devkits that are cheap but limited in what they can do or just very power hungry.
“‘But what about using a Microprocessor?’ I hear you say”, well these are great but you are then normally limited to programming in C which we all know isn’t great.
The Azure SDK for Embedded C is designed to allow small embedded (IoT) devices to communicate with Azure services and can be found here but again it’s C you are forced to use and it’s targeted at larger device as mentioned before due to the memory required.
So what options do we have for us DotNet developers that is smaller than a Raspberry Pi and allows us to use C#. Well we have the awesome Wilderness Labs F7 but what about even lower powered off the shelf from Amazon devices like the ESP32-Cam Modules that are just £10/$13 each or even cheaper if you shop around.
This blog post is hopefully going to show you how to take one of these super cheap and easy to purchase devices and with your DotNet skills connect to Azure Azure IoT Hub all using C#. For this magic to happen I will be using the NanoFramework which makes it easy to write C# code for a select number of IoT Devices.
Let’s dive in…
Device being used
The device I am using is an ESP32-CAM and you can get them direct from Amazon Here
Full disclosure this is using my Affiliates link but feel free to search around they can sometimes be found for cheaper.
Connecting the device
When you first connect the device to your PC/Laptop you will hear the Windows Bing-bong but looking in the Device Manager you will notice that it appears listed under the Other Devices
section.
To use the ESP32-CAM-MB you need the CH340 Drivers otherwise it will not connect as a USB device. You can get installation software for the drivers from Sparkfun as this is the quickest and easiest way to install them.
After you have the driver installed the device then appears as an Com device in the Device Manager:
Set-up Visual Studio
First I need to point out that you can use ANY version of Visual Studio to follow along, from the FREE Community Edition all the way up to the Enterprise version. You can grab the FREE Community edition Here
We now need to install the Nano Framework VS2019 extention this is a simple installation from either the MarketPlace or using the Extention Manager in VS19 and searching for NanoFrameWork
Configure the ESP32 Board
Now that the VS19 Extention is installed you need to flash the NanoFramework Firmware onto the ESP32 board as this will include the required NanoFramework Firmware and Bootloaders etc so that you can connect the board to the PC with Visual Studio and target the board for development.
The tool need is Open Source here on Github from the NanoFramework team if you want to read up on it.
From here you can see that you need to install the CLI tools which is really easy using the dotnet CLI
so within your Terminal program like WindowsTerminal run this command.
dotnet tool install -g nanoff
Now that nanoff
is installed you can connect your board and find it’s Com Port in your Device Manager, right click the windows logo and select Device Manager
I find is the quickest way, under the Ports (Com & LPT)
you will see your device if you have multiple then unplug and see which one disappears is a good way to work out which port you need. Make a note of the COM number so for me it’s COM6.
Now you have the Com port you can flash the firmware so back to the Terminal program and run this command, changing the digit after the COM
to match your device COM Port
nanoff --target ESP32_WROOM_32 --serialport COM6 --update --preview
You should see it whirl away for a minute or two as it erases the flash and then burns the new firmware.
Create the Project
We are now ready to create our project, so within Visual Studio create a new project on opening and in the search box enter nano
and you will see that you can create a Blank Application, give it a name and save location as you normally do in Visual Studio.
When you have the default blank app showing you now need to install some NuGet packages
so that you can use the GPIO of the device and these can be found by typing nanoFramework.Windows.Devices.Gpio
into the search box, you may also need to select the ‘Include Pre-release’ box as I did at the time of writing.
GPIO = General Purpose Input Output
We will also need the following NuGets so may as well install them now but be warned these are currently Preview packages so be sure to tick the ‘Include Pre-release` check box:
AMQPNetLite.nanoFramework
nanoFramework.NetWorkHelper
Write some code.
I have based this project on the AMQP examples on the NanoFramework Github page but as I don’t like just reporting Temperature and prefer reporting something a little more fun and useful here is my version. I am generating a Latitude and Longitude based off a random bearing and distance from the last waypoint to simulate tracking a device in the real world. This code you can cut and paste into Visual Studio and have a read over but there are a few more steps we need to complete before we can upload the code to the device.
using Amqp;
using nanoFramework.Networking;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using AmqpTrace = Amqp.Trace;
namespace ConnectESP32ToIOTHub
{
public class Program
{
// Set-up Wifi Credentials so we can connect to the web.
private static string Ssid = "<<YOUR SSID>>";
private static string WifiPassword = "<< WIFI PASSWORD >>";
// Azure IoTHub settings
const string _hubName = "CAS-Learning-IOTHub";
const string _deviceId = "NanoFramework-Device1";
const string _sasToken = "SharedAccessSignature sr=CAS-Learning-IOTHub.azure-devices.net%2Fdevices%2FNanoFramework-Device1&sig=0eGgNE7BqjzWKfeffojGJYaTEQgFV72bTytkUCkU8qQ%3D&se=1631051254";
// Lat/Lon Points
static double Latitude;
static double Longitude;
const double radius = 6378; // Radius of earth in Kilometers at the equator, yes it's a big planet. Fun Fact it's 6356Km pole to pole so the planet is an oblate spheroid or a squashed ball.
private static Random _random = new Random();
static bool TraceOn = false;
public static void Main()
{
// Set-up first Point and I have chossen to use the great Royal Observatory, Greenwich, UK where East meets West.
Latitude = 51.476852;
Longitude = 0.0;
Debug.WriteLine("Waiting for network up and IP address...");
bool success = false;
CancellationTokenSource cs = new(60000);
success = NetworkHelper.ConnectWifiDhcp(Ssid, WifiPassword, setDateTime: true, token: cs.Token);
if (!success)
{
Debug.WriteLine($"Can't get a proper IP address and DateTime, error: {NetworkHelper.ConnectionError.Error}.");
if (NetworkHelper.ConnectionError.Exception != null)
{
Debug.WriteLine($"Exception: {NetworkHelper.ConnectionError.Exception}");
}
return;
}
else
{
Debug.WriteLine($"YAY! Connected to Wifi - {Ssid}");
}
// setup AMQP
// set trace level
AmqpTrace.TraceLevel = TraceLevel.Frame | TraceLevel.Information;
// enable trace
AmqpTrace.TraceListener = WriteTrace;
Connection.DisableServerCertValidation = false;
// launch worker thread
new Thread(WorkerThread).Start();
Thread.Sleep(Timeout.Infinite);
}
private static void WorkerThread()
{
try
{
// parse Azure IoT Hub Map settings to AMQP protocol settings
string hostName = _hubName + ".azure-devices.net";
string userName = _deviceId + "@sas." + _hubName;
string senderAddress = "devices/" + _deviceId + "/messages/events";
string receiverAddress = "devices/" + _deviceId + "/messages/deviceBound";
Connection connection = new Connection(new Address(hostName, 5671, userName, _sasToken));
Session session = new Session(connection);
SenderLink sender = new SenderLink(session, "send-link", senderAddress);
ReceiverLink receiver = new ReceiverLink(session, "receive-link", receiverAddress);
receiver.Start(100, OnMessage);
while (true)
{
string messagePayload = $"{{\"Latitude\":{Latitude},\"Longitude\":{Longitude}}}";
// compose message
Message message = new Message(Encoding.UTF8.GetBytes(messagePayload));
message.ApplicationProperties = new Amqp.Framing.ApplicationProperties();
// send message with the new Lat/Lon
sender.Send(message, null, null);
// data sent
Debug.WriteLine($"*** DATA SENT - Lat - {Latitude}, Lon - {Longitude} ***");
// update the location data
GetNewDestination();
// wait before sending the next position update
Thread.Sleep(5000);
}
}
catch (Exception ex)
{
Debug.WriteLine($"-- D2C Error - {ex.Message} --");
}
}
private static void OnMessage(IReceiverLink receiver, Message message)
{
try
{
// command received
Double.TryParse((string)message.ApplicationProperties["setlat"], out Latitude);
Double.TryParse((string)message.ApplicationProperties["setlon"], out Longitude);
Debug.WriteLine($"== Received new Location setting: Lat - {Latitude}, Lon - {Longitude} ==");
}
catch (Exception ex)
{
Debug.WriteLine($"-- C2D Error - {ex.Message} --");
}
}
static void WriteTrace(TraceLevel level, string format, params object[] args)
{
if (TraceOn)
{
Debug.WriteLine(Fx.Format(format, args));
}
}
// Starting at the last Lat/Lon move along the bearing and for the distance to reset the Lat/Lon at a new point...
public static void GetNewDestination()
{
// Get a random Bearing and Distance...
double distance = _random.Next(10); // Random distance from 0 to 10km...
double bearing = _random.Next(360); // Random bearing from 0 to 360 degrees...
double lat1 = Latitude * (Math.PI / 180);
double lon1 = Longitude * (Math.PI / 180);
double brng = bearing * (Math.PI / 180);
double lat2 = Math.Asin(Math.Sin(lat1) * Math.Cos(distance / radius) + Math.Cos(lat1) * Math.Sin(distance / radius) * Math.Cos(brng));
double lon2 = lon1 + Math.Atan2(Math.Sin(brng) * Math.Sin(distance / radius) * Math.Cos(lat1), Math.Cos(distance / radius) - Math.Sin(lat1) * Math.Sin(lat2));
Latitude = lat2 * (180 / Math.PI);
Longitude = lon2 * (180 / Math.PI);
}
}
}
Fill in the Blanks.
As you can see at the top of the Class there are some placeholders where you need to fill in with some data, the easy ones are the Wifi credentials these you hopefully should know for your local network and they will allow the ESP32 module to connect to the internet so that it can communicate with the Azure Azure IoT Hub.
Next we have some Azure IOT Hub settings that are needed and this is so that the ESP32 knows which Hub and device it is as well as the connection string or SAS Token it should use to find the IOT Hub. So where do we get these from? The Azure IoT Hub name and DeviceID we can get from the Azure IoT Hub on the Azure portal so lets head out to out Azure IoT Hub and add a device, if you want more detail on creating your first Azure IoT Hub and adding a device there is no better place than Docs.Microsoft.com
When in the Azure Portal and you have found or created your IOT Hub on the left menu under the Explorer tab click IoT Devices
and then the + Add Device
.
On the new pane enter a meaningful name for your device and ensure that Symmetric Keys
is selected and when your happy click the Save
button at the bottom of the page.
This means we now have the _hubname
which is the name of your IoT Hub and will be at the top of the page as you can see in the first image in this section so for me it’s CAS-Learning-IOTHub
.
Enter that into the placeholder in the code, next is the device name you just created so you know this already and can enter that for the placeholder of _deviceId
but what about that _sasToken
that one is a little more tricky.
Creating the SAS Token.
You can read all about SAS Tokens on the Docs website here but how do we go about creating the Token for our device.
There are a few ways, as the docs suggest we can use the CLI extension command az iot hub generate-sas-token, or the Azure IoT Tools for Visual Studio Code
Lets try the CLI
In the Azure portal in the top right where your account details are shown you can click to open the Azure CLI:
This will open the Azure CLI at the bottom of the browser window and you can do the normal resize things and drag to increase the size or use the icons on the top right of that window.
Now we can use the az iot hub CLI commands to request a SAS Token for our device and this will look something like this:
az iot hub generate-sas-token --hub-name CAS-Learning-IOTHub --device-id NanoFramework-Device1
--login 'HostName=CAS-Learning-IOTHub.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=<SECRET KEY>'
As you can see you need to use the Hub Name and the Device ID which you already have from the previous steps but what about that login part? Well this we get from the Azure IoT Hub menu in the portal and click the ‘Shared Access Policies’ then under the list of policies click iothubowner
and finally copy the ‘Primary Connection String’ and it’s this you need to use as the login in the CLI, just beware that you have to surround the string in the CLI with SINGLE quote marks for it to be accepted and used.
Now we have the connection string we can copy this into out place holder in our code, obviously I have blurred the parts that are my keys but you hopefully get the idea here.
What about VSCode
If you have not discovered already there is a fantastic Azure IOT extention for VSCode and it’s this that will be used here.
Install the extention and follow the set-up instructions on the marketplace so that you can connect to your Azure Account, personally I use the 2nd option to sign in to my account when doing this on a new machine as it’s easier.
Setup Azure IoT Hub through Sign in to Azure
Now your connected in your explorer you will see you ‘AZURE IOT HUB’ tab which for me is at the bottom, open this and select your IOT Hub and then Devices.
Now right clicking on the device will show a context menu where you can just click the ‘Generate SAS token’
The VSCode Command Palette will then show asking for the number of HOURS
that you wish the token to live for so enter a number.
Just beware that this is in hours and not seconds like the CLI so it makes the maths in your head a little easier...
Once you enter this you will notice in the VSCode output window at the bottom your new SASToken will be displayed (CTRL+ALT+O if the window not displayed).
Again take this and place it into the placeholder in the code.
Root Certificate
This one got me at first as it’s a very easy thing to miss so I hope me putting it here helps you or even future ME! get this right.
We need to ensure that the device is connecting to the web and thus IOT Hub with the correct certificate, but they are currently in the process of switching them over from the Baltimore CyberTrust to DigiCert Global G2 now rather than me try to explain this here is a blog post all about it
So at the time of writing this post Azure IOT will only use the Baltimore CyberTrust so lets use that, the process is the same for both anyway. First we need to download the Certificate and there is a page in the Docs that has the links.
Once downloaded we need to upload this file to the ESP32 module and this is where the awesome NanoFramework team have us covered. Connect your device to your PC/Laptop again and in Visual Studio open the NanoFramework Device Explorer
In here we should see our device showing up, if not click the magnifying glass to scan and be sure that the cable you are using is a data cable and not a charging only cable (I throw charging only out now I have wasted too many hours of my life being caught by them…). Once your device is shown click the ‘Edit Network Configuration’ icon as shown boxed in the screen grab.
This opens a dialog box and clicking the last tab ‘General’ we can see the ‘Root CA’ where we can browse for the Cert we downloaded and have it uploaded to the ESP32 device.
Note:
There is no feedback sadly that the certificate is selected to be uploaded so you will have to trust in yourself that you did it right, or like me do it twice to be sure.
Upload our code
Now we have all the placeholders filled in and the Root cert prepared we are ready to upload and test, now as a DotNet developer what is your muscle memory for running code…. Yep click F5… How awesome is that.
The first time you do this it takes a minute or two but subsequent builds are much faster.
Don’t forget you have the full power of the Visual Studio debugger so feel free to add a breakpoint or two and step through the code to watch the magic.
We now have the code running on out little ESP device and we can step through and check the variables etc but how can we see the data arriving at the IoT Hub? Well that can be done very easily again using either the VSCode extention or the Azure CLi.
Monitor using the CLi
Using the same method as shown above open the Azure CLI and use the Azure CLI Command
az iot hub monitor-events --output table --hub-name {YourIoTHubName} --login 'HostName=CAS-Learning-IOTHub.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=<SECRET KEY>'
You will then see the data arriving and displayed inside the CLI, the cool advantage of using the CLI commands is that once you get used to them you can use the Mobile App on your Android/IoS device which is a Xamarin App by the way to query your Azure IoT Hub and devices.
Monitor using VSCode
This is by far the easier way and you right click on the device like you did to generate the SAS Token but now you select the Start Monitoring Built-In Event Endpoint
from the context menu.
You should now see in the Output Window at the bottom the data will start to arrive.
Consuming Messages
If like me you have an S1 Standard
tier IoT Hub then you have a daily limit of 400,000 message which sounds a lot but now lets think about the number of messages this actually gives. Our Device is sending a message every 5 seconds so given the 24 hrs in the day that’s:
24 * 60 * 60 = 86,400 seconds per day
Divide by 5 gives - 17,280 messages per day.
But with a limit of 400,000 per day that is only 23 devices before they are all used up?
Therefore it’s always a good idea when planning your system to think about the number of devices and also the number of messages each will send. Do you really need an update every 5 seconds for example or will every 30 seconds do? That would change this from 23 devices to 138 devices as an example.
Of course if you do need more you can step up to S2 giving 6 million or and S3 giving 300 million but it’s a higher cost.
The other alternative is to add more S1 units and get multiples of the 400,000. It’s always worth doing the maths and picking the correct tier rather than having your boss ask why the Azure bill just jumped, you can looking at the pricing more Here
You can check you message usage on the Overview page of the IoT Hub and look at the usage graphs as well.
Conclusion
So that’s it’s congratulations you now have a cheap £10/$13 ESP32 IoT device connecting over WiFi to Azure IoT Hub and sending messages, all using C# and DotNet and I hope you agree it opens the IoT space to DotNet developers which is awesome.
Connect a sensor or two to the Board and there are millions of sensors to choose from out there even some Temp and Humidity sensors if you really have to, and you have an IoT Solution for your business or your Home Automation and much cheaper and easier than other methods.
Those of you that didn’t just Cut&Paste the code from above like you do over at Stack Overflow will have noticed I added an OnMessage()
method and this I will use in the next post I write where I will show that you can also use NanoFramework and IoT Hub to send Cloud-2-Device messages. These are messages that are sent from the Azure Cloud down to the device to change setting or configure a value, in this case I will be editing the Lat/Lon among other things as well as plotting the points on a Bing Map using PowerBI and Stream Analytics if you can’t wait you can take a quick peak at the Microsoft Docs for that part.
It just leaves me to say thanks to the amazing team behind NanoFramework and also to the DotNet Foundation for supporting and helping the project grow.
Lastly if you have any projects that need support and help from a Freelance DotNet IoT and Mobile developer or just want to chat, geek out or comment on this blog post you can find me over at Twitter @CliffordAgius DM’s are open.
Happy Coding!