The Project
This tutorial will outline how to create an IR Remote using the ESP32 and then control it from the IoT hub on Microsoft Azure.
Driving an IR remote transmitter using an Arduino is simple, as there is a library, called IRremote.h which does all the hard work. You just need to connect your IR transmitter module signal pin to the appropriate Arduino pin, via a current limiting resistor and you are done. Connecting an Arduino to the cloud takes a bit more work (depending on the model you are using), which is why we wanted to use the ESP32.
Unfortunately, the standard IRRemote.h Arduino library only supports receiving IR signals on the ESP32 not transmitting them. Fortunately, Andreas Spiess has forked the standard library and added ESP32 transmission capability. You will need to
download the ESP32-IRremote library, so we can use it with the ESP32. Andreas did this by using ledC PWM. You can now select any pin to use with IRsend(pin). Note this is only for the ESP32, the other board types have defined pins you have to use due to the assigned timers.
The Duinotech Infrared Transmitter Module
The IR transmitting module which I used is the one from
Jaycar (branded Duinotech). There is a data sheet available on the Jaycar site but it is fairly sparse and doesn't clearly define the pins on the module.
It appears that this module is based on the KY-005 INFRARED TRANSMITTER MODULE. The specifications for which are:
Operating Voltage |
5V |
Forward Current |
20 ~ 60 mA |
Power Consumption |
90mW |
Operating Temperature |
-25°C to 80°C [-13°F to 176°F] |
Dimensions |
18.5mm x 15mm [0.728in x 0.591in] |
This being the case, the pin out is as follows:
The signal pin is clearly labeled with an S, the middle pin is GND via a resistor (* which you have to fit yourself to the module) and GND is connected to the third pin (with the "-" adjacent to it). This module is just an infrared diode (which emits at a wavelength of 940 nm).
Thus, we can drive it like any other diode via a current limiting resistor. The value of the resistor depends on what voltage your micro controller digital outputs (DO) are switching, the desired diode forward current and the forward voltage drop characteristic of the diode. So for our design:
V
DO = 3.3V
I
f = 20 mA
V
f = 1.2V (nominally 1.1V but I measured this using my LCR meter)
Then, R = (V
DO - V
f) / I
f
= (3.3 - 1.2) / 0.02
= 105
Ω
We will use a 100
Ω resistor in our circuit.
The CIR (Commercial Infrared) Transmission Protocol
As there are usually other sources of infrared radiation (e.g. sunlight and incandescent or LED lights), the 940nm IR transmitter is modulated by a carrier frequency in the 32-40 kHz range. CIR receivers incorporate a bandpass filter tuned to this carrier frequency. This allows the receiver to discriminate between the modulated IR signal and any ambient, unmodulated IR. In effect, the IR receiver is double tuned both to the wavelength of the IR radiation and to the carrier frequency.
Three factors influence CIR range. In order of decreasing importance they are: the power level of the IR emitter, the IR wavelength, and the carrier frequency. An IR emitter's output is proportional to the current through the emitter. Increasing the current will increase the power. Because the duty cycle is usually 50% or less, the emitter can be driven with quite high currents. For optimal range, the IR wavelength of the emitter and receiver should match.
A similar protocol to CIR is IrDA. IrDA was popular in the late 1990's but has largely been replaced by Bluetooth and WiFi. IrDA was designed to be very short range (< 1 m). It does not use any secondary carrier but directly modulates the 850nm IR with the data. Because of this, it is susceptible to interference from ambient IR. In addition, the IrDA transmitter is usually lower power than a CIR transmitter.
An IrDA transmitter with a CIR receiver is a mismatch as is a CIR transmitter with a IrDA receiver. They operate on a different wavelength (940 nm vs 850 nm), IrDA isn't modulated, and the beam angles are different (IrDA limits the beam angle to
±15
° while most CIR emitters are
±40
° or greater). Such mismatches have major effects on range and reliability.
We will use CIR in our design.
For RF control, both the transmitter and receiver need to be tuned to the same carrier frequency and need to use the same type of modulation. Most RF remotes use ASK (Amplitude Shift Keying) or OOK (On-Off Keying). OOK is really just a special case of ASK. OOK is also called CPCA (Carrier Present, Carrier Absent). You can have a look at the IRremote library to see how this coding is achieved.
Microsoft Azure
Azure is Microsoft's catch all name for their cloud services. It covers over 100 different services. The service of interest to us is
IoT Hub. You can use Azure IoT Hub to securely connect, monitor and manage billions of devices to develop Internet of Things (IoT) applications. To get started we will connect just one device!
You will need to sign up for a free Azure account. Follow the link above and do this. For some reason Microsoft make you provide credit card details, even for the free account. Note that when you sign up, the email address you provide becomes the name of the default active directory (which wouldn't be my first preference).
It will be interesting comparing the Microsoft IoT hub functionality with node-red, which is another dashboard option that we have had experience with. At this stage I suspect that node-red is much cheaper (free) and simpler but Azure is more robust, secure and scalable. The key features of IoT hub are:
- Bidirectional communication with LOTS of devices. Use device-to-cloud telemetry data to understand the state of your devices and define message routes to other Azure services without writing any code. In cloud-to-device messages, reliably send commands and notifications to your connected devices – and track message delivery with acknowledgement receipts. Device messages are sent in a durable way to accommodate intermittently connected devices.
- Authentication per device. Set up individual identities and credentials for each of your connected devices, and help retain the confidentiality of both cloud-to-device and device-to-cloud messages. To maintain the integrity of your system, selectively revoke access rights for specific devices as needed.
- Automated device registration. Speed up your IoT deployment by registering and provisioning devices with zero touch in a secure and scalable way. IoT Hub Device Provisioning Service supports any type of IoT device compatible with IoT Hub.
- Use IoT Edge. Take advantage of IoT Edge to make hybrid cloud and edge solutions. IoT Edge provides orchestration between code and services so they flow securely between cloud and edge to distribute intelligence across a range of devices. Enable artificial intelligence and other advanced analytics at the edge.
Once you have signed up for Azure, you will be presented with a dashboard similar to that shown above.
Create an IoT Hub
Microsoft call their menus "blades" in Azure. No idea why, maybe because it sounds cooler than menu? Anyway, click on the + Create a resource link on the blade to the left of the dashboard. This will open the Azure Marketplace.
In the Marketplace, click on Internet of Things. This will provide a new list of menu options to the right.
We want IoT Hub at the top. Click on this to setup your hub. For subscription select Free Trial and for Resource Group, Create new.
The free tier is intended for testing and evaluation. It allows 500 devices to be connected to the IoT hub and up to 8,000 messages per day. Each Azure subscription can create one IoT Hub in the free tier.
A resource group is a container that holds related resources for an Azure solution. The resource group can include all the resources for the solution, or only those resources that you want to manage as a group.
Select the Region closest to your location. In Australia the options are East and Southeast, which I think refer to Sydney and Melbourne respectively.
To create an IoT hub, you must name the IoT hub. This name must be unique across all IoT hubs. The IoT hub will be publicly discoverable as a DNS endpoint, so make sure to avoid any sensitive information while naming it. Once created, the name can't be changed.
Click the button at the bottom labelled - Next: Size and Scale >>
For pricing and scale tier, select F1: Free tier. That is all that you can adjust on this screen. Click Review + create.
When all previous steps are complete, you can create the IoT hub. Click Create to start the back-end process to create and deploy the IoT hub with the options you chose.
It can take a few minutes to create the IoT hub as it takes time for the back-end deployment to run on the appropriate location servers. Once your new IoT resource has been created, you can customise your dashboard.
Add an IoT Device
Before a device or module can connect to your IoT hub, there must be an entry for that device in the IoT hub's identity registry. A device must also authenticate with the IoT hub based on credentials stored in the identity registry. The device or module ID stored in the identity registry is case-sensitive.
To add a new IoT device, click on + Add, and the Add Device blade will be displayed.
- Device ID: A case-sensitive string (up to 128 characters long) of ASCII 7-bit alphanumeric characters.
- Authentication Type: Symmetric Key or X.509 Certificate. I used Symmetric Key. The differences are:
- Symmetric Key: a unique identity key (security tokens) for each device, which can be used by the device to communicate with the IoT Hub.
- X.509 Certificate: uses an on-device X.509 certificate and private key as a means to authenticate the device to the IoT Hub. This authentication method ensures that the private key on the device is not known outside the device at any time, providing a higher level of security.
- Auto Generate Keys: tick.
- Connect device to IoT hub: enable.
Click on Save, and your new device will be added to the hub. Click on the device ID of the newly added device to see the security keys and connection strings. You will need the device ID and a copy of the primary connection string for insertion into your ESP32 sketch. Now onto the ESP32.
ESP32 Software
You can download a copy of my ESP32 sketch from the
Reefwing Gist. You will need to fill in your SSID, password and primary connection string where indicated.
I spent quite a bit of time trying different tool chains to get everything configured and talking. My initial preference was to use Eclipse with the Arduino tool chain. This would give me a proper IDE and a remote control library that I knew was compatible with all the Arduino's out there.
Unfortunately, importing custom libraries is a bit problematic for the two Eclipse Arduino plug-ins available. It is theoretically possible but I ran out of patience trying to get it to work. The ESP-IDF has its own remote control library but it is not as well documented as the Arduino library. I'm also not familiar with coding the ESP32's natively.
While looking for a way to receive the messages sent from the ESP32 to the cloud I discovered a plug in for Visual Studio Code. It so happens that there is also a plug in for Arduino. Since I was already using this to monitor my IoT hub traffic, I decided to give it a crack with programming the ESP32. It just worked - I was astonished! It does use the Arduino IDE tool chain, so that may be why it was so seamless as I had already got everything working with that first. If IntelliSense complains about a missing library, right click on the light build and edit the c_cpp_properties.json file which contains the include path.
The other thing you will probably have to do is add:
"output": "../build",
To the
.vscode/arduino.json file, which can be found under the work space for your sketch. The same location as the c_cpp_properties.json file. If output is not set, Arduino will create a new temporary output folder each time it compiles your sketch, which means it cannot reuse the intermediate result of the previous build, leading to long verify/upload time. So it is recommended to set the field. Arduino requires that the output path should not be the workspace itself or subfolder of the workspace, otherwise, it may not work correctly. By default, this option is not set. Again, no idea why, you will get a warning if it isn't set when you verify.
Whether you use the Arduino IDE or Visual Studio Code, you need to download the Azure IoT library:
ESP32_AzureIoT - An Azure IoT Hub library for ESP32 devices in Arduino. Unzip and copy this to your Arduino libraries folder.
As a first test, load the GetStarted.ino sketch from the examples folder in the library you just downloaded. This sketch will connect to the IoT hub and continuously send messages containing fake data. You will need to fill in the following blanks in the sketch:
- DEVICE_ID - copy from your IoT registered device;
- connectionString - copy from the primary connection string;
- ssid - the displayed name for your WiFi network; and
- password - for your WiFi network.
Connect to your ESP32, check the port and board type, then compile and upload the sketch. Open up the serial monitor at 115,200 baud so that you can see what is happening. The monitor should be displaying something like the following.
To confirm that the IoT hub is receiving these messages have a look at the Azure dashboard and you should see these messages arriving.
If you have Visual Studio Code, you can use the
Azure IoT extension to monitor messages to your IoT hub. Just select your device and then right click and Start monitoring D2C (Device to Cloud) message. You can also send messages from the cloud to your device from here.
Once this was working, I updated the code to just send a heart beat message back to the cloud, letting us know that it was still alive. Every time the ESP32 does this, it broadcasts an IR remote control code three times. This is the usual methodology for remote controls. Currently it is just broadcasting the Sony power code, but we will look at ways we can start/stop the broadcast and change the code via Azure.
Cloud to Device Message Lifecycle
To guarantee at-least-once message delivery, IoT Hub persists cloud-to-device messages in per-device queues. Devices must explicitly acknowledge completion for IoT Hub to remove them from the queue. This approach guarantees resiliency against connectivity and device failures.
When the IoT Hub service sends a message to a device, the service sets the message state to Enqueued. When a device wants to receive a message, IoT Hub locks the message (by setting the state to Invisible), which allows other threads on the device to start receiving other messages. When a device thread completes the processing of a message, it notifies IoT Hub by completing the message. IoT Hub then sets the state to Completed.
The max delivery count property on IoT Hub determines the maximum number of times a message can transition between the Enqueued and Invisible states. After that number of transitions, IoT Hub sets the state of the message to Dead lettered.
The diagram above shows the lifecycle state graph for a cloud-to-device message in IoT Hub. Luckily, sending messages is a lot more straight forward than implementing the message lifecycle.
Controlling the ESP32 via Azure
Now that we have our ESP32 talking to Azure and broadcasting an IR code burst every 10 seconds we want to be able to control this via the cloud. The easiest way to do this is using Visual Studio Code again.
If you right click on the device, shown in Explorer under Azure IOT HUB DEVICES, then the window above is displayed. The two ways we will look at communicating with our device via the cloud is:
- Cloud to Device (C2D) Messaging; and
- Triggering a defined device method.
You can try out both.
Cloud to Device Messaging
After uploading the sketch to your ESP32, open the serial monitor from the Arduino IDE. I found the serial monitor function in Visual Studio Code was a bit dodgy. Select Send C2D (Cloud to Device) Message to Device, and a message entry window will open. Type in whatever you want and hit return.
In the Azure IoT Toolkit output window you should see:
[C2DMessage] Sending message to [ESP32_IRBeacon_1] ...
[C2DMessage] [Success] Message sent to [ESP32_IRBeacon_1]
A second or so later, the following will appear in the Serial Monitor:
Info: >>>Received Message [1], Size=14 Message test message
Message callback:
test message
The function which handles message handling in the ESP32 code is MessageCallback(const char* payLoad, int size). The call back function is set during setup() using:
Esp32MQTTClient_SetMessageCallback(MessageCallback);
Invoke a Direct Method
This is the way that I chose to control the IR beacon (since that is its purpose). You can define what methods you want to support in your code. Currently we are only handling start and stop but it would be trivial to add another method to set the IR code transmitted.
The process for invoking a method is the same as for sending a C2D message. Right click on your device in Explorer and select "Invoke Direct Method". A text entry window will open, type in your method name (e.g. stop) and hit enter. In the Azure IoT Toolkit output window you should see:
[DirectMethod] Invokeing Direct Method [stop] to [ESP32_IRBeacon_1] ...
[DirectMethod] Invokeing Direct Method [start] to [ESP32_IRBeacon_1] ...
Yes whoever wrote this code couldn't spell invoking! Then in the Serial monitor:
Info: Try to invoke method stop
Info: Stop sending IR burst and heart beat
The function which handles method handling in the ESP32 code is DeviceMethodCallback(). This call back function is set during setup() using:
Esp32MQTTClient_SetDeviceMethodCallback(DeviceMethodCallback);
Conclusion
If all you want is a dashboard for your IoT application then Node-Red is MUCH simpler to implement. If you need an industrial strength solution then you need something like Azure IoT hub.
Actually, using Visual Studio Code (VSC) was a pleasure, and this will be my go to IDE for Arduino from now on. Controlling and Monitoring your IoT devices via VSC was also very easy once you work out how it operates. The user interface is not very discoverable, you need to hit F1 to access most of the Arduino and Azure commands.
I will publish a short follow up article providing a simple PCB to mount the diagnostic LED's, IR transmitting module and the ARM/DEBUG switch. It is a simple enough circuit that you could do it on a breadboard or veroboard. Note that the Duinotech ESP32 doesn't leave any pins free on one side of a standard breadboard, due to its width. See the photo above. This is one reason I decided to use a PCB.