MQTT is a communication protocol specially designed for sensors and telemetry. It has become a ‘‘de-facto'' standard and a lot of different IoT service providers support it as a way to ingest data from sensors. To use MQTT you need an MQTT broker. The broker is the responsible for receiving and transmitting messages. Think of it as a post office service. You send a message (payload) to a mailbox (topic) and anyone subscribed to that mailbox will receive the message. There are a few configuration options (keep-alive time, quality of service, last will and testament, retain flag…) you can read about them in the Mosquitto MAN page. Mosquitto is a popular open-source MQTT broker.
Turning your AllWize G1 into a "Wize to MQTT Bridge" is very simple. There is even a full wize2mqtt example that comes with the AllWize library but we will summarize it here.
Configuration
The configuration.h file holds the basic configuration for the bridge. The options you have to configure are:
- the WiFi settings to connect the device to your router
- the Wize radio settings, the gateway must be listening in the same channel and datarate the sensors are using to report data
- the MQTT broker settings
You can also
/*
Configuration file
*/
#pragma once
//------------------------------------------------------------------------------
// Payload data format
// You have to uncomment just one of these defines
//------------------------------------------------------------------------------
#define PAYLOAD_CSV
//#define PAYLOAD_MBUS
//#define PAYLOAD_LPP
//------------------------------------------------------------------------------
// General configuration
//------------------------------------------------------------------------------
#define DEBUG_SERIAL Serial
//------------------------------------------------------------------------------
// WiFi credentials
//------------------------------------------------------------------------------
#define WIFI_SSID "..."
#define WIFI_PASSWORD "..."
//------------------------------------------------------------------------------
// Radio module connections
//------------------------------------------------------------------------------
#define RESET_PIN 14
#define RX_PIN 5
#define TX_PIN 4
//------------------------------------------------------------------------------
// Wize configuration
//------------------------------------------------------------------------------
#define WIZE_CHANNEL CHANNEL_04
#define WIZE_POWER POWER_20dBm
#define WIZE_DATARATE DATARATE_2400bps
//------------------------------------------------------------------------------
// MQTT configuration
//------------------------------------------------------------------------------
#define MQTT_HOST "192.168.2.2"
#define MQTT_PORT 1883
#define MQTT_USER ""
#define MQTT_PASS ""
#define MQTT_QOS 2
#define MQTT_RETAIN 0
Code
The main code takes care of the WiFi and Wize connections and perform the parsing of the messages to MQTT topics. You can see the full sketch for the wize2mqtt bridge but the main part here is the mqtt parsing method:
void wizeMQTTParse(allwize_message_t message) {
char uid[10];
snprintf(
uid, sizeof(uid), "%02X%02X%02X%02X",
message.address[0], message.address[1],
message.address[2], message.address[3]
);
// RSSI
char topic[32];
snprintf(topic, sizeof(topic), "/device/%s/rssi", uid);
mqttSend(topic, String(message.rssi / -2).c_str());
#ifdef PAYLOAD_CSV
// Parse a comma-separated payload
uint8_t field = 1;
char * payload;
payload = strtok((char *) message.data, ",");
// While there is a field
while (NULL != payload) {
// Publish message
snprintf(topic, sizeof(topic), "/device/%s/field_%d", uid, field);
mqttSend(topic, payload);
// Get new token and update field counter
payload = strtok (NULL, ",");
field++;
}
#endif // PAYLOAD_CSV
#ifdef PAYLOAD_MBUS
// Parse payload
MBUSPayload payload;
DynamicJsonDocument jsonBuffer(512);
JsonArray root = jsonBuffer.createNestedArray();
payload.decode(message.data, message.len, root);
// Walk JsonArray
for (uint8_t i = 0; i<root.size(); i++) {
snprintf(topic, sizeof(topic), "/device/%s/%06X", uid, (uint32_t) root[i]["vif"]);
char value[12];
uint8_t decimals = ((int32_t) root[i]["scalar"] > 0) ? 0 : - ((int32_t) root[i]["scalar"]);
if (decimals == 0) {
snprintf(value, sizeof(value), "%u", (uint32_t) root[i]["value_scaled"]);
mqttSend(topic, value);
} else {
dtostrf(root[i]["value_scaled"], sizeof(value)-1, decimals, value);
uint8_t start = 0;
while (value[start] == ' ') start++;
mqttSend(topic, &value[start]);
}
}
#endif // PAYLOAD_MBUS
#ifdef PAYLOAD_LPP
// Parse payload
CayenneLPP payload(1);
DynamicJsonDocument jsonBuffer(512);
JsonArray root = jsonBuffer.createNestedArray();
payload.decode(message.data, message.len, root);
char value[16];
// Walk JsonArray
for (JsonObject element: root) {
JsonVariant v = element["value"];
if (v.is<JsonObject>()) {
for (JsonPair kv : v.as<JsonObject>()) {
const char * name = kv.key().c_str();
dtostrf(kv.value().as<float>(), sizeof(value)-1, 4, value);
uint8_t start = 0; while (value[start] == ' ') start++;
snprintf(topic, sizeof(topic), "/device/%s/%s", uid, name);
mqttSend(topic, &value[start]);
}
} else {
const char * name = element["name"].as<char*>();
dtostrf(element["value"].as<float>(), sizeof(value)-1, 2, value);
uint8_t start = 0; while (value[start] == ' ') start++;
snprintf(topic, sizeof(topic), "/device/%s/%s", uid, name);
mqttSend(topic, &value[start]);
}
}
#endif // PAYLOAD_LPP
}
Parsers
As you can see there are actually 3 different parsers there: CSV, Cayenne LPP and MBUS.
CSV Parser
The CSV parser expects a raw string with sensor values in a comma-separated fashion, like this: 23.5,34,1003.50
. It just parses the string and sends one MQTT message for each field plus an RSSI message, so the previous payload would translate into (where 20212223
is the sender UID):
/devices/20212223/rssi -45
/devices/20212223/field_1 23.5
/devices/20212223/field_2 34
/devices/20212223/field_3 1003.5
Cayenne LPP Parser
The Cayenne LPP Parser uses the Cayenne Low Power Payload example frame format description to decode the message. The sketch uses the ElectronicCats CayenneLPP library that supports the standard field types plus a set of new ones. You can use the very same library to encode your messages too (check the slave_allwize_k2_lpp example in the AllWize library).
The protocol is designed to encode not only the value but also the magnitude so the gateway can be agnostic to the data format it receives. An example payload is (hex-encoded): 0164000000000267010B03688E047326E905880651F70053340003E8
which might look long but it gets translated into the following messages:
/devices/20212223/rssi -42
/devices/20212223/generic 0
/devices/20212223/temperature 26.7
/devices/20212223/humidity 71
/devices/20212223/pressure 996.1
/devices/20212223/latitude 41.42
/devices/20212223/longitude 2.13
/devices/20212223/altitude 10
MBUS Payload Parser
The MBUS Payload Parser uses the MBUS application payload protocol example to decode the message. The sketch uses the AllWize MBUSPayload library that supports a good subset of the field types defined in the standard. You can use the very same library to encode your messages too (check the slave_allwize_k2_mbus example in the AllWize library).
The protocol is designed to encode not only the value but also the magnitude and the precission of the representation so the gateway can be agnostic to the data format it receives. An example payload is (hex-encoded): 01FD08670166E1022BB404011339
which gets translated into the following messages:
/devices/20212223/rssi -43
/devices/20212223/00FD08 103 // access number
/devices/20212223/000066 22.5 // external temperature (C)
/devices/20212223/00002B 1204 // power (W)
/devices/20212223/000013 0.057 // volume (m3)
Encoding comparison
Each of the previous encoding formats have its strong and weak points. This table might help you choose (of course you can always use a different one or a custom binary format to suit your needs, whatever your choice just remember to use the same format in the encoding and decoding :).
In the examples below we are pretending to have deployed a set of home comfort and energy consumption sensors. The data reporting at a given time is: temperature 22.5C, humidity 57% and power 1204W.
Format | Binary Payload | Size | Comments |
CSV | 32322E352 C3 5372 C3 1323034 | 12 | * Only values, the receiver has to know what they mean! |
Cayenne LPP | 016700E1 028004B4 036872 | 11 | * Field types are reported along with values. * Field size is fixed and depends on the field. * Each field is abelled with a "channel". * Example, the first field is channel 0x01, type 0x67 (temperature) and temperature is always encoded in 0.1C steps using 2 bytes (0x00E1 == 225) |
MBUS Payload | 0166E1 022BB404 01FD3C39 | 11 | * Field types are reported along with values. * Field size is adjusted to be the minimum every time (temperature is automatically encoded as a single byte: 255). * Humidity is encoded as generic sensor since there is no specific type for it. * Example, the first field value is encoded as one single byte 0x01, type 0x66 (temperature in 0.1C steps) and value is 0xE1, 255 in decimal. |