IOT stands for Internet of Things and refers to small devices equipped with direct internet connectivity, without needing an external computer. They are usually composed of a main microcontroller and an additional connectivity (eg. ESP8266 for Wifi, ENC28J60 for Ethernet) or they are complete SOC including both the processing unit and the connectivity modems in one single package. Regardless of their form or shape, these devices open new applications for remote sensing and controlling, and bring system automation to a new level of performance. Examples of such devices were presented on this blog even before IOT was coined as a buzzword. See the Ethernet controlled power relay based on enc28j60 or the development of first prototypes of what is now uRADMonitor.
Simple IOT temperature sensorTo exemplify the IOT concept I propose the following architecture:
- several hardware remote nodes, each using a microcontroller, a sensor and a communication modem
- the backend, comprised of a data interface (data API and a database)
On the hardware side, we need the following components:
- Wemos D1 Lite
- DS18B20 temperature shield
- Battery shield
- External Lithium rechargeable battery
For the backend I will demo using the open source EXP server protocol introduced with the KIT1. This is handled by the uRADMonitor server, but alternatively a simple PHP script can be used too.
Assembling the hardwareThere are multiple way of putting these together, I went for putting the Wemos D1 at the bottom, the battery shield in the middle, and the Temperature shield at the top for better air contact and less internal heating interference. This means we solder the female headers to the Wemos D1, the mixed headers to the Battery Shield and the male headers to the DS18B20 Temperature shield. Here's the result:
The PCBs can now be stacked in the proposed order, with the Wemos at the bottom and the DS18B20 sensor at the top. Here's the assembled device:
With the simple IOT device ready, we need to program it. The firmware needs to setup the Wifi connection to an Internet Wireless Router, then periodically read the temperature values and send them to the backend. Before we can start, there are few tools we need to install:
- Arduino IDE download
- CH340G driver also available here.
- Install Wemos D1 support over Arduino. First, open Preferences and add "http://arduino.esp8266.com/stable/package_esp8266com_index.json" to Additional Boards Manager URLs:
Open Boards Manager and install the esp8266:
Finally, we're all set and you can select the Wemos D1 R2 & mini from the Tools -> Board menu:
Under Tools -> Port, select the corresponding serial port as allocated to the CH340G connected to your USB port. Go to Tools->Upload speed, and select 921600.
Simple IOT firmwareCreate a new Arduino sketch. We will be using a few extra libraries: the OneWire and the DallasTemperature, so go to Sketch->Include Library->Manage Libraries and add them:
The following code reads the DS18B20 temperature:
// DS18B20 libraries
#include <OneWire.h>
#include <DallasTemperature.h>
// sensor settings
#define SENSOR_BUS D2 // the DS18B20 has the sensor connected to D2
OneWire oneWire(SENSOR_BUS); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
DallasTemperature sensor(&oneWire); // Pass our oneWire reference to Dallas Temperature.
...
sensor.begin(); // Start up the sensor library
...
sensor.requestTemperatures(); // Send the command to get temperature readings
Serial.print("Temperature is: ");
Serial.println(sensor.getTempCByIndex(0));
Now we need to add ESP8266 code logic, to make the module connect to an Internet Wireless Router at program start, then periodically read temperature and send it to the backend.
The uRADMonitor EXP ProtocolThe EXP protocol was introduced with the open source KIT1 design, to implement a way of building custom IOT devices with a variable number of sensors. It defines the communication rules between an IOT Device ("Device") and a backend server ("Server") for the purpose of exchanging Sensor Data ("Data"). Each Device is uniquely identified using a Device ID allocated by the Server. All devices must have an Owner, identified with a UserId / UserKey pair.
The EXP Protocol is a bidirectional communication protocol - for the very minimum, the Server needs to return a unique allocated Device ID, the very first time a device connects (or in case the device reports an invalid Device ID). Most of the traffic however is for uploading data from the IOT Device to the Server. The EXP Protocol uses HTTP Post to send the data to the server. Each call needs to be authenticated with a userId/userKey pair sent along with the HTTP Post headers, for every call. The HTTP Post headers must also contain the Device ID:
- http.addHeader("X-User-id", userId);
- http.addHeader("X-User-hash", userKey);
- http.addHeader("X-Device-id", deviceID);
Device ID The Device ID is an 8 digit HEX number in the form 13XXXXXX. 13 is the Device class and is immutable, while the following digits are the Device number and are unique from one unit to another. The Device ID is saved in the EEPROM at address 0, using 4 bytes. EEPROM On startup, the Device reads its own Device ID from the EEPROM. The very first time, the Device number will be 0 (invalid) but the Device class will be added (13000000). Every minute, the temperature sensor is read, and a HTTP Post is issued to the Server. If the Server receives an invalid Device ID (like 13000000), it will allocate a new valid Device ID and will return it as a response to the HTTP Post in JSON Format. The IOT Device will parse the response, and if a Device ID is received, it will store it to the EEPROM so on the next call will report with a valid Device ID. Owner Authentication
Each Device belongs to an Owner, identified with a User Id / User Key pair. You will need to register a user account on the uRADMonitor Dashboard.
The User Id / User Key pair needs to be sent out with each HTTP Post call.
The sensor Data
Each HTTP Post calls a RESTful API on the server. The format is:
http://data.uradmonitor.com/api/v1/upload/exp/
, followed by a variable number of key-value pairs separated by slash sign "/". Each key identifies a specific sensor parameters, as per the following list:
// expProtocol
#define ID_TIME_SECONDS "01" // compulsory: local time in seconds
#define ID_TEMPERATURE_CELSIUS "02" // optional: temperature in degrees celsius
#define ID_PRESSURE_PASCALS "03" // optional: barometric pressure in pascals
#define ID_HUMIDITY_RH "04" // optional: humidity as relative humidity in percentage %
#define ID_LUMINOSITY_RL "05" // optional: luminosity as relative luminosity in percentage ‰
#define ID_VOC_OHM "06" // optional: volatile organic compounds in ohms
#define ID_CO2_PPM "07" // optional: carbon dioxide in ppm
#define ID_CH2O_PPM "08" // optional: formaldehyde in ppm
#define ID_PM25_UGCM "09" // optional: particulate matter in micro grams per cubic meter
#define ID_BATTERY_VOLTS "0A" // optional: device battery voltage in volts
#define ID_GEIGER_CPM "0B" // optional: radiation measured on geiger tube in cpm
#define ID_INVERTERVOLTAGE_VOLTS "0C" // optional: high voltage geiger tube inverter voltage in volts
#define ID_INVERTERDUTY_PM "0D" // optional: high voltage geiger tube inverter duty in ‰
#define ID_VERSION_HW "0E" // optional: hardware version
#define ID_VERSION_SW "0F" // optional: software firmware version
#define ID_TUBE "10" // optional: tube type ID
To report a temperature reading, the HTTP Post call URL must be:
http://data.uradmonitor.com/api/v1/upload/exp/01/120/02/26.53
Reporting the time with "01" is compulsory.
The code becomes:
// send a HTTP Post to the backend, using the EXP protocol
bool sendSensorData (uint32_t seconds, float temperature, String userId, String userKey, uint32_t devId) {
Serial.println("sendSensorData " + String(seconds) + " " + String(temperature) + " " + userId + " device:" + String(devId, HEX));
// prepare post data
String postUrl = String(ID_TIME_SECONDS) + "/" + String(seconds, DEC) + "/" + String(ID_VERSION_HW) + "/" + String(VER_HW, DEC) + "/" + String(ID_VERSION_SW) + "/" + String(VER_SW, DEC) + "/" + String(ID_TEMPERATURE_CELSIUS) + "/" + String(temperature);
Serial.println(postUrl);
// prepare http client to do the post
HTTPClient http;
// data is sent as URL / RESTful API
http.begin(URADMONITOR_SERVER + postUrl);
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
// the expProtocol requires these customs headers
http.addHeader("X-User-id", userId);
http.addHeader("X-User-hash", userKey);
http.addHeader("X-Device-id", String(devId, HEX));
int httpCode = http.POST("");
// check server results
if (httpCode != 200) {
Serial.println("not successful");
http.end();
return false;
} else {
char buffer[200] = {0};
http.getString().toCharArray(buffer, 200);
Serial.print("Server response:"); Serial.println(buffer);
// check response
char value[10] = {0}; // this has to be inited, or when parsing we could have extra junk screwing results
if (jsonKeyFind(buffer, "setid", value, 10)) {
Serial.print("Server allocated new ID:"); Serial.println(value);
deviceID = hex2int(value);
eeprom_write_dword(EEPROM_ADDR_DEVID, deviceID);
EEPROM.commit();
}
http.end();
return true;
}
}
Data access and visualisationThe automated device ID allocation means the same code can be installed on multiple devices, and they will be individually addressable via their distinct Device IDs. Once connected, the device will start uploading measurements automatically, and these can be accessed via the API in numerical form, or charts can be seen on the uRADMonitor website. To open a specific device ID, you can use http://www.uradmonitor.com/?open=13XXXXXX
where the parameter needs to be a valid device ID. Here are the measurements taken by the assembled device:
Comments