Handling hardware faults & timeouts
In an Industrial IoT ecosystem, a clear distinction is typically made between a network infrastructure failure and a physical hardware operation failure.
Because microcontrollers running C++ operate in a procedural environment where exceptions are typically disabled for performance reasons, they rely entirely on numeric error codes. Conversely, .NET applications often leverage object-oriented exception handling or structured result/functional patterns.
This guide suggests a few clean approaches for bridging these two design philosophies using the SuccessCode and MessageDetails properties of SyMqtt. While the examples below demonstrate a clean way to handle faults, the SyMqtt library is unopinionated, and you have complete freedom to structure your error pipeline differently.
Network timeouts vs. hardware failures
Section titled “Network timeouts vs. hardware failures”When invoking .FetchAsync() from your .NET gateway, your architecture will generally encounter two distinct failure categories:
- Network Timeout (System Fault): The communication channel was interrupted, the device lost power, or the broker dropped the connection. The .NET thread hits the specified timeout threshold (e.g.,
4000ms) and returns a failedGenericResultwithout ever receiving a response string. - Hardware Failure (Operational Fault): The network path is perfectly healthy, and the ESP32/Arduino received the command instantly. However, a physical sensor or actuator reported an error (e.g., a motor jammed). The microcontroller responds immediately—well within the timeout window—returning a non-zero
SuccessCodealong with diagnostic details.
Mapping C++ error codes to .NET exceptions
Section titled “Mapping C++ error codes to .NET exceptions”To keep your .NET business logic clean, avoid littering your services with manual if statements checking raw integers. Instead, encapsulate the error translation layer using a Factory pattern or a concise C# extension method.
Here is a production-grade pattern to map SyMqtt’s hardware error codes into strongly-typed .NET domain exceptions:
using System;using SyMqtt;
namespace SyMqtt.IoTGateway.Extensions{ public class IndustrialHardwareException : Exception { public int ErrorCode { get; } public IndustrialHardwareException(int errorCode, string message) : base(message) => ErrorCode = errorCode; }
public class PressureTooHighException : IndustrialHardwareException { public PressureTooHighException(string details) : base(104, $"Critical Pressure Alert: {details}") { } }
public class EmergencyStopException : IndustrialHardwareException { public EmergencyStopException(string details) : base(911, $"E-STOP Engaged: {details}") { } }
public static class SyMqttErrorExtensions { /// <summary> /// Validates the SyMqtt response and throws a strongly-typed domain exception if the hardware reported a failure. /// </summary> public static void EnsureSuccess(this SerialMessage message) { if (message.MessageSuccessCode == 0) return;
throw message.MessageSuccessCode switch { 104 => new PressureTooHighException(message.MessageDetails), 911 => new EmergencyStopException(message.MessageDetails),
// Fallback for unmapped or generic hardware errors _ => new IndustrialHardwareException(message.MessageSuccessCode, $"Generic hardware fault: {message.MessageDetails}") }; } }}Consuming the error pipeline in the control loop
Section titled “Consuming the error pipeline in the control loop”By implementing the EnsureSuccess extension pattern, your main application loop can cleanly separate network-level routing issues from physical equipment faults using standard .NET try-catch blocks.
// Inside your synchronous control loop execution branch:GenericResult<SerialMessage> result = await mqttBox.FetchAsync("conveyor_channel", startMessage, 4000, targetDevice);
if (!result.Success){ // Category 1: Network Timeout or Broker Disconnection Console.WriteLine($"Network Fault: Could not reach the device. {result.ErrorDescription}");}else{ try { // Category 2: Evaluate the actual hardware execution status SerialMessage response = result.Value; response.EnsureSuccess();
// If we pass this line, the physical operation succeeded (SuccessCode == 0) Console.WriteLine($"Operation succeeded: {response.MessageDetails}"); } catch (IndustrialHardwareException hwEx) { // Gracefully handle specific machine alerts reported by the firmware Console.WriteLine($"[HARDWARE ALERT #{hwEx.ErrorCode}] Execution halted: {hwEx.Message}"); }}Maximizing diagnostic details on embedded firmware
Section titled “Maximizing diagnostic details on embedded firmware”Since SyMqtt operates with lightweight, fixed-size text strings, you can optimize your microcontroller’s fixed memory buffer (e.g., MQTT_MAX_RESPONSE_SIZE = 128) by formatting structured diagnostic tokens into the MessageDetails segment whenever a fault occurs.
Instead of returning a vague "ERROR" string, your C++ firmware should pack high-value machine states using a simple delimiter (like a pipe | symbol) that can be easily parsed by the .NET gateway:
Raw wire examples
Section titled “Raw wire examples”Example 1: Safe Operational Success
4a9c8d20e1f34b6bb2a1d8c7e6f5a4b3,0,PUMP_3_RUNNING_1200_RPMExample 2: Structured Hardware Fault
4a9c8d20e1f34b6bb2a1d8c7e6f5a4b3,104,ERR_OVERHEAT|TEMP:87C|CURRENT:14ABy adhering to this structure, the .NET application can read response.MessageDetails, split the string by |, and pipe the telemetric context straight into your centralized monitoring or telemetry logging infrastructure.