#include "Arduino.h"
#include "ArduinoUniqueID.h"
#include "Adler16.h"
#define SERIAL_PORT_SPEED 2000000
#define CompileDate __DATE__
#define CompileTime __TIME__
struct MQTTTopicMessageStuct
{
const PROGMEM String topic; // Full MQTT topic (this is used to calculate the Adler16 topic ID) [remove once adler16 is pre-calculated]
String adler_topic; // Adler16 MQTT topic ID as string
const bool direction; // input from serial = 0, output to serial = 1
String message; // Current message
String last_message; // Last message sent
const PROGMEM String default_message; // Default message (used to initialize the message at startup and set home positions on shutdown etc)
const uint16_t period_ms; // Period to re-transmit the message after (maximum period is 65,535 ~ 65 seconds)
long next_sent_ms; // Next sent time in milliseconds
};
const String device_path = "Adeept"; // root for DUT in MQTT messages)
const String device_idn = "Adeept Hat"; // IDN response
const String software_version = String(CompileDate) + " " + String(CompileTime); // Software version string
// topic, adler_topic, direction, message, last_message, default_message, period_ms, last_sent_ms, description (comment),
MQTTTopicMessageStuct MQTTTopicMessageData[] =
{
{"board/idn", "", 1, "0", "0", device_idn, 60000, 0}, // Board ID
{"board/sn", "", 1, "0", "0", "0", 60000, 0}, // Board Serial Number
{"board/sw", "", 1, "0", "0", software_version, 60000, 0}, // Software Version
{"board/heartbeat", "", 1, "0", "0", "0", 1000, 0}, // an Incramental number between 0-255
{"input/analogue/battery/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 0: Battery Level
{"input/analogue/light_tracker/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 1: Light Tracker
{"input/analogue/2/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 2: un-used
{"input/analogue/3/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 3: un-used
{"input/analogue/4/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 4: un-used
{"input/analogue/5/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 5: un-used
{"input/analogue/6/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 6: un-used
{"input/analogue/7/raw", "", 1, "0", "0", "0", 1000, 0}, // Analog channel 7: un-used
};
const char delimiter = ':'; // use '' NOT ""
uint8_t heartbeat;
uint16_t topicSize;
Adler16 adler16;
void setup() {
Serial.begin(SERIAL_PORT_SPEED);
while (!Serial);
delay(1000);
topicSize = sizeof(MQTTTopicMessageData) / sizeof(MQTTTopicMessageData[0]); // get the size of the MQTTTopicMessageData array (number of rows/topics)
for (int i = 0; i < topicSize; i++) {
MQTTTopicMessageStuct& current_topic = MQTTTopicMessageData[i]; // The & is important to create a reference to the array element rather than a copy! -- https://www.reddit.com/r/arduino/comments/1qno8ko/comment/o1vlejc/
// Calculate the adler16 topic ID
String device_path_lower = device_path;
device_path_lower.toLowerCase();
String full_topic = device_path_lower + "/" + current_topic.topic;
adler16.begin();
for (int j = 0; full_topic[j] != 0; j++) {
adler16.add(full_topic[j]);
}
String adler16_topic = String(adler16.getAdler(), HEX);
adler16_topic.toUpperCase();
current_topic.adler_topic = adler16_topic;
current_topic.message = current_topic.default_message; // Initialize the message to the default message
if (current_topic.topic == "board/serial_number") // Generate the serial number adler16 value
{
String Serial_Number;
for (size_t i = 0; i < UniqueIDsize; i++)
{
Serial_Number += String(UniqueID[i], HEX);
}
Serial_Number.toUpperCase();
current_topic.message = Serial_Number;
adler16.begin();
for (int j = 0; Serial_Number[j] != 0; j++)
{
adler16.add(Serial_Number[j]);
}
String adler16_serial = String(adler16.getAdler(), HEX);
adler16_serial.toUpperCase();
current_topic.message = adler16_serial;
}
// At the end of Setup topic5 should look like:
// "adeept/input/analogue/light_tracker/raw -> C5A4:0
Serial.print(full_topic);
Serial.print(" -> ");
Serial.println(current_topic.adler_topic + delimiter + current_topic.message);
}
delay(5000); // this is just a temp thing for debug
}
void loop()
{
// these don't need global scope, so move them in here, I don't know if it helps with memory managment; but it can't hurt?
String serialResponse;
String MQTTtopic;
String MQTTmessage;
int delimiterLocation;
if (Serial.available() > 0)
{
serialResponse = Serial.readStringUntil('\n'); // read the incoming data as string until newline character
delimiterLocation = serialResponse.indexOf(delimiter); // find the location of the delimiter character
if (delimiterLocation != -1) // make sure the delimiter was found
{
MQTTtopic = serialResponse.substring(0, delimiterLocation); // extract the topic
MQTTmessage = serialResponse.substring(delimiterLocation + 1); // extract the message
}
for (int i = 0; i < topicSize; i++) // loop the array of topics
{
MQTTTopicMessageStuct& current_topic = MQTTTopicMessageData[i];
if (current_topic.direction == 0 && MQTTtopic == current_topic.adler_topic) // is it an input topic from serial and a topic we care about?
{
current_topic.message = MQTTmessage; // update the message
}
}
}
for (int i = 0; i < topicSize; i++)
{
MQTTTopicMessageStuct& current_topic = MQTTTopicMessageData[i]; // The & is important to create a reference to the array element rather than a copy
if (current_topic.direction == 1 && (current_topic.last_message != current_topic.message || millis() >= current_topic.next_sent_ms)) // is it an output topic to serial AND has it changed or time to resend it?
{
current_topic.last_message = current_topic.message; // update the last message sent
Serial.println(current_topic.adler_topic + delimiter + current_topic.message); // send the topic and message to serial
current_topic.next_sent_ms = millis() + current_topic.period_ms + random(100); // Reset the next sent time with a small random delay to avoid collisions
if (current_topic.topic == "board/heartbeat") // heartbeat is a special case
{
heartbeat ++; // increment the heartbeat, hearbeat is an unsigned 8 bit int; so will overflow at 255 wrapping back to 0
current_topic.message = current_topic.last_message = heartbeat; // update the heartbeat message
}
}
}
}