LoRaWAN Communication Protocol
The Industrial Gateway has a lot of different functions and therefore uses a lot of different ports and formats for LoRaWAN. They are all listed in the following table for quick reference. They are described in detail, grouped by their functions.
Any numerical data that is added by Lobaro's format will be sent using Big Endian (aka Network Byte Order). Data contained inside M-Bus telegrams or data read from Modbus slaves is sent as it is read; the byte order used there is dependent on the devices that generate the data. Timestamps will be uploaded as signed Big Endian 40 bit integers holding a UNIX timestamp.
Where payload formats are described, byte positions are counted beginning with 0. Values are often written in hex.
Overview
| Direction | Function | Port | PlFmt Value | Message |
|---|---|---|---|---|
| Uplink | Modbus | 3 | 1 | Modbus Responses triggered by configuration. |
| Uplink | Modbus | 4 | any | Modbus Responses triggered by Downlinks. |
| Uplink | Modbus | 5 | any | Continuation of Responses that do not fit in a single Uplink. |
| Uplink | Modbus | 20-59 | 4 | Compact payload format with timestamp |
| Uplink | Modbus | 20-59 | 5 | Compact payload format without timestamp |
| Uplink | Status | 64 | any | Status messages |
| Uplink | wM-Bus | 102 | wM-Bus telegrams with meta data. Split for long telegrams | |
| Uplink | M-Bus | 110 | Discoverd M-Bus slave addresses | |
| Uplink | M-Bus | 112 | M-Bus telegrams with meta data. Split for long telegrams | |
| Uplink | Config | 128 | any | Remote configuration response |
| Uplink | Config | 129-131 | any | Remote configuration long response 129 = start, 130 = middle, 131 = last |
| Downlink | Modbus | 4 | any | Modbus Commands to be forwarded by the Bridge. |
| Downlink | Config | 128 | any | Remote configuration |
Status Message (Up, Port 64)
The Industrial Gateway sends a status message several times a day. From this you will quickly know, if there is a problem with the device in the field. It will also allow you to reach the device via downlink on a short notice, whatever its configuration is. The status message will be sent every 6 hours, so 4 times a day. The exact time is different for each individual device, but will be consistent for each specific device.
Status messages are transmitted on port 64 and have a fixed length of 13 bytes (might get longer in future versions).
Structure of a message on port 64
Bytes | 0 . 1 . 2 | 3 . 4 . 5 | 6 . 7 . 8 | 9 . 10 | 11 . 12 |
+-----------+-----------+-----------+---------+-------------+
Data | firmware | version | rfu | voltage | temperature |
Firmware
Bytes 0 to 2 contain three ASCII chars that identify the firmware running on the device. Values are MBB for the Battery Gateway and MBD for the DIN-rail Gateway.
Version
Bytes 3 to 5 hold the version of the firmware running on the device, encoded in 3 independent unsigned 8 bit integers. Example: 0x00 0x09 0x1a stands for v0.9.26.
RFU
Bytes 6 to 8 are reserved for future use.
Voltage
Bytes 9 and 10 hold the voltage supplied by the power source in mV, encoded as an unsigned 16 bit big endian integer. Example: 0x0DDB = 3547 = 3.547 V.
Temperature
Bytes 11 and 12 hold the Gateway's internal temperature (measured inside the µC), encoded as a signed 16 bit big endian integer, holding the temperature in tenth of °C. Examaple: 0x00F6 = 246 = 24.6 °C.
Modbus - two different formats
The Industrial Gateway supports two very different formats for uploading Modbus data via LoRaWAN.
The verbose format (PlFmt = 1) includes all information needed, so that the Modbus command and the response received can be read from it. It will also include error codes in case the communication did not work. It is more complex to parse, and it will not use the datarate given by LoRaWAN efficiently, because every uplink will contain the request used.
The compact format (PlFmt = 4 and PlFmt = 5) only upload data from the response received, so they will be using LoRaWAN's limited datarate more efficiently. To understand the data in the uplinks, the backend will need to know the exact configuration of the commands. Errors in communication can be detected, but not as exactly as in the verbose format.
You need a solid understanding of how Modbus works, if you want to understand the formats explained here. Explaining Modbus is outside the scope of this document. You can find a good introduction on the English Wikipedia: https://en.wikipedia.org/wiki/Modbus.
Modbus Verbose Format (Up, Ports 3 to 5)
When PlFmt is set to 1, the Gateway will send Modbus uplinks in the verbose format on port 3. The format contains the complete response to each command. It has all additional information to know what command was sent, even if it runs into an error. The format addapts to changing Spreading Factors and tries to use the available payload capacity. If a response is too long for the payload capacity, messages are split into parts. For split uplinks, the first one will also be sent on port 3, following parts (that need to be re-attached) will be sent on port 5. It is possible to write configurations that will never need to split a message. Just make sure that no response will be longer than the minimal available uplink size (respecting the additinal bytes of this format). The device will try to fit as many responses as possible into a single uplink, without changing the order in which they are sent. If the next response will not fit, a new uplink will be started for it. Only responses that do not fit in a single uplink will ever be split. Responses from separate entries to MbCmd (with their own cron) will never be put into the same uplink.
Commands that are executed from downlinks sent on port 4 will also use the Verbose Format. The responses to downlinks will be sent on port 4 instead of 3, the rest stays the same. If those responses are split over multiple uplinks, the additional parts will also be sent on port 5.
Take a look at the example uplinks and at the reference parser in this document, to get a better understanding on how this format works.
Structure of a message on port 3
Byte | 0 . 1 . 2 . 3 . 4 | 5 ... | ... | ... | ... |
+-------------------+------------+------------+-----+------------+
Data | timestamp | response 1 | response 2 | ... | response n |
Structure of a response part on port 3
Byte | 0 | 1 .. len-3 | len-2 . len-1 | len |
+--------+-----------------+----------------+-------+
Data | length | Modbus response | start register | count |
Timestamp
Bytes 0 to 4 will hold the timestamp when the execution of the entry started, the timestamp on which the entry's cron triggered. That might not be the exact time at which the commands of this uplink have been executed. The commands of the entry will be executed one ofter another, and communication over Modbus takes time. That time is much longer if the communication fails (as the Gateway waits for a timeout and executes retries for failed commands). The commands of this uplink could have been executed several seconds later than the timestamp. The messages are built like this on purpose. It allowes the backend to know, which uplinks are from the same execution of the same entry.
The timestamp is sent as a signed 40 bit integer containing the number of seconds passed since 1970-01-01T00:00:00 UTC (a UNIX timestamp).
Response
Each uplink will contain one or more responses. Each response starts with its length, so that the parser can find out, how many responses are in an uplink.
Length
Each uplink starts with an unsigned 8 bit integer that holds the length of the response (including the additional 3 bytes for start register and count). If there are multiple responses in an uplink, the length can be used to find out where they end. If a response has been split over multiple uplinks, this can be identified by the length as well.
Modbus response
The bytes sent as response by the Modbus slave device as they were sent on the wire, excluding the checksum. This will normally include the data you need, as well as the slave's address and the method used to read the data. Keep in mind, that in case of an error, the response will be shorter then on successful execution. Error responses are 3 bytes long and contain an error code as the third byte. If the error code is 11 (= 0x0B), the error response is actually created by the Gateway. It is an error code for Modbus gateways to indicate that no response could be received. An error condition is indicated in the response by highest bit of the function byte. This is part of the Modbus spec.
Start register
The address of the first register/coil that was affected, encoded as an unsigned big endian 16 bit integer. This information is needed on error conditions to know which command was executed. For Modbus commands that do not have a starting register/coil (e.g. function 7, reading exeption status), the value of this field is undefined.
Count
The number of registers/coils that have been affected, encoded as an unsigned 8 bit integer. This information is sometimes needed to know which command was executed. For Modbus commands that do not have a register/coil value (e.g. funtion 5, forcing a single coil), this value is undefined. When a command is executed that affects more than 255 coils, this value is undefined.
Split uplinks (Port 5)
If a Modbus response does not fit into a single uplink, it will be split into multiple parts. The first part will be put into an uplink on port 3 as described above. There will be only a single response in an uplink if splitting happens. You can see by the length field, and the uplinks size, that the response has been split. There will follow as many uplinks as needed to fit the hole response sent on port 5. Use the length byte and the LoRaWAN frame counter to fit all the parts together. The payloads must all be appended and can than be handled as a single large uplink on port 3. For responses to downlinks on port 4, the splitting process is the same, only that the first uplink will be sent on port 4 instead of port 3. It is possible to write configurations that avoid splitting completely. This is often easier then handling split uploads.
Responsed to Downlinks (Port 4)
Uplinks triggered by downlinks on port 4 are always using the Verbose Format. They are sent on port 4 instead of port 3. The timestamp in port 4 uplinks holds the time when the downlink with the command was received.
Modbus Compact Format (Up, Ports 20 to 59)
When PlFmt is set to 4 or 5, the Gateway will send Modbus uplinks in compact format. All Modbus commands in all entries in MbCmd will be processed in the order they appear. The Gateway will create an uplink format that holds all data that is read from any Modbus slaves. It will be a fixed format, where every byte from every register or coil will have its fixed position.
Look at the Log when you test the configuration for the compact format. The Gateway will output detailed information on the format it creates from the configuration. It will list register number and slave address for each byte. If there is a problem in the configuration and no format can be created, that will also be reported.
The fixed upload format created will start using port 20 and use as many ports needed (up to port 59) to fit all the bytes from your configuration. No message will be longer then the number of bytes set in PlMax. The bytes of the responses will simply be attached in the order they appear in the command definition. Whenever a response wont fit in the message, a new uplink definition on a new port will be startet. If a response is too long for the payload size set in PlMax then the configuration is invalid. Longer commands must be split into multiple shorter commands, so that the responses will fit. The order of responses will never be changed, even if a different order would fit into less different uplink formats. It is up to the person configuring the Industrial Gateway to find a configuration that uses this format efficiently. When there exist multiple entries in MbCmd each new entry will always start a new uplink format.
It is possible to build very efficient formats with this feature. You can group data points together that you need in a single uplink. If you need help with configuring the Gateway for your installation, please contact sales@lobaro.de for an offer.
Each uplink format will have a single header byte that holds an error flag and a format id (0-127) that you can set freely for each of your devices. PlFmt = 4 will include a 5 byte timestamp in each uplink. PlFmt = 5 will not. Appart from that, the formats work identically.
The payload in the compact format is influenced by the config parameters MbCmd, PlFmt, PlMax, and PlId.
Uplinks responding to downlinks on port 4 will always respond in the verbose format on port 4.
Structure of an uplink using PlFmt = 4
Byte | 0 | 1 . 2 . 3 . 4 . 5 | 6 ... | ... | ... | ... |
+--------+-------------------+------------+------------+------------------+
Data | header | timestamp | response 1 | response 2 | ... | response n |
Structure of an uplink using PlFmt = 5
Byte | 0 | 1 ... | ... | ... | ... |
+--------+------------+------------+-----+------------+
Data | header | response 1 | response 2 | ... | response n |
Header
Byte 0 of each uplink holds the header byte. The most significant bit will indicate if an error occurred during any of the commands that were executed to collect the data for this uplink. Be aware that this format will neither report the error code, nor will it explicitly state, which commands resulted in an error. The bytes comming from the failed modbus command will all be set to 0xff. This can also be a legitimate value returned by a Modbus slave device.
The lower seven bits of the header byte contain the value of PlId. This can be useful if you have a large number of devices in the field where many share a configuration. You can create a parser for your formats that identifies a specific format by this ID. If you don't need that feature, just keep PlId = 0.
If you do not use PlId, the header byte will be 0x00 if all commands for that format were executed successfully. If there has an error on at least one of the commands, it will be 0x80.
Timestamp
On PlFmt = 4, bytes 1 to 5 will hold the timestamp when the execution of the entry started. That is the timestamp on which the entry's cron triggered. That might not be the exact time at which the commands of this uplink have been executed. The commands of the entry will be executed one ofter another, and communication over Modbus takes time. That time is much longer if the communication fails (as the Gateway waits for a timeout and executes retries for failed commands). The commands of this uplink could have been executed several seconds later than the timestamp. The messages are built like this on purpose. It allowes the backend to know, which uplinks are from the same execution of the same entry.
The timestamp is sent as a signed 40 bit integer containing the number of seconds passed since 1970-01-01T00:00:00 UTC (a UNIX timestamp).
Wireless M-Bus (Up, Port 102)
The payload format for uploading wireless M-Bus telegrams is the same that is used by Lobaro's Wireless M-Bus when set to PayloadFormat 2. Telegrams are uploaded on port 102 with some metadata prefixing it. Because wM-Bus telegrams are often longer than the available payload size in LoRaWAN, telegrams can be split over several uplinks. The first byte in each uplink on port 102 indicates if that uplink is part of a split message, and if there are uplinks comming before and/or after it. With this information and the frame number of the LoRaWAN uplink, split messages can be put back together again. The payload will always use as many bytes as possible for the current spreading factor used.
If you are using the Lobaro Platform for integrating your devices, it will take care of putting the parts back together. It can also decrypt encrypted wM-Bus telegrams if you provide the keys to the platform. It also has a parser for M-Bus telegrams, that will present the contained data in a JSON-Format that is easier to read than a raw M-Bus telegram.
Structure of a message on port 102
Start of a wireless M-Bus new telegram uploaded:
Byte | 0 | 1 . 2 . 3 . 4 . 5 | 6 | 7 ... |
+------+-------------------+------+--------------------------+
Data | part | timestamp | rssi | raw telegram (beginning) |
Continuation of a wireless M-Bus telegram upload:
Byte | 0 | 1 ... |
+------+--------------------------+
Data | part | raw telegram (continued) |
Part byte
The byte at position 0 (part) indicates if the uplink containes the first and/or final part of a telegram:
| Meaning | |
|---|---|
| 0x03 | This uplink contains both, the first and final part of a telegram. Bytes 1 to 6 contain the timestamp of reception and the RSSI. The bytes from position 7 to the end of this uplink are the complete telegram. |
| 0x02 | This uplink contains the final part of the telegram, but not its start. There has been at least one uplink before this for the telegram. The telegram bytes start from position 1; there is no meta data in this uplink. |
| 0x01 | This uplink contains the first part of the telegram, but not the final part. There will be at least one more uplink containing bytes of the telegram. Bytes 1 to 6 contain the timestamp of reception and the RSSI. The bytes from position 7 to the end of this uplink contain the first part of the telegram. |
| 0x00 | This uplink contains neither the first nor the final part of a telegram. There has been at least one uplink before this, and there will be at least one uplink after this, for the current telegram. There is no meta data in this uplink. The bytes from position 1 to the end of the uplink contain a part of the raw telegram. |
Timestamp
The bytes 1 to 5 contain the time of reception of the w-MBus telegram, measured by the internal clock of the Industrial Gateway. It is sent as a signed 40 bit number containing the number of seconds passed since 1970-01-01T00:00:00 UTC (a UNIX timestamp). The timestamp is only included in uplinks that contain the first part of a telegram (Part byte = 0x03 or Part byte = 0x01).
RSSI
The byte at position 6 holds the reception quality of the wireless M-Bus telegram, as it was received by the Industrial Gateway. This can be useful as an indicator of how far away a received meter is from the Gateway. It also gives you an estimation of how likely it is that the reception of the meter's telegram by the Gateway will be disrupted by radio disturbances. The byte contains an unsigned integer that holds the negative RSSI measured on reception in dBm. So a value of 0x31 means an RSSI of -49 dBm. The RSSI is only included in uplinks that contain the first part of a telegram (Part byte = 0x03 or Part byte = 0x01).
Raw Telegram
The remaining payload of an uplink is used to send as many bytes of the telegram as possible. If a telegram is split, it must be reattached in your backend (or in the Lobaro Platform). Use the part byte and the LoRaWAN frame counter to fit the parts together. If there are skips in the frame counter, than part of the telegram has been lost. If an uplink holds the beginning of a telegram (Part byte = 0x03 or Part byte = 0x01), the telegram's data starts at byte 7. For continued uplinks (Part byte = 0x02 or Part byte = 0x00), te telegram's data starts at byte 1.
Wired M-Bus (Up, Ports 110 and 112)
The payload format used to upload wired M-Bus telegrams is similiar to the format used for wireless M-Bus telegrams. Because M-Bus telegrams are often longer than the available payload size in LoRaWAN, telegrams can be split ofer several uplinks. The first byte in each uplink on port 112 indicates if that uplink is part of a split message, and if there are uplinks comming before and/or after it. With this information and the frame number of the LoRaWAN uplink, split messages can be put back together again. The payload will always use as many bytes as possible for the current spreading factor used.
To use wired M-Bus with the Industrial Gateway, you will need a M-Bus converter between the Gateway and your slave devices. Lobaro provides a combined solution under order number 8000200, that allows to attach up to 20 wired M-Bus devices.
If you are using the discovery feature, the device will scan for attached M-Bus slaves after booting. It will upload the result of the scanning process on port 110. There will be discovery uplink for each entry in MbusCmd that has * as the device list. If there are more devices discovered than fit in a LoRaWAN uplink, the information is split over mutliple uplinks.
If you are using the Lobaro Platform for integrating your devices, it will take care of putting the parts back together. It also has a parser for M-Bus telegrams, that will present the contained data in a JSON-Format that is easier to read than a raw M-Bus telegram.
Structure of a message on port 110
Byte | 0 . 1 . 2 . 3 | 4 | 5 |
+---------------+--------------+----------------+
Data | Baud rate | slaves found | addresses sent |
Baud rate
The Baud rate used for the discovery, encoded as an unsigned big endian 32 bit integer.
Slaves found
The number of slaves fond during scanning, encoded as an unsigned 8 bit integer.
Addresses sent
Number of addresses already sent in uplinks before this one, encoded as an unsigned 8 bit integer. This will only have a value > 0 when there have been more slaves discovered than can be fit in a single uplink. For this to happen, you will need to use a different M-Bus converter than the one normally provided by Lobaro (the Relay M-Bus PW20L/RS485), as that one only supports up to 20 slave devices.
Structure of a message on port 112
Start of a new wired M-Bus telegram uploaded:
Byte | 0 | 1 . 2 . 3 . 4 . 5 | 6 | 7 . 8 . 9 | 10 ... |
+------+-------------------+---------+--------------------------------------+
Data | part | timestamp | address | request | raw telegram (beginning) |
Continuation of a wired M-Bus telegram upload:
Byte | 0 | 1 ... |
+------+--------------------------+
Data | part | raw telegram (continued) |
Part byte
The byte at position 0 (part) indicates if the uplink containes the first and/or final part of a telegram:
| Meaning | |
|---|---|
| 0x03 | This uplink contains both, the first and final part of a telegram. Bytes 1 to 9 contain meta data. The bytes from position 10 to the end of this uplink are the complete telegram. |
| 0x02 | This uplink contains the final part of the telegram, but not its start. There has been at least one uplink before this for the telegram. The telegram bytes start from position 1; there is no meta data in this uplink. |
| 0x01 | This uplink contains the first part of the telegram, but not the final part. There will be at least one more uplink containing bytes of the telegram. Bytes 1 to 9 contain metadata. The bytes from position 10 to the end of this uplink contain the first part of the telegram. |
| 0x00 | This uplink contains neither the first nor the final part of a telegram. There has been at least one uplink before this, and there will be at least one uplink after this, for the current telegram. There is no meta data in this uplink. The bytes from position 1 to the end of the uplink contain a part of the raw telegram. |
Timestamp
The bytes 1 to 5 contain the time of reception of the w-MBus telegram, measured by the internal clock of the Industrial Gateway. It is sent as an signed 40 bit number containing the number of seconds passed since 1970-01-01T00:00:00 UTC (a UNIX timestamp). The timestamp is only included in uplinks that contain the first part of a telegram (Part byte = 0x03 or Part byte = 0x01).
Address
Byte 6 holds the (primary) M-Bus address of the slave this telegram was received from. It is encoded as an unsigned 8 bit integer. The address is only included in uplinks that contain the first part of a telegram (Part byte = 0x03 or Part byte = 0x01).
Request
Bytes 7 to 9 contain the request this telegram is a response to, encoded as three ascii characters. This will normally be "UD2". The request is only included in uplinks that contain the first part of a telegram (Part byte = 0x03 or Part byte = 0x01).
Raw Telegram
The remaining payload of an uplink is used to send as many bytes of the telegram as possible. If a telegram is split, it must be reattached in your backend (or in the Lobaro Platform). Use the part byte and the LoRaWAN frame counter to fit the parts together. If there are skips in the frame counter, than part of the telegram has been lost. If an uplink holds the beginning of a telegram (Part byte = 0x03 or Part byte = 0x01), the telegram's data starts at byte 10. For continued uplinks (Part byte = 0x02 or Part byte = 0x00), te telegram's data starts at byte 1. Remote Configuration (Down & Up, Ports 128 to 131) The Industrial Gateway supports remote configuration by downlinks on port 128. It can be used to read and write config parameters and execute commands, e.g. a reboot. Responses to these downlinks are sent on port 128. If a response is too long for a single uplink, it will be split using the ports 129 - 131.
For more information, see LoRaWAN Downlink Config.
On devices running a firmware version <= 0.5.1, the config changed by LoRaWAN downlinks will not be persisted in the device. After a reboot, the original configuration will be restored.
LoRaWAN reference Decoder
This is a decoder written in JavaScript that can be used to parse the device's LoRaWAN messages. It can be used as is in The Things Network.
Details
// Version 0.0.2
// Changelog
//
// 0.0.2 - 2024-09-24
// - Added support for Coils and do not return NaN in some cases.
function readVersion(bytes) {
if (bytes.length<3) {
return null;
}
return "v" + bytes[0] + "." + bytes[1] + "." + bytes[2];
}
function int40_BE(bytes, idx) {
bytes = bytes.slice(idx || 0);
return bytes[0] << 32 |
bytes[1] << 24 | bytes[2] << 16 | bytes[3] << 8 | bytes[4] << 0;
}
function int16_BE(bytes, idx) {
bytes = bytes.slice(idx || 0);
return bytes[0] << 8 | bytes[1] << 0;
}
function uint16_BE(bytes, idx) {
bytes = bytes.slice(idx || 0);
return bytes[0] << 8 | bytes[1] << 0;
}
function port1(bytes) {
return {
"port":1,
"version":readVersion(bytes),
"flags":bytes[3],
"temp": int16_BE(bytes, 4) / 10,
"vBat": int16_BE(bytes, 6) / 1000,
"timestamp": int40_BE(bytes, 8),
"operationMode": bytes[13],
"noData": !!(bytes[3] & 0x01)
};
}
function port2(bytes) {
var regs = [];
if (bytes.length > 5) {
// loop through data packs
var b = bytes.slice(5);
while (b.length>=4) {
var r = {
"device":b[0],
"register":int16_BE(b, 1),
"count":b[3] & 0x3f,
"error":!!(b[3]>>7),
"data":null
};
var dataLen = r["count"]*2;
if (b.length >= dataLen+4) {
r["data"] = b.slice(4, 4 + dataLen);
}
regs.push(r);
b = b.slice(4+dataLen);
}
}
return {
"port":2,
"timestamp": int40_BE(bytes, 0),
"registers": regs
};
}
function modbusErrorString(code) {
// Modbus exception codes
// see https://en.wikipedia.org/wiki/Modbus#Exception_responses
switch (code) {
case 1:
return "Illegal Function";
case 2:
return "Illegal Data Address";
case 3:
return "Illegal Data Value";
case 4:
return "Slave Device Failure";
case 5:
return "Acknowledge";
case 6:
return "Slave Device Busy";
case 7:
return "Negative Acknowledge";
case 8:
return "Memory Parity Error";
case 10:
return "Gateway Path Unavailable";
case 11:
return "Gateway Target Device Failed to Respond";
default:
return "Unknown error code";
}
}
function parseModbusPayloadRegisters(payload) {
if (payload.length < 1) {
return null;
}
var byteCnt = payload[0];
if (payload.length !== byteCnt + 1) {
return null;
}
var fun = payload[1] & 0xf;
var vals = [];
if (fun == 0x01) {
// Coils
for (var i=0; i<byteCnt; i++) {
vals.push(+payload[i+1])
}
} else {
// 2 Byte Registers
for (var i=0; i<byteCnt; i+=2) {
vals.push([+payload[i+1], +payload[i+2]])
}
}
return vals;
}
function parseModbusResponse(raw) {
var resp = {};
if (raw.length >= 6) {
var fun = raw[1] & 0xf;
var error = !!(raw[1] & 0x80);
var rawResp = raw.slice(0, raw.length - 3);
resp["slave"] = raw[0];
resp["function"] = fun;
resp["error"] = error;
resp["start"] = uint16_BE(raw, raw.length - 3);
resp["cnt"] = raw[raw.length - 1];
resp["raw"] = rawResp;
if (error) {
resp["errorCode"] = raw[2];
resp["errorText"] = modbusErrorString(raw[2]);
} else {
resp["values"] = parseModbusPayloadRegisters(rawResp.slice(2))
}
}
return resp;
}
function FullResponses(bytes, port) {
var timestamp = int40_BE(bytes);
var pos = 5;
var resps = [];
while (pos < bytes.length) {
var respLen = bytes[pos++];
if (bytes.length >= pos + respLen) {
var rawResponse = bytes.slice(pos, pos + respLen);
resps.push(parseModbusResponse(rawResponse));
pos += respLen;
} else {
break;
}
}
return {
"port": port,
"timestamp" : timestamp,
"responses": resps
};
}
function bin2String(array) {
var result = "";
for (var i = 0; i < array.length; i++) {
result += String.fromCharCode(array[i]);
}
return result;
}
function ConfigResponse(data) {
var t = bin2String(data);
return {
"response" : t,
"error" : (t.length === 0) || (t[0] === '!')
}
}
/**
* TTN decoder function.
*/
function Decoder(bytes, port) {
switch (port) {
case 1:
// Status message:
return port1(bytes);
case 2:
// not legacy format:
return port2(bytes);
case 3:
case 4:
// v1.0.0 format, full modbus responses:
return FullResponses(bytes, port);
case 5:
// continuation of previous response:
return {};
case 6:
// dense format with prefixed timestamp:
return {};
case 7:
// dense format without timestamp:
return {};
case 128:
return ConfigResponse(bytes);
}
return {"error":"invalid port", "port":port};
}
/**
* LoRaServer decoder function.
*/
function Decode(fPort, bytes) {
// wrap TTN Decoder:
return Decoder(bytes, fPort);
}
function Parse(input) {
var data = bytes(atob(input.data));
var port = input.fPort;
var fcnt = input.fCnt;
var vals = Decoder(data, port);
vals["port"] = port;
vals["data"] = data;
vals["fnct"] = fcnt;
var lastFcnt = Device.getProperty("lastFcnt");
vals["reset"] = fcnt <= lastFcnt;
Device.setProperty("lastFcnt", fcnt);
return vals;
}
LoRaWAN Examples
This chapter illustrates with some examples, how working with the Industrial Gateway looks like.The bytes that are sent via LoRaWAN are presented here as hex strings, while on the air they are sent as raw bytes.Modbus Commands and Responses are broken down to their parts in the explanations, but explaining the format used by Modbus in detail is beyond the scope of this manual.You can find a short explanation on Modbus on Wikipedia: https://en.wikipedia.org/wiki/Modbus.
The Examples section contains an illustration of a split up Response
Uplinks Modbus in Verbose Payload Format (PlFmt=1)
The following shows some examples of configuration for the automated reading and what the generated Uplinks for that could look like.
Example A1: Read Holding Registers 0, 1, and 2 of device with address 1
Verbose Payload Format (Port 3, PlFmt=1)
MbCmd = '010300000003'
# Example resulting Uplink after successful readout
Up, Port 3: '005d1698fd0c0103061234567890ab000003'
'005d1698fd' -> timestamp = 1561762045 -> 2019-06-28T22:47:25 UTC
'0c' -> first Response is 12 bytes long
'0103061234567890ab000003' 12 bytes modbus response:
'01' -> slave device with address 1
'03' -> function 3 = read Holding Register, success
'06' -> 6 bytes of data in Response following
'1234567890ab' -> 6 bytes of data
'0000' -> start reading at register 0
'03' -> read 3 consecutive registers
# Example resulting Uplink after failing readout
Up, Port 3: '005d1698fd0601830b000003'
'005d1698fd' -> timestamp = 1561762045 -> 2019-06-28T22:47:25 UTC
'06' -> first Response is 6 bytes long
'01830b000003' 3 bytes modbus response:
'01' -> slave device with address 1
'83' -> function 3 with error indicator 80 = read Holding Register, failed
'0b' -> error code 11: "Gateway Target Device Failed to Respond"
'0000' -> start reading at register 0
'03' -> read 3 consecutive registers
Example A2: Read coils 1000-1019 of device 32
Verbose Payload Format (Port 3, PlFmt=1)
MbCmd = '200103e80014'
# Example resulting Uplink
Up, Port 3: '005d1698fd 09 200103f1041a03e814'
'005d1698fd' -> timestamp = 1561762045 -> 2019-06-28T22:47:25 UTC
'09' -> first Response is 9 bytes long
'200103f1041a03e814' 9 bytes of response:
'20' -> slave device with address 32
'01' -> read coils, success
'03' -> 3 bytes of data
'f1041a' -> 20 bits of data packed into 3 bytes
'03e8' -> start reading at coil 1000
'14' -> read 20 consecutive coils
Example A3: Read registers from two devices
Verbose Payload Format (Port 3, PlFmt=1)
MbCmd = '0a0300010005,3001ea600020'
# Example resulting Uplink
Up, Port 3: '005d1698fd100a030a111122223333444455550001050a30010412345678ea6020'
'005d1698fd' -> timestamp = 1561762045 -> 2019-06-28T22:47:25 UTC
'10' -> first Response is 16 bytes long
'0a030a11112222333344445555000105' 16 bytes of Response
'0a' -> slave device with address 10
'03' -> read Holding Registers, success
'0a' -> 10 bytes of data following
'11112222333344445555' 10 bytes of data
'0001' -> start reading at register 1
'05' -> read 5 registers
'0a' -> second Response is 10 bytes long
'30010412345678ea6020' 10 bytes of Response
'30' -> slave device with address 48
'01' -> read Coils, success
'04' -> 4 bytes of data following
'12345678' -> 32 bits of data packed in 4 bytes
'ea60' -> start at coil 60000
'20' -> read 32 coils
Example A4: Split uplink message
Verbose Payload Format (Port 3, PlFmt=1)
MbCmd = '010300010020'
# Command reads 32 consecutive registers resulting in 64 bytes payload
# Example resulting Uplinks for a Spreading Factor of 12 with 51 bytes of payload per message
Up 1, Port 3:
'005d1698fd46010340000100020003000400050006000700080009000a000b000c000d000e000f001000110012001300140015'
'005d1698fd' -> timestamp = 1561762045 -> 2019-06-28T22:47:25 UTC
'46' -> first Response is 70 bytes long since the remainder of the message does not contain 70 bytes,
you know there must be an additional part coming
Up 2, Port 5: '0016001700180019001a001b001c001d001e001f00200120'
This contains the rest of the message. Appended to the previous message, it adds up to the correct number of bytes.
Uplinks triggered by Downlink Commands
Example B1: Read single Input Register by Downlink
Verbose Payload Format (Port 4, Downlink Response)
Down, Port 4: '06180401000001'
'06' -> first Command is 6 bytes long
'180401000001' 6 bytes of Modbus Command
'18' -> slave device with address 24
'04' -> function 4, read Input Register
'0100' -> start at register 256 '0001' -> read 1 register
# Example resulting Uplink
Up, Port4: '004b3dd67508180402abcd010001'
'004b3dd675' -> timestamp = 1262343797 -> 2010-01-01T11:03:17 UTC
'08' -> first Response is 8 bytes long
'180404abcd010001' 8 bytes of Response
'18' -> slave device with address 24
'04' -> read Input Register, success
'02' -> 2 bytes of data following
'abcd' -> 2 bytes of data
'0100' -> start at register 256
'01' -> read 1 register
Example B2: Writing holding registers on multiple devices
Verbose Payload Format (Port 4, Downlink Response)
Down, Port 4: '06a106aabb12340fa210a0010004081122334455667788'
'06' -> first Command is 6 bytes long
'a106aabb1234' 6 bytes of Modbus Command
'a1' -> slave device with address 161
'06' -> function 6, write single Holding Register
'aabb' -> address of Register to write = 43707
'1234' -> two bytes of data
'0f' -> second Command is 15 bytes long
'a210a0010004081122334455667788' 15 byte of Modbus Command
'a2' -> slave device with address 162
'10' -> function 16, write multiple Holding Registers
'a001' -> start at register 40961
'0004' -> 4 registers to write
'08' -> 8 bytes of data follow
'1122334455667788' -> 8 bytes of data
# Example resulting Uplink Up, Port 4: '004b3dd67506a1860200000006a210a0010004'
'004b3dd675' -> timestamp = 1262343797 -> 2010-01-01T11:03:17 UTC
'06' -> first Response is 3 bytes long
'a18602000000' 3 bytes of Modbus Response
'a1' -> slave device address 161 '86' -> write single Holding Regsiter, failed
'02' -> error code 2: "Illegal Data Address"
'0000' -> start register not used (undefined)
'00' -> count not used (undefined)
'06' - second Response is 6 bytes long
'a210a0010004' 6 bytes of Modbus Response
'a2' -> slave device address 162
'10' -> write multiple Holding Registers, success
'a001' -> start at register 40961
'0004' -> 4 registers to write
Uplinks in Compact Payload Format
Example C1: Single Modbus Command, PlFmt 4
# Attached device: B+G E-Tech power Meter
# Config:
MbCmd = 010300000003 -> Read registers 0 to 3 from Slave 1
PlFmt = 4 -> Compact Format with Timestamp
PlMax = 51 -> Max 51 Bytes per Uplink
PlId = 0 -> Payload Id = 0
# Info from Log
APP| Number of commands to be executed on cron: 1
APP| 01 03 00 00 00 03
APP| Compact format definition, id=0, max size=51
APP| Port 20:
APP| 000-000=error&fmt-id
APP| 001-005=timestamp
APP| 006-011=resp(010300000003)
# Successful readout, leading to Uplink on Port 20: '00005fd8bf08000000010033'
'00' -> '0' no error, '00' -> PlId = 0
'005fd8bf08' -> timestamp = 1608040200 -> 2020-12-15T13:50:00 UTC
'000000010033' -> 6 Bytes data from 3 Registers -> 3.07 kWh
# Failed readout, leading to Uplink on Port 20: '80005fd8c7caffffffffffff'
'80' -> '1' error in any of the responses, '00' -> PlId = 0
'005fd8c7ca' -> timestamp = 1608042442 -> 2020-12-15T14:27:22 UTC
'ffffffffffff' -> 6 Bytes set to 0xff, indicating error
Example C2: Multiple Commands, PlFmt 5
# Attached devices: Multiple B+G E-Tech power Meters
# Config:
MbCmd = 010300000003,020300000003 -> Read registers 0 to 3 from Slave 1
PlFmt = 5 -> Compact Format with Timestamp
PlMax = 51 -> Max 51 Bytes per Uplink
PlId = 13 -> Payload Id = 13
# Info from Log
APP| Number of commands to be executed on cron: 2
APP| 01 03 00 00 00 03
APP| 02 03 00 00 00 03
APP| Compact format definition, id=29, max size=51
APP| Port 20:
APP| 000-000=error&fmt-id
APP| 001-006=resp(010300000003)
APP| 007-012=resp(020300000003)
# Successful readout of both, leading to Uplink on Port 20: '1d0000000100330000001a0040'
'1d' -> '0' no error, '1d' -> PlId = 13
'000000010033' -> 6 Bytes data from 3 Registers, Meter 1 -> 3.07 kWh
'0000001a0040' -> 6 Bytes data from 3 Registers, Meter 2 -> 67.20 kWh
# Partly successful readout, leading to Uplink on Port 20: '9d000000010033ffffffffffff'
'9d' -> '1' error (in any of the values in message), '1d' -> PlId = 13
'000000010033' -> 6 Bytes data from 3 Registers, Meter 1 -> 3.07 kWh
'ffffffffffff' -> 6 Bytes set to 0xff to indicate error, Meter 2
# Failed readout, leading to Uplink on Port 20: '9dffffffffffffffffffffffff'
'9d' -> '1' error (in any of the values in message), '1d' -> PlId = 13
'ffffffffffff' -> 6 Bytes set to 0xff to indicate error, Meter 1
'ffffffffffff' -> 6 Bytes set to 0xff to indicate error, Meter 2
Example C3: Multimple Commands, leading in multiple Upoads, PlFmt 5
# Config
MbCmd = 010300000010,010301000004,0103020a000c,010300800008
PlFmt = 5 -> Compact Format with Timestamp
PlMax = 40 -> Max 40 Bytes per Uplink
PlId = 10 -> Payload Id = 10
# Info from Log
APP| Number of commands to be executed on cron: 4
APP| 01 03 00 00 00 10
APP| 01 03 01 00 00 04
APP| 01 03 02 0A 00 0C
APP| 01 03 00 80 00 08
APP| Compact format definition, id=10, max size=40
APP| Port 20:
APP| 000-000=error&fmt-id
APP| 001-032=resp(010300000010)
APP| Port 21:
APP| 000-000=error&fmt-id
APP| 001-008=resp(010301000004)
APP| 009-032=resp(0103020a000c)
APP| Port 22:
APP| 000-000=error&fmt-id
APP| 001-016=resp(010300800008)
# Resulting in 3 consecutive uploads on Port 20-22:
# Port 20: 0a0000000100020003000400050006000700080009000a000b000c000d000e000f
'0a' -> '0' no error, '0a' -> PlId 10
'0000000100020003000400050006000700080009000a000b000c000d000e000f' -> 32 Bytes of Data from Registers 0x0000-0x000f
# Port 21: 8affffffffffffffff111122223333444455556666777788889999aaaabbbbcccc
'8a' -> '1' error occured, '0a' -> PlId 10
'ffffffffffffffff' -> 8 Bytes set to 0xff indicating error reading Registers 0x0100-0x0103
'111122223333444455556666777788889999aaaabbbbcccc' -> 24 Bytes of Data from Registers 0x020a-0x0215
# Port 22: 0a01010202030304040505060607070808
'0a' -> '0' no error, '0a' -> PlId 10
'01010202030304040505060607070808' -> 16 Bytes of Data from Registers 0x0080-0x0087