Skip to content

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 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.");
}
}
}
}

When the .NET runtime evaluates the code block above, the structural handshake maps perfectly to the C++ firmware memory array:

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.

  • The request leg: SyMqttBox automatically generates a standardized 32-character GUID MessageId and 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/conveyor1 using a predictable fixed-size buffer. The awaiting .NET gateway matches the returned 32-character correlation token, resolves the internal TaskCompletionSource, and passes control back to your synchronous execution thread.