Cross-platform IoT (Arduino C++)
One of the primary structural advantages of SyMqtt is its lightweight text layout. Because it avoids complex envelope structures like JSON or XML at the transport layer, resource-constrained edge devices (such as Arduino, ESP8266, or ESP32 microcontrollers) can parse requests and compile responses with minimal CPU overhead and zero memory leaks.
To participate in SyMqtt’s synchronous Request-Response pattern, an embedded device simply needs to follow two rules when a message arrives:
- Extract the incoming
MessageId(everything before the first comma). - Publish the response back containing that exact same
MessageId, followed by aSuccessCodeand the operationalDetails.
Arduino C++ - Minimal in-place implementation example
Section titled “Arduino C++ - Minimal in-place implementation example”The following standalone sketch demonstrates how to handle incoming SyMqtt channel requests using the popular PubSubClient library on an ESP32 or Arduino network shield.
#include <WiFi.h>#include <PubSubClient.h>
// Network configurationconst char* ssid = "Your_WiFi_SSID";const char* password = "Your_WiFi_Password";const char* mqtt_server = "mybroker.net";
// SyMqtt Channel configuration pathsconst char* subscribe_topic = "smartcontrol/factory/floor1/conveyor/cmd/conveyor1";const char* response_topic = "smartcontrol/factory/floor1/conveyor/res/conveyor1";
// Maximum predictable size for any outbound SyMqtt response message stringconst size_t MQTT_MAX_RESPONSE_SIZE = 128;
WiFiClient espClient;PubSubClient client(espClient);
// Callback function executed whenever an MQTT payload arrivesvoid callback(char* topic, byte* payload, unsigned int length) { // 1. Convert raw byte payload into a safe null-terminated C-string in-place char incomingBuffer[length + 1]; memcpy(incomingBuffer, payload, length); incomingBuffer[length] = '\0';
// 2. Locate the envelope delimiter (the first comma) using pointer arithmetic char* firstComma = strchr(incomingBuffer, ','); if (firstComma == NULL) { // Invalid SyMqtt wire structure; discard the message safely return; }
// 3. Extract tokens in-place by splitting the string natively (zero-allocation) *firstComma = '\0'; // Mutates the first comma to a null-terminator, splitting the string char* messageId = incomingBuffer; // This pointer now points strictly to the MessageId char* commandContents = firstComma + 1; // This pointer points to the remaining payload
// 4. Process the business logic safely int successCode = -1; const char* responseDetails = "";
if (strcmp(commandContents, "START_CONVEYOR") == 0) { // Simulate successful hardware execution successCode = 0; responseDetails = "CONVEYOR_RUNNING_NORMAL"; } else if (strcmp(commandContents, "STOP_CONVEYOR") == 0) { successCode = 0; responseDetails = "CONVEYOR_STOPPED_SAFETY"; } else { // Command not recognized by the firmware execution tree successCode = 404; responseDetails = "UNKNOWN_HARDWARE_COMMAND"; }
// 5. Construct the SyMqtt response message string safely into a fixed static buffer char responsePayload[MQTT_MAX_RESPONSE_SIZE]; snprintf(responsePayload, sizeof(responsePayload), "%s,%d,%s", messageId, successCode, responseDetails);
// 6. Return the response back to the .NET waiting thread via the response leg client.publish(response_topic, responsePayload);}
void setup_wifi() { delay(10); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); }}
void reconnect() { while (!client.connected()) { if (client.connect("ESP32_Conveyor_Client")) { client.subscribe(subscribe_topic); } else { delay(5000); } }}
void setup() { Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, 1883); client.setCallback(callback);}
void loop() { if (!client.connected()) { reconnect(); } client.loop();}Arduino C++ - Production-ready refactored architecture example
Section titled “Arduino C++ - Production-ready refactored architecture example”While the inline pointer mutation in the first example is highly efficient for single-command devices, it quickly becomes difficult to maintain and scale as your firmware evolves to handle dozens of different hardware operations.
To transition this pattern into production-grade firmware, we can refactor the logic by separating concerns. By defining dedicated data structures for incoming and outgoing payloads (InputMessage and OutputMessage) and moving the low-level string manipulation into specialized helper functions (ParseMessage and PackMessage), we achieve an architecture that is highly readable and easy to extend.
Crucially, this structured approach still completely avoids the dangerous String class. It operates strictly with stack allocation and fixed-size buffers, preserving SyMqtt’s core promises: minimal footprint, zero memory fragmentation, and zero risk of runtime leaks on long-running edge devices.
#include <WiFi.h>#include <PubSubClient.h>
// Network configurationconst char* ssid = "Your_WiFi_SSID";const char* password = "Your_WiFi_Password";const char* mqtt_server = "mybroker.net";
// SyMqtt Channel configuration pathsconst char* subscribe_topic = "smartcontrol/factory/floor1/conveyor/cmd/conveyor1";const char* response_topic = "smartcontrol/factory/floor1/conveyor/res/conveyor1";
// Maximum predictable size for any outbound SyMqtt response message stringconst size_t MQTT_MAX_RESPONSE_SIZE = 128;
WiFiClient espClient;PubSubClient client(espClient);
// --- SyMqtt Types & Helpers ---
// Zero-allocation structures holding pointers directly into the network bufferstruct InputMessage { const char* MessageId; const char* Contents;};
struct OutputMessage { const char* MessageId; int SuccessCode; const char* Details;};
// Parses the raw buffer in-place by replacing the first comma with a null-terminatorbool ParseMessage(char* incomingBuffer, InputMessage& outMsg) { char* firstComma = strchr(incomingBuffer, ','); if (firstComma == NULL) { return false; // Invalid SyMqtt wire structure }
*firstComma = '\0'; // Mutate comma to null-terminator to split strings natively outMsg.MessageId = incomingBuffer; outMsg.Contents = firstComma + 1; return true;}
// Safely compiles the fields into a standard SyMqtt response message string formatvoid PackMessage(char* outputBuffer, size_t bufferSize, const OutputMessage& msg) { snprintf(outputBuffer, bufferSize, "%s,%d,%s", msg.MessageId, msg.SuccessCode, msg.Details);}
// --- MQTT Infrastructure ---
void callback(char* topic, byte* payload, unsigned int length) { // 1. Create a safe null-terminated stack buffer for the incoming raw data char incomingBuffer[length + 1]; memcpy(incomingBuffer, payload, length); incomingBuffer[length] = '\0';
// 2. Extract into the InputMessage structure InputMessage inMsg; if (!ParseMessage(incomingBuffer, inMsg)) { return; // Discard invalid payload safely }
// 3. Prepare the OutputMessage container linked to the received MessageId OutputMessage outMsg; outMsg.MessageId = inMsg.MessageId; outMsg.SuccessCode = -1; outMsg.Details = "";
// 4. Clean execution branch based on structured fields if (strcmp(inMsg.Contents, "START_CONVEYOR") == 0) { outMsg.SuccessCode = 0; outMsg.Details = "CONVEYOR_RUNNING_NORMAL"; } else if (strcmp(inMsg.Contents, "STOP_CONVEYOR") == 0) { outMsg.SuccessCode = 0; outMsg.Details = "CONVEYOR_STOPPED_SAFETY"; } else { outMsg.SuccessCode = 404; outMsg.Details = "UNKNOWN_HARDWARE_COMMAND"; }
// 5. Serialize and transmit the SyMqtt response message string safely using a fixed-size buffer char responsePayload[MQTT_MAX_RESPONSE_SIZE]; PackMessage(responsePayload, sizeof(responsePayload), outMsg);
client.publish(response_topic, responsePayload);}
void setup_wifi() { delay(10); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); }}
void reconnect() { while (!client.connected()) { if (client.connect("ESP32_Conveyor_Client")) { client.subscribe(subscribe_topic); } else { delay(5000); } }}
void setup() { Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, 1883); client.setCallback(callback);}
void loop() { if (!client.connected()) { reconnect(); } client.loop();}Architectural checklist for IoT devices
Section titled “Architectural checklist for IoT devices”When writing custom embedded firmware (whether in C++, MicroPython, or Rust) to interface with a .NET application running SyMqtt, verify that your device logic honors the following constraints:
- Strict token preservation: The
MessageIdstring parsed from the request payload must be captured completely and mirrored back into the SyMqtt response message string without any character mutations, capitalization changes, or whitespace stripping. - Double-comma response design: Ensure your response layout formats exactly two delimiters separating the three target atoms (
[Id],[Code],[Details]). If your details string payload contains internal commas, that is entirely fine—the .NETSerialMessageengine only treats the first two commas as structural envelopes, passing the remainder cleanly into theMessageDetailsvariable. - Timeout alignment: Because the .NET thread suspends while invoking
.FetchAsync()with a specified time budget (e.g.,3000ms), your device firmware must process the incoming payload and publish its response back within that designated timeout window to prevent the .NET service from throwing a communication timeout exception. If a physical hardware operation inherently requires more time to execute, ensure that the.FetchAsynctimeout value on the .NET side is configured with an appropriately larger threshold to accommodate the latency.