Skip to main content

MQTT 3.1.1

Zeus.Protocols.Mqtt targets message-oriented devices, edge gateways, and industrial IoT brokers. It layers MQTT sessions on top of Zeus channels and supports publish/subscribe, QoS, retained messages, wills, keep-alive, reconnects, and direct topic-to-point-table mapping.

Install

dotnet add package Zeus.Communications
dotnet add package Zeus.Protocols.Mqtt

Hardware-Free Testing

Use the virtual broker to verify connection, subscription, publishing, and point write-back locally:

using Zeus;

var memory = new MqttBrokerMemory();
memory.SetText("factory/temperature", "25.3");
memory.SetText("factory/running", "true");

await using var app = ZeusHost.Create(builder =>
{
builder.AddAcquisition(TimeSpan.FromMilliseconds(500));
builder.AddVirtualChannel("mqtt-link", new MqttBrokerResponder(memory));
builder.AddMqtt(
"gateway",
"mqtt-link",
new MqttOptions { ClientId = "zeus-mqtt-demo" },
points: map => map
.Double("temperature", "factory/temperature")
.Boolean("running", "factory/running")
.Double("setpoint", "factory/setpoint")
.Writable("setpoint"));
});

await app.StartAsync();
await Task.Delay(600);
Console.WriteLine(app.Points.Get<double>("temperature"));

await app.Points.WriteAsync("setpoint", 18.6);

MqttBrokerMemory stores retained messages for the virtual broker. Publishing an empty payload with retain: true deletes the retained message for that topic.

Connect to a Real Broker

MQTT commonly uses TCP port 1883. Replace only the channel; the device and point map can remain the same:

await using var app = ZeusHost.Create(builder =>
{
builder.AddTcpClient("mqtt-link", "192.168.1.20", 1883);
builder.AddMqtt(
"gateway",
"mqtt-link",
new MqttOptions
{
ClientId = "zeus-gateway-01",
Username = "zeus",
Password = "secret",
KeepAliveSeconds = 60,
AutomaticKeepAlive = true,
AutomaticReconnect = true
},
points: map => map
.Double("temperature", "factory/temperature")
.Writable("temperature"));
});

await app.StartAsync();

QoS, Retained Messages, and Wills

Client publishing defaults to QoS 0. Select QoS 1 or QoS 2 explicitly when needed:

var gateway = app.Devices.Get<MqttDevice>("gateway");

await gateway.PublishTextAsync(
"factory/status",
"online",
MqttQualityOfService.AtLeastOnce,
retain: true);

await gateway.Client.SubscribeAsync(
"factory/#",
MqttQualityOfService.ExactlyOnce);

var message = await gateway.Client.WaitForMessageAsync("factory/status");

QoS 1 uses PUBACK; QoS 2 uses PUBREC / PUBREL / PUBCOMP. Subscription filters support + and #; publish topics and point topics cannot contain wildcards.

Configure a will with both a topic and a payload:

var options = new MqttOptions
{
ClientId = "zeus-gateway-01",
WillTopic = "factory/status",
WillPayload = "offline"u8.ToArray(),
WillQualityOfService = MqttQualityOfService.AtLeastOnce,
WillRetain = true
};

Point Mapping

MqttPointMap supports common message payload types:

APIPayloadResult
TextUTF-8 textstring
Booleantrue / false or 1 / 0bool
Int32 / Int64Decimal textInteger
DoubleInvariant-culture number textdouble
BytesRaw bytesbyte[]

Numeric points can define alarm limits. Writable points publish using the point's QoS and retain settings:

points: map => map
.Double("temperature", "factory/temperature",
new PointAlarmLimits(0, 80))
.WithQualityOfService("temperature", MqttQualityOfService.AtLeastOnce)
.Retained("temperature")
.Writable("temperature")

JSON Configuration

{
"acquisition": { "intervalMilliseconds": 500, "pollImmediately": true },
"channels": [
{ "name": "mqtt-link", "type": "virtual", "responder": "mqtt" }
],
"devices": [
{
"name": "gateway",
"channel": "mqtt-link",
"type": "mqtt",
"mqttClientId": "zeus-json-gateway",
"mqttKeepAliveSeconds": 60,
"mqttAutomaticKeepAlive": true,
"mqttAutomaticReconnect": true,
"points": [
{
"name": "temperature",
"topic": "factory/temperature",
"dataType": "double",
"mqttQos": "1",
"mqttRetain": true,
"lowAlarmLimit": 0,
"highAlarmLimit": 80
},
{
"name": "running",
"topic": "factory/running",
"dataType": "boolean"
},
{
"name": "setpoint",
"topic": "factory/setpoint",
"dataType": "double",
"writable": true
}
]
}
]
}

The device type can be mqtt, mqtt311, or mqtt-3-1-1. Use responder: "mqtt" for a virtual channel. For a real broker, change the channel to tcp and provide host and port.

Troubleshooting

SymptomFirst check
CONNECT timeoutTCP address/port, firewall, and whether the broker is listening
CONNACK rejectedClientId, credentials, and MQTT 3.1.1 support on the broker
No messages receivedTopic filter, broker ACL, subscription QoS, and $ system-topic rules
Point has no valuePayload format for the point's dataType and whether the first poll completed
Reconnect does not restore dataAutomaticReconnect, channel returning to Open, and reconnect backoff settings

For field diagnostics, enable channel tracing and inspect CONNECT, SUBSCRIBE, PUBLISH, and acknowledgment packets.

Next: JSON Configuration, Communication Tracing, or Virtual Channels.