Random wanderings through Microsoft Azure esp. PaaS plumbing, the IoT bits, AI on Micro controllers, AI on Edge Devices, .NET nanoFramework, .NET Core on *nix and ML.NET+ONNX
The first prototype of “transforming” telemetry data used C# code complied with the application. The HiveMQClient based application subscribes to topics (devices publishing environmental measurements) and then republishes them to multiple topics.
The MQTTX application subscribed to topics that devices (XiaoTandHandCO2A, XiaoTandHandCO2B etc.) and the simulated bridge (DESKTOP-EN0QGL0) published.
The second prototype “transforms” the telemetry message payloads with C# code that is compiled (with CSScript) as the application starts. The application subscribes to the topics which devices publish (environmental measurements), transforms the payloads, and then republishes the transformed messages to “bridge” topics
private static void OnMessageReceived(object? sender, HiveMQtt.Client.Events.OnMessageReceivedEventArgs e)
{
HiveMQClient client = (HiveMQClient)sender!;
Console.WriteLine($"{DateTime.UtcNow:yy-MM-dd HH:mm:ss:fff} HiveMQ.receive start");
Console.WriteLine($" Topic:{e.PublishMessage.Topic} QoS:{e.PublishMessage.QoS} Payload:{e.PublishMessage.PayloadAsString}");
Console.WriteLine($"{DateTime.UtcNow:yy-MM-dd HH:mm:ss:fff} HiveMQ.Publish start");
foreach (string topic in _applicationSettings.PublishTopics.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
e.PublishMessage.Topic = string.Format(topic, _applicationSettings.ClientId);
foreach (MQTT5PublishMessage message in _evaluator.Transform(e.PublishMessage))
{
Console.WriteLine($"{DateTime.UtcNow:yy-MM-dd HH:mm:ss:fff} Topic:{e.PublishMessage.Topic} HiveMQ Publish start ");
var resultPublish = client.PublishAsync(message).GetAwaiter().GetResult();
Console.WriteLine($"{DateTime.UtcNow:yy-MM-dd HH:mm:ss:fff} Published:{resultPublish.QoS1ReasonCode} {resultPublish.QoS2ReasonCode}");
}
}
Console.WriteLine($"{DateTime.UtcNow:yy-MM-dd HH:mm:ss:fff} HiveMQ.Receive finish");
}
// This code is compiled as the application starts up, it implements the IMessageTransformer interface
const string sampleTransformCode = @"
using System.Text;
using HiveMQtt.MQTT5.Types;
public class messageTransformer : devMobile.IoT.MqttTransformer.CSScriptLoopback.IMessageTransformer
{
public MQTT5PublishMessage[] Transform(MQTT5PublishMessage message)
{
// Example: echo the payload to a new topic
var payload = Encoding.UTF8.GetString(message.Payload);
// Simple transformations: convert to uppercase or lowercase
var toLower = new MQTT5PublishMessage
{
Topic = message.Topic,
Payload = Encoding.UTF8.GetBytes(payload.ToLower()),
QoS = QualityOfService.AtLeastOnceDelivery
};
var toUpper = new MQTT5PublishMessage
{
Topic = message.Topic,
Payload = Encoding.UTF8.GetBytes(payload.ToUpper()),
QoS = QualityOfService.AtLeastOnceDelivery
};
return new[] { toLower, toUpper };
}
}";
The sample C# code implements the IMessageTransformer interface and republishes lower and upper case versions of the message.
public interface IMessageTransformer
{
public MQTT5PublishMessage[] Transform(MQTT5PublishMessage mqttPublishMessage);
}
...
_evaluator = CSScript.Evaluator.LoadCode<IMessageTransformer>(sampleTransformCode);
The SwarmSpace, and Myriota gateways both use an interface based approach to process uplink and downlink messages. Future versions will include support for isolating processing so that a rogue script can’t crash the application or reference unapproved assemblies
This sensor has an operating voltage of 3.6-30V/DC so it can be powered by the 5V output of a RS485 Breakout Board for Seeed Studio XIAO (SKU 113991354). The red wire is for powering the breakout and device with a 12V power supply so was tied back so it didn’t touch any of the other electronics.
HardwareSerial RS485Serial(1);
ModbusMaster node;
// -----------------------------
// RS485 Pin Assignments (Corrected)
// -----------------------------
const int RS485_RX = 6; // UART1 RX
const int RS485_TX = 5; // UART1 TX
const int RS485_EN = D2;
// Sensor/Modbus parameters (from datasheet)
#define MODBUS_SLAVE_ID 0x2A
#define REG_TEMPERATURE 0x0000
#define REG_HUMIDITY 0x0001
#define REG_DEWPOINT 0x0002
// Forward declarations for ModbusMaster callbacks
void preTransmission();
void postTransmission();
void setup() {
Serial.begin(9600);
delay(5000);
Serial.println("ModbusMaster: Seeed SKU101990882 starting");
// Wait for the hardware serial to be ready
while (!Serial)
;
Serial.println("Serial done");
pinMode(RS485_EN, OUTPUT);
digitalWrite(RS485_EN, LOW); // Start in RX mode
// Datasheet: 9600 baud, 8N1
RS485Serial.begin(9600, SERIAL_8N1, RS485_RX, RS485_TX);
while (!RS485Serial)
;
Serial.println("RS485 done");
// Tie ModbusMaster to the UART we just configured
node.begin(MODBUS_SLAVE_ID, RS485Serial);
// Register callbacks for half-duplex direction control
node.preTransmission(preTransmission);
node.postTransmission(postTransmission);
}
...
void loop() {
float temperature;
uint16_t humidity;
uint16_t dewPoint;
uint8_t result = node.readInputRegisters(0x0000, 3);
if (result == node.ku8MBSuccess) {
// --- Read Temperature ---
uint16_t rawTemperature = node.getResponseBuffer(REG_TEMPERATURE);
temperature = (int16_t)rawTemperature / 100.0;
// --- Read Humidity ---
humidity = node.getResponseBuffer(REG_HUMIDITY);
humidity = humidity / 100;
// --- Read DewPoint ---
dewPoint = node.getResponseBuffer(REG_DEWPOINT);
dewPoint = dewPoint / 100;
Serial.printf("Temperature: %.1f°C Humidity: %u%%RH Dewpoint: %u°C\n", temperature, humidity, dewPoint);
} else {
Serial.printf("Modbus error: %d\n", result);
}
delay(60000);
}
The Arduino ModbusMaster based application worked first time I forgot to scale the dewpoint.
After hours of fail trying to get nanoMQ TCP bridge running on my Windows11 development system it was time to walk away. I ran nanoMQ with different log levels but “nng_dialer_create failed 9” was the initial error message displayed.
The setup looked good…
bridges.mqtt.MyBridgeDeviceID {
## Azure Event Grid MQTT broker endpoint
server = "tls+mqtt-tcp://xxxx.newzealandnorth-1.ts.eventgrid.azure.net:8883"
proto_ver = 5
clientid = "MyBridgeDeviceID"
username = "MyBridgeDeviceID"
clean_start = true
keepalive = "60s"
## TLS client certificate authentication
ssl = {
# key_password = ""
keyfile = "certificates/MyBridgeDeviceID.key"
certfile = "certificates/MyBridgeDeviceID.crt"
cacertfile = "certificates/xxxx.crt"
}
## ------------------------------------------------------------
## Topic forwarding (NanoMQ → Azure Event Grid)
## ------------------------------------------------------------
## These are the topics your device publishes locally.
## They will be forwarded upstream to Event Grid.
##
forwards = [xxxx]
## ------------------------------------------------------------
## Topic subscription (Azure Event Grid → NanoMQ)
## ------------------------------------------------------------
## This is the topic your device subscribes to from Event Grid.
subscription = [xxxx]
}
Most of my applications have focused on telemetry but I had been thinking about local control for solutions that have to run disconnected. In “real-world” deployments connectivity to Azure EventGrid MQTT Broker isn’t 100% reliable (also delay and jitter issues) which are an issue for control at the edge.
Over Christmas I read an article about the Internet of Vehicles(IoV) which got me thinking about “edge brokers”. In “real-world” deployments connectivity to Azure EventGrid MQTT Broker would not 100% reliable so I have been looking at lightweight edge brokers.
NanoMQ is a Linux Foundation Edge project. It is ultra-lightweight with a footprint of less than 200Kb when deployed with the minimum feature set, is cross platform (POSIX), has MQTT V5 support, bridging, and message persistence. The support for secure deployment is very limited.
Eclipse Mosquitto is another open-source message broker that implements the MQTT versions 5.0, 3.1.1 and 3.1. It is lightweight and is suitable for use on all devices from low power single board computers to full servers.
HiveMQ Edge is an MQTT V5, V3.1.1 and V3.1 compliant, with protocol adapters which connect various industrial communication protocols and integrate several database engines, bidirectional connections to enterprise brokers. For disconnected scenarios messages are stored on disk and published once online. There are opensource(community support) and commercial versions (additional features and support). The open-source version does not include offline persistence.
The first step was to bend the crimp connector locks back using a very small screwdriver.
Then split the heat-shrink and cable outer, so the individual cables were longer.
The crimp connector at the of each wire had to be “modified” by trimming them with a pair of wire cutters (just in front of the crimped section) so that they could be inserted into the breakout board connectors.