Thingsboard.io is a popular IoT platform for device management, data storage and visualisation. It is open-source and allows you to install your own server, and build browser based dashboards. These are 2 advantages it has over Blynk.io which we demo'ed with Industruino earlier.
This blog post aims to show how to get started with Thingsboard and Industruino PROTO or INDIO, and using an Ethernet module for connectivity. We are using Thingsboard's live-demo server, so you will have to signup for an accoun there to do the same.
General documentation is of course available, but the below steps may be sufficient to get you started.
We assume here that you have your Industruino (PROTO or INDIO) and Ethernet module working.
First we need to install a few extra libraries into our Arduino IDE via the library manager (Sketch > Include Library > Manage Libraries):
- Thingsboard
- PubSubClient
- ArduinoJson
- ArduinoHttpClient
DO NOT install the Thingsboard library from the IDE library manager, just use the .zip file from github (via library manager v0.4.0 gave compilation errors)
You may need to rename the Thingsboard example sketches (remove the initial numbers from the file and folder names) to use them in the IDE. We don't need them for below demo.
The Thingsboard library prints some debug into to 'Serial' but on our D21G this should be 'SerialUSB', so you need to edit the library files thingsboard.cpp and replace 'Serial.' with 'SerialUSB.' on the 2 bottom lines in this logger function:
void ThingsBoardDefaultLogger::log(const char *msg) { SerialUSB.print(F("[TB] ")); SerialUSB.println(msg); }
The 2 parameters that link the Industruino to Thingsboard, are the THINGSBOARD_SERVER and the TOKEN. In below example we use the demo server, and we create a unique token for our Industruino device: "industruino_test1". You will have to choose a different token and use that in the below code.
UPLOAD the below code before continuing.
Thingsboard
Login to your account on the demo.thingsboard.io server
Go to devices, and create a new device
Click 'manage credentials' and modify the Access TOKEN to a unique value
Go to dashboards, and create a new dashboard
Edit the dashboard, and click on 'entity aliases'
Create an alias ‘industruino’ as ‘single entity’ and ‘device’ and ‘industruino’ (name of the device you created earlier)
Go back to your device, click Latest Telemetry to see the 2 data values
Select one of the 2 row with the check box, and click 'show on widget', this allows you to add this data to the dashboard
You can add diffent type of cards, charts, controls to the dashboard.
It's relatively easy to add data streams like this, and the visualisations can be highly customised.
The next step is to add remote control to the setup, to be able to use the dashboard to send instructions to the Industruino. This is the RPC feature: Remote Procedure Calls.
In this example, we add 2 controls to our dashboard: a SWITCH to switch the Industruino's backlight on/off, and a KNOB CONTROL to pass an analog value to the Industruino.
Both controls can be added manually to the dashboard, from the control widgets menu. Target device is 'industruino', and the only setting we have to modify is the name of the function that needs to be called on the device.
For the switch, change the 'RPC set value method' to 'example_set_switch'.
For the knob, change the 'set value method' to 'example_set_dial' and the 'get value method' to 'example_get_dial'.
There seems to be a bug in the Thingsboard library: it ignores RPC calls without "params", such as the get value method. To work around this, we can edit the Thingsboard.cpp library file, comment out lines 183-185 for the "params" check, as suggested here. The bug is also mentioned here.
For troubleshooting the RPC, it is useful to have a look at the Device > Audit logs, which show the JSON data of each RPC.
Serial Monitor on the left shows the output when the Industruino is successfully connected to Thingsboard. It sends the dummy data every 3 seconds, and receives the RPC instructions.
The [SDK] messages are printed by the Thingsboard library so you need to modify this to SerialUSB for use with Industruino.
The Industruino shows the last instruction of the control knob, and the LCD backlight.
This is the sketch for the Industruino D21G, be sure to modify your TOKEN. It uses the unique MAC address stored in the RTC chip, and gets an IP address via DHCP.
/* Basic Thingsboard example for Industruino D21G Industruino PROTO or INDIO with Ethernet module > upload this sketch after modifying your own TOKEN (different from below industruino_test1) > the sketch creates 2 random data streams temperature/humidity > the sketch receives instructions from the dashboard: lcd backlight on/off and analog knob value Thingsboard.io: > create an account on live-demo > create a device 'industruino' with credentials: unique access token (different from below industruino_test1) > create a dashboard > edit dashboard, click on 'entity aliases', add alias: name 'industruino', filter type 'single entity', type 'device', device 'industruino' > go back to device > latest telemetry > watch the 2 values for humidity and temperature being updated > device > latest telemetry > check box of 1 value and click 'show on widget' to add to your dashboard for RPC (remote procedure calls) > manually add switch and knob, and attach the functions in tab 'advanced' switch: RPC set value method = example_set_switch knob: set value method = example_set_dial, and get value method = example_get_dial (requires modifying Thingsboard.cpp, comment out lines 183-185 "params" check) */ #include <SPI.h> // for Ethernet #include <Ethernet2.h> // Industruino version of Ethernet2 #include <Wire.h> // for RTC MAC byte mac[6] ; // use Industruino unique MAC EthernetClient ethClient; #include "ThingsBoard.h" #define TOKEN "industruino_test1" // TO BE REPLACED BY YOUR OWN TOKEN #define THINGSBOARD_SERVER "demo.thingsboard.io" ThingsBoard tb(ethClient); #include <UC1701.h> // LCD screen static UC1701 lcd; // 2 dummy variables int temp = 30; float hum = 50; int interval = 0; RPC_Response getDial(const RPC_Data &data) { SerialUSB.println("Received the get dial RPC method"); lcd.setCursor(0, 5); lcd.print("Knob: sending init 5"); return RPC_Response(NULL, 5); // this only works if we modify Thingsboard.cpp, comment out lines 183-185, "params" check } RPC_Response processDialChange(const RPC_Data &data) { float example_dial = data; SerialUSB.print("Received dial update: "); SerialUSB.println(example_dial); lcd.setCursor(0, 5); lcd.print("Knob: "); lcd.print(example_dial, 2); lcd.print(" "); return RPC_Response("OK", 1); } RPC_Response processSwitchChange(const RPC_Data &data) { boolean switch_state = data; SerialUSB.print("Received switch state: "); SerialUSB.println(switch_state); digitalWrite(26, switch_state); // switch Industruino LCD backlight return RPC_Response("OK", 1); } const size_t callbacks_size = 3; RPC_Callback callbacks[callbacks_size] = { { "example_set_dial", processDialChange }, { "example_get_dial", getDial }, { "example_set_switch", processSwitchChange } }; bool subscribed = false; unsigned long timestamp; ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// void setup() { SerialUSB.begin(115200); pinMode(26, OUTPUT); // LCD backlight digitalWrite(26, HIGH); lcd.begin(); lcd.print("Thingsboard demo"); delay(1000); SerialUSB.println("Industruino Thingsboard demo"); readMACfromRTC(); // mac stored in rtc eeprom Ethernet.begin(mac); SerialUSB.print("Ethernet started on IP: "); SerialUSB.println(Ethernet.localIP()); lcd.setCursor(0, 2); lcd.print("MAC= "); for (int i = 0; i < 6; i++) { lcd.print(mac[i], HEX); if (i < 5) lcd.print(":"); } lcd.setCursor(0, 3); lcd.print("IP= "); lcd.print(Ethernet.localIP()); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// void loop() { delay(100); if (!tb.connected()) { subscribed = false; // Connect to the ThingsBoard SerialUSB.print("Connecting to: "); SerialUSB.print(THINGSBOARD_SERVER); SerialUSB.print(" with token "); SerialUSB.println(TOKEN); if (!tb.connect(THINGSBOARD_SERVER, TOKEN)) { SerialUSB.println("Failed to connect"); return; } } if (!subscribed) { SerialUSB.println("Subscribing for RPC..."); if (!tb.RPC_Subscribe(callbacks, callbacks_size)) { SerialUSB.println("Failed to subscribe for RPC"); return; } SerialUSB.println("Subscribe done"); subscribed = true; } if (millis() - timestamp > 3000) { SerialUSB.print("[" + String(millis() / 1000.0) + "] "); SerialUSB.println("Sending data..."); // Uploads new telemetry to ThingsBoard using MQTT. // generate dummy data = changing temp and humidity temp += random(3) - 1; // -1, 0, +1 temp = constrain(temp, -20, 50); hum += (random(3) - 1) * 2.5; // -2.5, 0, +2.5 hum = constrain(hum, 0, 100); tb.sendTelemetryInt("temperature", temp ); tb.sendTelemetryFloat("humidity", hum ); timestamp = millis(); } tb.loop(); } ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // the RTC has a MAC address stored in EEPROM - 8 bytes 0xf0 to 0xf7 void readMACfromRTC() { Wire.begin(); SerialUSB.println("READING MAC from RTC EEPROM"); int mac_index = 0; for (int i = 0; i < 8; i++) { // read 8 bytes of 64-bit MAC address, 3 bytes valid OUI, 5 bytes unique EI byte m = readByte(0x57, 0xf0 + i); SerialUSB.print(m, HEX); if (i < 7) SerialUSB.print(":"); if (i != 3 && i != 4) { // for 6-bytes MAC, skip first 2 bytes of EI mac[mac_index] = m; mac_index++; } } SerialUSB.println(); SerialUSB.print("Extracted 6-byte MAC address: "); for (int u = 0; u < 6; u++) { SerialUSB.print(mac[u], HEX); if (u < 5) SerialUSB.print(":"); } SerialUSB.println(); } ///////////////////////////////////////////////////////////////////////// // the RTC has a MAC address stored in EEPROM uint8_t readByte(uint8_t i2cAddr, uint8_t dataAddr) { Wire.beginTransmission(i2cAddr); Wire.write(dataAddr); Wire.endTransmission(false); // don't send stop Wire.requestFrom(i2cAddr, 1); return Wire.read(); }