Cross-platform IoT (.NET Companion)
To complete the cross-platform communication loop, this companion guide demonstrates how to configure a .NET application using SyMqtt to send synchronous commands to the ESP32 conveyor controller we implemented in the previous section.
By utilizing a single SyMqttChannel, the .NET gateway can interact with specific physical hardware instances on the factory floor simply by overriding the message destination at runtime using the TargetTopicSuffix property.
The .NET gateway implementation
Section titled “The .NET gateway implementation”The following example initializes the SyMqttBox engine, maps the base channel paths to match the hardware’s subscription profile, and executes sequential hardware commands inside a safe execution tree.
using SerbanLib;using SyMqtt;using SyMqtt.Tools;using System;using System.Threading.Tasks;
namespace SyMqtt.IoTGateway{ class Program { static async Task Main(string[] args) { Console.WriteLine("Starting SyMqtt Industrial IoT Gateway...");
// 1. Setup the centralized communication box var mqttBox = new SyMqttBox { TopicsPrefix = "smartcontrol", AutoReconnect = true };
// 2. Define the topology layout to match the device's channel structure // Base Request (cmd): smartcontrol/factory/floor1/conveyor/cmd/[Suffix] // Base Response (res): smartcontrol/factory/floor1/conveyor/res/[Suffix] mqttBox.AddChannel(new SyMqttChannel( mqttBox, channelId: "conveyor_channel", senderTopic: "factory/floor1/conveyor/cmd", receiverTopic: "factory/floor1/conveyor/res" ));
var connectionResult = await mqttBox.Initialize(new SyMqttClientParameters { ClientId = "SyMqtt_Core_Gateway", TcpServer = "mybroker.net", TcpPort = 1883, CleanSession = true }) .OpenConnectionAsync();
if (!connectionResult.Success) { Console.WriteLine($"Failed to initialize the MQTT communication engine: {connectionResult.ErrorDescription}"); return; }
// 3. Target a specific physical device instance via its hardware suffix var targetDeviceInfo = new SyMqttSendMessageOptions { TargetTopicSuffix = "conveyor1" };
// 4. Execute the synchronous Request-Response control loop try { Console.WriteLine("\n[1] Sending Command: START_CONVEYOR...");
// Wrap payload into a SerialMessage structure var startMessage = SerialMessage.NewMessage("START_CONVEYOR");
// FetchAsync suspends the thread, handles timeouts strictly, and awaits the C++ response GenericResult<SerialMessage> startResult = await mqttBox.FetchAsync( "conveyor_channel", startMessage, 4000, targetDeviceInfo );
if (startResult.Success) { SerialMessage response = startResult.Value; Console.WriteLine($"-> Success! Hardware Code: {response.MessageSuccessCode}"); Console.WriteLine($"-> Device Telemetry: {response.MessageDetails}"); } else { Console.WriteLine($"-> Command failed: {startResult.ErrorDescription}"); }
// Wait a few seconds before firing the next hardware state mutation await Task.Delay(3000);
Console.WriteLine("\n[2] Sending Command: STOP_CONVEYOR..."); var stopMessage = SerialMessage.NewMessage("STOP_CONVEYOR");
GenericResult<SerialMessage> stopResult = await mqttBox.FetchAsync( "conveyor_channel", stopMessage, 4000, targetDeviceInfo );
if (stopResult.Success) { Console.WriteLine($"-> Success! Hardware Code: {stopResult.Value.MessageSuccessCode}"); Console.WriteLine($"-> Device Telemetry: {stopResult.Value.MessageDetails}"); } else { Console.WriteLine($"-> Command failed: {stopResult.ErrorDescription}"); } } catch (SyMqttException ex) { Console.WriteLine($"Critical library execution failure ({ex.Category}): {ex.Message}"); } finally { // Cleanly close infrastructure before exiting scope await mqttBox.CloseConnectionAsync(); Console.WriteLine("\nGateway connection disposed safely."); } } }}Wire-format alignment analysis
Section titled “Wire-format alignment analysis”When the .NET runtime evaluates the code block above, the structural handshake maps perfectly to the C++ firmware memory array:
1. Topic reconstruction mechanics
Section titled “1. Topic reconstruction mechanics”The base layout requested by the C# channel is compiled dynamically during the .FetchAsync invocation into a fully qualified MQTT routing string:
- Formula:
[TopicsPrefix]/[SenderTopic]/[TargetTopicSuffix] - Result:
smartcontrol/factory/floor1/conveyor/cmd/conveyor1
This matches exactly the const char* subscribe_topic pre-allocated inside the Arduino flash chip.
2. Envelope processing flow
Section titled “2. Envelope processing flow”- The request leg:
SyMqttBoxautomatically generates a standardized 32-character GUIDMessageIdand prepends it to the raw string (e.g.,e02b803f2a584ab8a7c29370bb931fa0,START_CONVEYOR) before transmitting the payload. - The C++ processing: The embedded device splits this incoming payload at runtime in-place using pointer mutations. It extracts the structural ID without executing any heap allocation, isolating the microcontroller from memory fragmentation.
- The response leg: The C++ firmware writes the evaluation results directly back into
smartcontrol/factory/floor1/conveyor/res/conveyor1using a predictable fixed-size buffer. The awaiting .NET gateway matches the returned 32-character correlation token, resolves the internalTaskCompletionSource, and passes control back to your synchronous execution thread.