Configuration
The configuration is done using Lobaro Maintenance Tool and the Lobaro USB PC adapter or remote via LoRaWAN downlink (see LoRaWAN Downlink Config) or LTE.
Network (general)
Name#LoRaWAN-RemoteConfiguration | Description | Default Value | Value Description & Examples |
---|
WAN | Radio technology used for connection to backend | lte | lte : use either cellular NB-IoT or LTE-Mnbiot : use cellular NB-IoTltem : use cellular LTE-Mlorawan : use LoRaWAN with OTAAlorawan-abp : use LoRaWAN with ABP
|
Host |
|
|
|
LoRaWAN
The connection to the LoRaWAN network is defined by multiple configuration parameters. This need to be set according to your LoRaWAN network and the way your device is supposed to be attached to it, or the device will not be able to send any data.
For a detailed introduction into how this values need to be configured, please refer to the chapter LoRaWAN configuration in our LoRaWAN background article.
Name#LoRaWAN-RemoteConfiguration | Description | Default Value | Value Description & Examples |
---|
LostReboot | Days without downlink before reboot (triggers downlinks) | 5 | days, 0 =don't reboot |
DevEUI | DevEUI used to identify the Device |
| e.g. 0123456789abcdef |
AppKey | Key used for OTAA (v1.0 and v1.1) |
|
|
NwkKey | Key used for OTAA (v1.1 only) |
|
|
JoinEUI | Used for OTAA (called AppEUI in v1.0) |
| e.g. 0123456789abcdef |
SF | Spreading Factor | 12 |
|
LTE
Name#LoRaWAN-RemoteConfiguration | Description | Default Value | Value Description & Examples |
---|
Host | Hostname / IP of the Lobaro Platform API | coaps://platform.lobaro.com,coap://platform.lobaro.com |
|
Operator | Mobile Operator Code (optional) | 26201 | 26201 (=Deutsche Telekom), for other operators, see above. Empty = Auto detect (longer connecting time) |
Band | NB-IoT Band |
| "8", "20", "8,20", Empty = Auto detect (longer connecting time) |
APN | Mobile operator APN (optional) | * | 1nce: iot.1nce.net Vodafone Easy Connect: lpwa.vodafone.com (l = littel L) |
PIN | SIM PIN (optional) |
| Empty or 4 digits (e.g. 1234 ) |
DNS | DNS Server | 9.9.9.9,1.1.1.1 |
|
UseNbiot |
| true |
|
UseLtem |
| true |
|
wMBus (experimental)
Name#LoRaWAN-RemoteConfiguration | Description | Default Value | Value Description & Examples |
---|
EncryptionMode | 7 | 7 | 5 or 7 |
Operation
Configuration values defining the behaviour of the device.
Name#LoRaWAN-RemoteConfiguration | Description | Default Value | Value Description & Examples |
---|
ObisCode1 | Comma separated list of ObisCodes to select a subset of the available information on port 1 | 1-0:1.8.0*255 |
|
ObisCode2 | Comma separated list of ObisCodes to select a subset of the available information on port 2 |
ObisCode3 | Comma separated 1-0:2.8.0*255 |
|
ObisCode3 | Comma separated list of ObisCodes to select a subset of the available information on port 3 | 1-0:1.8.0*255 |
|
ObisCode4 | Comma separated list of ObisCodes to select a subset of the available information on port 4 | 1-0:2.8.0*255 |
|
PayloadFormat | Format used for data upload (include timestamps or not) | int | 1 =no timestamp, 2 =include timestamp |
ReadCron | Cron expression defining when to read and upload | 0 0/15 * * * * | 0 0/15 * * * * for every 15 minutes |
VerboseLogging | Enables additional Debug output | false | true = enabled, false = disabled |
See also our Introduction to Cron expressions and our Introduction to Obis Codes.
LED blinking patterns
The following pattery are explained in the order in which they appear after initial power on / reset of the device.
red/green/blue | 500 ms each | initial pattern after reset |
|
|
|
Payload (LoRaWAN)
Payload
Status Packet 64)Once per day a status packet will be sent. It contains basic information about the device. The Battery Voltage is transmitted in 1/1000 V and the temperature in 1/10 °C. Both are in Big Endian byte order.
1 byte | 1 byte | 1 byte | 1 byte | 2 byte | 2byte |
unsigned | unsigned | unsigned | unsigned | unsigned | signed |
The "Flags" field represents different error conditions by setting the individual bits:
0 (LSB) | Parsing the obis code failed |
1 | No incoming data from the opto head |
2 | Brown Out |
3 | None of the defined Obis Codes could be found in the incoming data |
4-7 | Unused |
This Format is used, when the configuration parameter PayloadFormat
is set to 1
. Please refer to the documentation of Format 2. The only differences are the port and that there is no Unix Timestamp included in the header.
This Format is used, when the configuration parameter PayloadFormat
is set to
1
(which is the default value).The payload consists of multiple entries, one entry per OBIS code given in the configuration. Each entry follows the following structure:
6 bytes | 1 byte | n bytes, LSB first | 1 byte (signed) |
Example packet: 01 00 01 08 00 FE 08 FF 01 00 00 00 00 00 00 ff 01 00 01 08 00 FE 08 FF 02 00 00 00 00 00 00 02
Entry 1:
01 00 01 08 00 FE | 08 | FF 01 00 00 00 00 00 00 | ff |
1-0:1.8.0*254 | 8 | 511 | -1 |
Value = 511 * 10^-1 = 51.1 | Entry 2:
01 00 01 08 00 FE | 08 | FF 02 00 00 00 00 00 00 | 02 |
1-0:1.8.0*254 | 8 | 767 | 2 |
Value = 767 * 10^2 = 76700 | This Format is used, when the configuration parameter PayloadFormat
is set to 2
.
The Obis Codes and Data Values are transmitted as in Payload Format 1, but each uploaded LoRaWAN-Message with data is prefixed with a 5 byte Timestamp, indicating when the values where requested from the attached meter. This allows for a more precise timing information then using the time of reception, as the upload can be delayed quite heavily due 2
.
Each uploaded LoRaWAN-Message with data is prefixed with a 5 byte Timestamp, indicating when the values where received from the attached meter. This allows for a more precise timing information then using the time of reception, as the upload can be delayed quite heavily due to our random delay feature and potentially due to duty cycle restrictions. The
timestamp also makes it easy to reassociate values from multiple uplinks to a single reading, when multiple uplinks must be used to upload all values. If a readout is spilt over multiple uplinks (because of LoRaWAN's length restrictions), every uplink from that reading will have the same timestamp (which is the time of requesting the values from the meter).timestamp also makes it easy to reassociate values from multiple uplinks to a single reading, when multiple uplinks must be used to upload all values. If a readout is spilt over multiple uplinks (because of LoRaWAN's length restrictions), every uplink from that reading will have the same timestamp (which is the time of received the values from the meter).
The Timestamp is sent as a UNIX-Timestamp encoded as a bigendian signed 40-bit number.
"Unit" is not yet implemented an will always be zero. Please be aware that SML relates to SI units whereas IEC can output units like kWh. The output of the device will not be automatically converted to SI units.
The payload consists of a header and multiple entries, one entry per OBIS code given in the configuration. Each entry follows the following structure:
Header
Head Number | Unix Timestamp |
---|
1 byte | 5 bytes |
Entry
OBISCode (hex) | length of value (n) | value | exponent | unit |
---|
6 bytes | 1 byte | n bytes, LSB first | 1 byte (signed) | 1 byte |
Example packet: 03 00 63 d1 47 80 01 00 01 08 00 ff 03 14 e3 31 ff 00 01 00 01 08 00 FE 08 FF 02 00 00 00 00 00 00 02 00
Header:
| Head Number | Unix Timestamp |
---|
Bytes | 03 | 00 63 d1 47 80 |
Description | Opto Head connected to Port 3 | Wednesday, 25. January 2023 15:15:12 |
Entry 1:
| OBISCode (hex) | length of value (n) | value | exponent | unit |
---|
Bytes | 01 00 01 08 00 FF | 03 | 14 e3 31 (=3269396) | ff | 00 |
Description | 1-0:1.8.0*255 | 3 | 326939.6 (3269396*10^-1) | -1 | 0 |
Entry 2:
| OBISCode (hex) | length of value (n) | value | exponent | unit |
---|
Bytes | 01 00 01 08 00 FE | 08 | FF 02 00 00 00 00 00 00 (=767) | 02 | 00 |
Description | 1-0:1.8.0*254 | 8 | 76700 (767*10^2) | 2 | 0 |
Multiple messages
The Bridge puts as many values in a single data message as possible (respecting the current Spreading Factor). When it cannot fit all values in a single message, it will send multiple data messages until all values are uploaded. It will never split a single value. Since every value is prefixed with the Obis code, the parser can easily assign values to Obis codes.
Status Message (Port 64)
Example Payload: 45 44 4c 00 00 01 00 00 00 0e 31 00 dc 00
name | len | type | description | in example |
---|
Firmware Identifier | 3 | String | 3 Charcter FW Identifier | 45 44 4c → EDL |
Firmware Version | 3 | uint8[3]
| Version: <major>.<minor>.<patch> | 00 00 01 → 0.0.1 |
Status | 1 | uint8 | RFU - always 0 | 00 |
Reboot reason | 1 | uint8 | RFU - always 0 | 00 |
Final words | 1 | uint8 | RFU - always 0 | 00 |
vBat | 2 | int16 | battery Voltage in mV | 0e 31 → 3633 mV → 3.633 V |
Temperature | 2 | int16 | Internal temperature from controller in 1/10 °C | 00 dc →220 → 22.0 °C |
Custom Status | 1 | uint8, bitfield | Indicates global errors, i.e. if bit 5 is set no opto head could be found | 00 → no error, everything is fine |
The Timestamp is sent as a UNIX-Timestamp encoded as a bigendian signed 40-bit number.
Payload (LTE)
Handled by the Platform
Payload (wMBus)
See Standard. APPKey will be used as key for encryption.
Reference decoder
This is a decoder written in JavaScript that can be used to parse the device's messages.
Code Block |
---|
|
function readName(bytes, i) { |
return bytes.slice(i, i + 6); |
function readValue(len, bytes, i) { |
return bytes.slice(i, i + len); |
function toHexString(byteArray) { |
byteArray.forEach(function (byte) { |
s += ('0' + (byte & 0xFF).toString(16)).slice(-2); |
function parse_sint16(bytes, idx) { |
bytes = bytes.slice(idx || 0); |
var t = bytes[0] << 8 | bytes[1] << 0; |
if( (t & 1<<15) > 0){ // temp is negative (16bit 2's complement) |
t = ((~t)& 0xffff)+1; // invert 16bits & add 1 => now positive value |
function parse_uint16(bytes, idx) { |
bytes = bytes.slice(idx || 0); |
var t = bytes[0] << 8 | bytes[1] << 0; |
function signed(val, bits) { |
if ((val & 1 << (bits - 1)) > 0) { // value is negative (16bit 2's complement) |
var mask = Math.pow(2, bits) - 1; |
val = (~val & mask) + 1; // invert all bits & add 1 => now positive value |
function uint40_BE(bytes, idx) { |
bytes = bytes.slice(idx || 0); |
bytes[1] << 24 | bytes[2] << 16 | bytes[3] << 8 | bytes[4] << 0; |
function uint16_BE(bytes, idx) { |
bytes = bytes.slice(idx || 0); |
return bytes[0] << 8 | bytes[1] << 0; |
function int40_BE(bytes, idx) {return signed(uint40_BE(bytes, idx), 40);} |
function int16_BE(bytes, idx) {return signed(uint16_BE(bytes, idx), 16);} |
function int8(bytes, idx) {return signed(bytes[idx || 0], 8);} |
function toNumber(bytes) { |
for (var i = bytes.length-1; i >= 0 ; i--) { |
function readVersion(bytes) { |
return "v" + bytes[0] + "." + bytes[1] + "." + bytes[2]; |
function decodeStatus(bytes) { |
"version":readVersion(bytes), |
"vBat": uint16_BE(bytes, 4) / 1000, |
"temp": int16_BE(bytes, 6) / 10, |
Device.setProperty("version", decoded.version); |
Device.setProperty("voltage", decoded.vBat); |
Device.setProperty("temperature", decoded.temp); |
Device.setProperty("status_flags", decoded.flags); |
function decodeSmlValuesV1(bytes) { |
if (bytes.length === 1) { |
while (pos < bytes.length) { |
var name = readName(bytes, pos); |
var value = readValue(len, bytes, pos); |
nameHex: toHexString(name), |
valueHex: toHexString(value) |
decoded.values.push(val); |
function decodeSmlValuesV2(bytes) { |
if (bytes.length === 1) { |
while (pos < bytes.length) { |
var name = readName(bytes, pos); |
var value = readValue(len, bytes, pos); |
var exponent = int8(bytes, pos); |
var unit = int8(bytes, pos); |
obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5], |
value: toNumber(value) * Math.pow(10, exponent), |
//valueHex: toHexString(value), |
obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5], |
//valueHex: toHexString(value), |
decoded.values.push(val); |
function decodeSmlValuesV3(bytes) { |
if (bytes.length === 1) { |
decoded.time = int40_BE(bytes, 1) * 1000; |
while (pos < bytes.length) { |
var name = readName(bytes, pos); |
var value = readValue(len, bytes, pos); |
var exponent = int8(bytes, pos); |
var unit = int8(bytes, pos); |
obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5], |
//nameHex: toHexString(name), |
value: toNumber(value) * Math.pow(10, exponent), |
//valueHex: toHexString(value), |
obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5], |
//valueHex: toHexString(value), |
decoded.values.push(val); |
function decode_status_code(code) { |
function decode_reboot_reason(code) { |
// STM reboot code from our HAL: |
return "LOW_POWER_RESET"; |
return "WINDOW_WATCHDOG_RESET"; |
return "INDEPENDENT_WATCHDOG_RESET"; |
return "EXTERNAL_RESET_PIN_RESET"; |
function DecoderPort64(bytes) { |
// legacy format, firmware 4.x |
// Decode an uplink message from a buffer |
// (array) of bytes to an object of fields. |
var firmware = String.fromCharCode.apply(null, bytes.slice(0, 3)); |
var version = readVersion(bytes, 3); |
var status_code = bytes[6]; |
var status_text = decode_status_code(status_code); |
var reboot_code = bytes[7]; |
var reboot_reason = decode_reboot_reason(reboot_code); |
var final_code = bytes[8]; |
var vcc = (parse_uint16(bytes, 9) / 1000) || 0.0; |
var temp = (parse_sint16(bytes, 11) / 10) || -0x8000; |
var error_state = bytes[13]; |
Device.setProperty("firmware", firmware); |
Device.setProperty("version", version); |
Device.setProperty("status_code", status_code); |
Device.setProperty("status_text", status_text); |
Device.setProperty("reboot_code", reboot_code); |
Device.setProperty("reboot_reason", reboot_reason); |
Device.setProperty("final_code", final_code); |
Device.setProperty("error_state", error_state); |
Device.setProperty("temperature", temp); |
Device.setProperty("voltage", vcc); |
"status_code": status_code, |
"status_text": status_text, |
"reboot_code": reboot_code, |
"reboot_reason": reboot_reason, |
"final_code": final_code, |
"error_state": error_state |
function Decoder(bytes, port) { |
// Decode an uplink message from a buffer |
// (array) of bytes to an object of fields. |
return decodeStatus(bytes); |
return decodeSmlValuesV1(bytes); |
return decodeSmlValuesV2(bytes); |
return decodeSmlValuesV3(bytes); |
return DecoderPort64(bytes); |
function NB_ParseDeviceQuery(input) { |
for (var key in input.d) { |
Device.setProperty("device." + key, v); |
function NB_ParseConfigQuery(input) { |
for (var key in input.d) { |
Device.setConfig(key, input.d[key]); |
function ParseV2(input) { |
decoded.headNo = input.d.head; |
decoded.time = new Date(input.d.timestamp*1000).toISOString(); |
//return toNumber(parseBase64(input.d.batch[0].value)) * Math.pow(10, input.d.batch[0].scaler); |
//decoded.payload = input.d; |
if ((input.d.batch) && (input.d.batch.length > 0) ) { |
for( i = 0; i<input.d.batch.length; i++){ |
obiscode: input.d.batch[i].obiscode, |
value: input.d.batch[i].value * Math.pow(10, input.d.batch[i].scaler), |
//valueHex: toHexString(input.d.batch[i].value), |
unit: toNumber(input.d.batch[i].unit), |
decoded.values.push(val); |
function NB_ParseStatusQuery(input) { |
for (var key in input.d) { |
Device.setProperty("device." + key, v); |
function ParseNBiot(input) { |
var query = input.q || "data"; |
res = NB_ParseDeviceQuery(input); |
res = NB_ParseConfigQuery(input); |
res = NB_ParseStatusQuery(input); |
throw new Error("Unknown message type: '" + query + "'"); |
// Wrapper for Lobaro Platform |
// Decode an incoming message to an object of fields. |
var b = bytes(atob(input.data)); |
return Decoder(b, input.fPort); |
toc