How to IoT: MQTT on Nucleo and ESP8266
Have you ever heard of Internet of Things? How about MQTT protocol? I think so. But maybe you didn’t start an IoT project of your own yet. If yes, the time is now. In this post you will find the details of my project on STM32F103 Nucleo board running FreeRTOS and ESP8266 WiFi module, logging temperature data to Eclipse MQTT broker using Paho Embedded library.
Short on MQTT
It is a data protocol which is the core of the most IoT devices. What makes it so special is the simplicity and efficiency of course. There is a broker (host) and multiple clients. Clients have 2 cards: they can publish a message to the broker with a topic name or they can subscribe to a topic and receive all messages that are posted to this topic. All communication passes through the broker and that is the way how it becomes efficient. A publisher has to publish a message only once, and the broker re-publishes the message to all subscribers (they don’t have to poll the broker). You can evaluate one of the public brokers or install your own Mosquitto which is free and nice.
Temperature Logger Project
Hardware
The project hardware consists of STM32f103 Nucleo-64 board featuring ARM Cortex-M3 and ESP8266 WiFi SoC (ESP-01 module in my case). The module is connected to the board via PA9(TX) and PA10(RX) pins that are internally connected to USART1. Communication baud-rate is 115200 by default.
WiFi over ESP8266
Espressif’s ESP8266 SoC is very popular among DIY people since it makes things easy with integrated TCP/IP stack and possibility to work with memory constrained microcontroller systems. It only needs full-duplex UART serial port which is nearly a default hardware in every MCU. For me the only drawback is that it operates over a terminal-friendly but not software-friendly AT command set and it makes the client interface very complex.
Software
For the ones who want to try it now, the project is on GitHub and is implemented in C language. It is ready-to-compile using GNU MCU Eclipse (compile instructions). To use the project as-is, don’t forget to edit your WiFi username and password in wifi_credentials.hfile.
ESP8266 IoT Driver
I implemented as-simple-as-possible client-mode esp8266 driver that takes care of the basic operations. The driver also uses a stream-based UART driver which is built on top of c-circus -circular buffer. Both circular buffer and UART implements can be found under this repository.
Paho Embedded MQTT Library
Eclipse supported Paho library is ported for embedded C applications and can be found here. My project uses the MQTTPacket part of the library as well as the basic transport implementation. Paho Embedded also has MQTTClient codes but it is not directly compatible with ESP8266 and supports only FreeRTOS+TCP. That is why I only made use of the Packet part because it is the core.
The Project Code
This project [download] is simple for convenience. It reads the STM32 internal temperature sensor using ADC and publishes it to tcp://iot.eclipse.org:1883in every 5s inside a FreeRTOS task. The bare-metal version of transport.c from Paho implements non-blocking transport_sendPacketBuffernb(...)and transport_getdatanb(...) methods even though that are not crucial in RTOS environment. The operational block scheme of prvTemperaturePublisher(...)task is shown below.
The main.cfile content is given below. It blinks the LED on board when it is transmitting the data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
/** * @file main.c * @author Atakan S. * @date 01/06/2018 * @version 1.0 * @brief PAHO Embedded demo publishes temperature to MQTT via ESP8266. * * @copyright Copyright (c) 2018 Atakan SARIOGLU ~ www.atakansarioglu.com * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ // Include #include "FreeRTOS.h" #include "task.h" #include "MQTTPacket.h" #include "transport.h" #include "networkwrapper.h" #include "temperature.h" #include "led.h" #include <stm32f10x.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> // Settings. #define CONNECTION_KEEPALIVE_S 60UL /* * @brief Publishes temperature value to mqtt. * @param pvParameters Task call parameters. */ static void prvTemperaturePublisher(void *pvParameters) { unsigned char buffer[128]; MQTTTransport transporter; int result; int length; // Transport layer uses the esp8266 networkwrapper. static transport_iofunctions_t iof = {network_send, network_recv}; int transport_socket = transport_open(&iof); // State machine. int internalState = 0; while(true) { switch(internalState){ case 0: { // Turn the LED off. vLedWrite(0, false); // Initialize the temperature sensor. temperature_init(); // Initialize the network and connect to network_init(); if(network_connect("iot.eclipse.org", 1883, CONNECTION_KEEPALIVE_S, false) == 0){ // To the next state. internalState++; } } break; case 1: { // Populate the transporter. transporter.sck = &transport_socket; transporter.getfn = transport_getdatanb; transporter.state = 0; // Populate the connect struct. MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer; connectData.MQTTVersion = 3; connectData.clientID.cstring = "TemperaturePublisher"; connectData.keepAliveInterval = CONNECTION_KEEPALIVE_S * 2; length = MQTTSerialize_connect(buffer, sizeof(buffer), &connectData); // Send CONNECT to the mqtt broker. if((result = transport_sendPacketBuffer(transport_socket, buffer, length)) == length){ // To the next state. internalState++; } else { // Start over. internalState = 0; } } break; case 2: { // Wait for CONNACK response from the mqtt broker. while(true) { // Wait until the transfer is done. if((result = MQTTPacket_readnb(buffer, sizeof(buffer), &transporter)) == CONNACK){ // Check if the connection was accepted. unsigned char sessionPresent, connack_rc; if ((MQTTDeserialize_connack(&sessionPresent, &connack_rc, buffer, sizeof(buffer)) != 1) || (connack_rc != 0)){ // Start over. internalState = 0; break; }else{ // To the next state. internalState++; break; } } else if (result == -1) { // Start over. internalState = 0; break; } } } break; case 3: { // Turn the LED on. vLedWrite(0, true); // Set delay timer. TickType_t wakeTime = xTaskGetTickCount(); // Populate the publish message. MQTTString topicString = MQTTString_initializer; topicString.cstring = "temperature/value"; unsigned char payload[16]; length = MQTTSerialize_publish(buffer, sizeof(buffer), 0, 0, 0, 0, topicString, payload, (length = sprintf(payload, "%d", (int)temperature_read()))); // Send PUBLISH to the mqtt broker. if((result = transport_sendPacketBuffer(transport_socket, buffer, length)) == length){ // Turn the LED off. vLedWrite(0, false); // Wait 5s. vTaskDelayUntil(&wakeTime, pdMS_TO_TICKS(5000)); } else { // Start over. internalState = 0; } } break; default: internalState = 0; } } } /* * @brief Time provider for the networkwrapper.. * @return Time in ms. */ long unsigned int network_gettime_ms(void) { return (xTaskGetTickCount() * portTICK_PERIOD_MS); } /* * @brief Main. * @return Non-zero on error. */ int main(void) { // Create the task. xTaskCreate(prvTemperaturePublisher, "TempPub", 512UL, NULL, (tskIDLE_PRIORITY + 1), NULL); // Start FreeRTOS. vTaskStartScheduler(); // Program should not reach here. return 1; } |
Note that networkwrapper.c can easily be modified to use another network module since it is flexible. Currently it uses ESP8266 driver and has several state machines as an overlay to the driver.
This project is implemented on a MCU with 20kB RAM but it uses less than 8kB RAM. ESP8266 driver needs several buffers to keep things smooth. If there is a tighter RAM limitation you can modify buffer sizes in ESP8266Client.c as well as networkwrapper.c depending on your application.
As the final note, since this project uses TCP connection (default for MQTT) we need to set keep-alive time in CONNECTION_KEEPALIVE_S and make sure it is long enough so that you can transmit messages to keep the connection alive before it times out.
Verification
I use an Android MQTT Client app to see the messages I sent from Nucleo. From the app, I subscribe to temperature/value topic and receive the messages in dashboard screen.
Final Words
The implemented code is robust enough to return to the beginning when things go wrong. However, to improve more, it is possible to send PINGREQ and to get PINGRESP between PUBLISH messages to be sure that the broker is listening. Another way is using non-zero QoS level (i.e. QoS1 or QoS2) but handling the response requires more complex background operations. Moreover, to generalize this project more, the FreeRTOS task can wait on a queue and publish the messages that were sent to that queue by other tasks.
Keep developing ?