Thingsboard is an open source software dedicated to connecting many IoT devices. While the uses of Thingsboard and RG-15 Sensors are limitless, the example provides a simple dashboard that allows easy setup, logging, and status control.
Docker
In this example a docker container with influx is used. Docker has requirements which usually are covered by modern processors:
- 64-bit processor with Second Level Address Translation (SLAT)
- 4GB system RAM
- BIOS-level hardware virtualization support must be enabled in the BIOS settings. For more information, see Virtualization.
Please install Docker and WSL 2, which can be installed with a powershell command:
wsl --install
If any step in docker building is confusing please refer to docker install guide.
Thingsboard
Download this file and place it in a new folder:
open a terminal:
cd folder/you/created
docker-compose pull
docker-compose up
docker-compose start
After this you should be able to go to http://localhost:8080 and see:
Username: tenant@thingsboard.org
Password: tenant
ThingsBoard Configuration
Provided below in the download are two .json files which configure your thingsboard instance. import raindashboard.json into the dashboards tab and rainguage.json into the device profile tab
Before moving to the arduino setup we need a provision key from the device profile we just imported. Select Device Profiles -> rainguage -> device provisioning. we need both the “provision device key” and the “Provision device secret”.
Warning: because of the device profile being imported you will have the same key as the tutorial. It is highly suggested to change it if security is a concern.
After both these are running you should now be able to start connecting devices to the Thingsboard server.
Arduino
The Arduino code can be used without any need to code. After uploading the code the serial monitor will ask for information on wifi and Thingsboard credentials. In your prefered serial monitor type in each credential prompted by the Arduino.
#include <SPI.h>
#include <PubSubClient.h>
#include <WiFiNINA.h>
#include <LittleFS_Mbed_RP2040.h>
#include <StringSplitter.h>
#include <simpleRPC.h>
///////please enter your sensitive data in the Secret tab/arduino_secrets.h
String ssid = ""; // your network SSID (name)
String psw = ""; // your network password (use for WPA, or use as key for WEP)
String key = "provision";
String provisionPub = "";
String provisionPrivate = "";
String clientId = "provision";
const int memoryPosition = 131072;
String serverAddress = "10.0.1.196"; // server address
int status = WL_IDLE_STATUS;
bool devMode = true;
int secSinceLastSub = 20;
WiFiClient wClient;
PubSubClient client(wClient);
LittleFS_MBED *myFS;
char credentialsFile[] = MBED_LITTLEFS_FILE_PREFIX "/cred.txt";
//-------------------------------------------------------------------------------------------------------
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
//-------------------------------------------------------------------------------------------------------
void(* resetFunc) (void) = 0;
//-------------------------------------------------------------------------------------------------------
void setup() {
//Initialize serial and wait for port to open:
Serial.begin(9600);
Serial1.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial1.write('c');
Serial1.write('\n');
myFS = new LittleFS_MBED();
if (!myFS->init())
{
Serial.println("LITTLEFS Mount Failed");
return;
}
//Scheduler.startLoop(httpSocketLoop);
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
Serial.println("Communication with WiFi module failed!");
// don't continue
while (true);
}
String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
Serial.println("Please upgrade the firmware");
}
checkOrAskForCredentials();
// attempt to connect to WiFi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid.c_str());
// Connect to WPA/WPA2 network. Change this line if using open or WEP network:
status = WiFi.begin(ssid.c_str(), psw.c_str());
// wait 10 seconds for connection:
delay(15000);
}
Serial.println("Connected to wifi");
client.setServer(serverAddress.c_str(), 8080);
client.setCallback(callback);
provisionDevice();
// you're connected now, so print out the status:
}
//-------------------------------------------------------------------------------------------------------
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect(clientId.c_str(), key.c_str(), NULL)) {
Serial.println("connected");
// Once connected, publish an announcement...
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
//-------------------------------------------------------------------------------------------------------
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
if (Serial.available()) {
if (Serial.readStringUntil('\n') == "reset") {
deleteFile(credentialsFile);
resetFunc();
}
}
String response = Serial1.readStringUntil('\n');
if (response.startsWith("Acc")) {
char acc[7], eventAcc[7], totalAcc[7], rInt[7], unit[4];
sscanf (response.c_str(), "%*s %s %[^,] , %*s %s %*s %*s %s %*s %*s %s", &acc, &unit, &eventAcc, &totalAcc, &rInt);
String postData = "{\"AccumulationDelta\":" + String(acc) + ",\"EventAccumulation\":" + String(eventAcc) + ",\"RainIntensity\":" + String(rInt) + ",\"TotalAccumulation\":" + String(totalAcc) + "}";
publish("/api/v1/" + key + "/telemetry", postData);
} else if (response.startsWith("FRD")) {
char frda[5], frdb[5];
sscanf (response.c_str(), "%*s %s %s", &frda, &frdb);
Serial.println("p");
String postData = "{\"FRDA\":" + String(frda) + ",\"FRDB\":" + String(frdb) + "}";
publish("/api/v1/" + key + "/telemetry", postData);
}
}
//-------------------------------------------------------------------------------------------------------
String publish(String url, String postData) {
client.publish(url.c_str(), postData.c_str());
// read the status code and body of the response
return "";
}
//-------------------------------------------------------------------------------------------------------
void provisionDevice() {
String response = publish("/api/v1/provision/", "{ \"provisionDeviceKey\": \"" + provisionPub + "\", \"provisionDeviceSecret\": \"" + provisionPrivate + "\"}");
key = response.substring(21, 41); //todo fix - Json library would be preferred then this hack but its way to large to fit in the UNO
}
//-------------------------------------------------------------------------------------------------------
void checkOrAskForCredentials() {
readFile(credentialsFile);
}
//-------------------------------------------------------------------------------------------------------
void readFile(const char * path)
{
Serial.print("Reading file: "); Serial.print(path);
FILE *file = fopen(path, "r");
if (file) {
Serial.println(" => Open OK");
char c;
String buffer = "";
uint32_t numRead = 1;
while (numRead) {
numRead = fread((uint8_t *) &c, sizeof(c), 1, file);
if (numRead) {
buffer += c;
}
}
StringSplitter *splitter = new StringSplitter(buffer, ',', 5);
ssid = String(splitter->getItemAtIndex(0));
psw = String(splitter->getItemAtIndex(1));
provisionPub = String(splitter->getItemAtIndex(2));
provisionPrivate = String(splitter->getItemAtIndex(3));
fclose(file);
}
else {
Serial.println(" => Input Credentials");
Serial.println("Wifi SSID: ");
while (Serial.available() == 0) {
}
String content;
ssid = Serial.readStringUntil('\n');
content = ssid;
content += ",";
Serial.println("Wifi Password: ");
while (Serial.available() == 0) {
}
psw = Serial.readStringUntil('\n');
content += psw;
content += ",";
Serial.println("Provision Public Key: ");
while (Serial.available() == 0) {
}
provisionPub = Serial.readStringUntil('\n');
content += provisionPub;
content += ",";
Serial.println("Provision Private Key: ");
while (Serial.available() == 0) {
}
provisionPrivate = Serial.readStringUntil('\n');
content += provisionPrivate;
writeFile(path, content.c_str(), content.length());
return;
}
}
//-------------------------------------------------------------------------------------------------------
void writeFile(const char * path, const char * message, size_t messageSize)
{
Serial.print("Writing file: "); Serial.print(path);
FILE *file = fopen(path, "w");
if (fwrite((uint8_t *) message, 1, messageSize, file)) {
Serial.println("* Writing OK");
}
else {
Serial.println("* Writing failed");
}
fclose(file);
}
//-------------------------------------------------------------------------------------------------------
void deleteFile(const char * path)
{
Serial.print("Deleting file: "); Serial.print(path);
if (remove(path) == 0)
{
Serial.println(" => OK");
}
else
{
Serial.println(" => Failed");
return;
}
}
Conclusion
When a sensor sends telemetry it should immediately show up in thingsboard and able to download.