You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 17 Next »

General Information

The Dzero Gateway 4X is a device that can be used to readout modern utility meters with standardized infrared "INFO" interface.

The meter outputs over its infrared "INFO" interface a serial protocol conforming to the Smart Meter Language Protocol 1.04 (SML) or IEC 62056-21 protocol. This interface is intended to be used by end-users and not for billing purposes of the electricity supplier.

The read information normally contains the current consumption values of the meter and gets interpreted and forwarded by the Dzero Gateway 4X via a LoRaWAN or Nb-IoT to web based applications interested in further processing this data.


Warning

Older meters with "infrared pulse" output are not compatible to the Dzero Gateway 4X. Please check our list of compatible meters to make sure it is equipped with the correct interface.

Hint

Consider using the latest firmware on your hardware

  • See available firmware downloads

Compatible utility meters

Any meter that adheres to the standard can be read. The following list contains meters that we successfully tested.

Electricity meter

Manufacturer

DTZ541-ZEBAHolley
LK13 seriesLogarex
OpenWay® 3.HZiTron
SGM-C4 seriesefr
SGM-D seriesefr
eHZ-K seriesEMH
mMe4.0 seriesEMH
ED300 seriesEMH
eBZD seriesEMH
E320Landis+Gyr
MT681

ISKRA

Quickstart

  1. Connect to the device with the Lobaro Tool using the Lobaro Config Adapter
  2. Under Configuration click "Reload Config" and change the fields "ReadCron" and "ObisCode" for all connected heads as needed as well as choosing the desired Network Connection by setting the "WAN" followed by clicking on "Write to Device".
  3. If you selected lorawan as WAN: Register the device in your LoRaWAN network
  4. Place the EDL21 opto head on the "Info" interface
  5. Restart the device by pressing the reset button

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-RemoteConfigurationDescriptionDefault ValueValue Description & Examples
WANRadio technology used for connection to backendlte
  • lte: use either cellular NB-IoT or LTE-M
  • nbiot: use cellular NB-IoT
  • ltem: use cellular LTE-M
  • lorawan: use LoRaWAN with OTAA
  • lorawan-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-RemoteConfigurationDescriptionDefault ValueValue Description & Examples
LostRebootDays without downlink before reboot (triggers downlinks)5days, 0=don't reboot
DevEUIDevEUI used to identify the Device
e.g. 0123456789abcdef
AppKeyKey used for OTAA (v1.0 and v1.1)

NwkKeyKey used for OTAA (v1.1 only)

JoinEUIUsed for OTAA (called AppEUI in v1.0)
e.g. 0123456789abcdef
SFSpreading Factor12

LTE

Name#LoRaWAN-RemoteConfigurationDescriptionDefault ValueValue Description & Examples
HostHostname / IP of the Lobaro Platform APIcoaps://platform.lobaro.com,coap://platform.lobaro.com
OperatorMobile Operator Code (optional)2620126201 (=Deutsche Telekom), for other operators, see above. Empty = Auto detect (longer connecting time)
BandNB-IoT Band
"8", "20", "8,20", Empty = Auto detect (longer connecting time)
APNMobile operator APN (optional)*

1nce: iot.1nce.net

Vodafone Easy Connect: lpwa.vodafone.com (l = littel L)

PINSIM PIN (optional)
Empty or 4 digits (e.g. 1234)
DNSDNS Server9.9.9.9,1.1.1.1
UseNbiot
true
UseLtem
true

wMBus (experimental)

Name#LoRaWAN-RemoteConfigurationDescriptionDefault ValueValue Description & Examples
EncryptionMode775 or 7

Operation

Configuration values defining the behaviour of the device.

Name#LoRaWAN-RemoteConfigurationDescriptionDefault ValueValue Description & Examples
ObisCode1Comma separated list of ObisCodes to select a subset of the available information on port 1

ObisCode2Comma separated list of ObisCodes to select a subset of the available information on port 2

ObisCode3Comma separated list of ObisCodes to select a subset of the available information on port 3

ObisCode4Comma separated list of ObisCodes to select a subset of the available information on port 4

PayloadFormatFormat used for data upload (include timestamps or not)int1=no timestamp, 2=include timestamp
ReadCronCron expression defining when to read and upload0 0/15 * * * *0 0/15 * * * * for every 15 minutes
VerboseLoggingEnables additional Debug outputfalsetrue = 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/blue500 ms eachinitial pattern after reset



Payload (LoRaWAN)

Payload Format Status Packet (Port 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 byte1 byte1 byte1 byte2 byte2byte
unsignedunsignedunsignedunsignedunsigned

signed


The "Flags" field represents different error conditions by setting the individual bits:

0 (LSB)Parsing the obis code failed
1No incoming data from the opto head
2Brown Out
3None of the defined Obis Codes could be found in the incoming data
4-7Unused

Payload Format 1 (default, Port 3, with exponent)

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 bytes1 byten bytes, LSB first1 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 FE08FF 01 00 00 00 00 00 00ff
1-0:1.8.0*2548511-1
Value = 511 * 10^-1 = 51.1


Entry 2:

01 00 01 08 00 FE08FF 02 00 00 00 00 00 0002
1-0:1.8.0*25487672
Value = 767 * 10^2 = 76700


Payload Format 2 (extended, Port 4, with timestamp)

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 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).

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.


function readName(bytes, i) {
    return bytes.slice(i, i + 6);
}

function readValue(len, bytes, i) {
    if (len <= 0) {
        return [];
    }
    return bytes.slice(i, i + len);
}

function toHexString(byteArray) {
    var s = '';
    byteArray.forEach(function (byte) {
        s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
    });
    return s;
}

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
        t=t*-1;
    }
    return t;
}

function parse_uint16(bytes, idx) {
    bytes = bytes.slice(idx || 0);
    var t = bytes[0] << 8 | bytes[1] << 0;
    return t;
}


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
        val = val * -1;
    }
    return val;
}
function uint40_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 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) {
    var res = 0;

    for (var i = bytes.length-1; i >= 0 ; i--) {
        res *= 256;
        res += bytes[i];
    }
    
    return res;
}

function readVersion(bytes) {
    if (bytes.length<3) {
        return null;
    }
    return "v" + bytes[0] + "." + bytes[1] + "." + bytes[2];
}

function decodeStatus(bytes) {
    var decoded = {
        "version":readVersion(bytes),
        "flags": bytes[3],
        "vBat": uint16_BE(bytes, 4) / 1000,
        "temp": int16_BE(bytes, 6) / 10,
    };

    if (Device) {
        Device.setProperty("version", decoded.version);
        Device.setProperty("voltage", decoded.vBat);
        Device.setProperty("temperature", decoded.temp);
        Device.setProperty("status_flags", decoded.flags);
    }

    return decoded;
}

function decodeSmlValuesV1(bytes) {
    var decoded = {
        values: [],
    };

    if (bytes.length === 1) {
        // No Data! Read error?
        return decoded;
    }

    var pos = 0;
    while (pos < bytes.length) {
        var name = readName(bytes, pos);
        pos += 6;
        var len = bytes[pos];
        pos += 1;
        var value = readValue(len, bytes, pos);
        pos += len;

        var val = {
            nameHex: toHexString(name),
            len: len,
            value: toNumber(value),
            valueHex: toHexString(value)
        };

        decoded.values.push(val);
    }

    return decoded;
}

function decodeSmlValuesV2(bytes) {
    var decoded = {
        values: [],
    };

    if (bytes.length === 1) {
        // No Data! Read error?
        return decoded;
    }

    var pos = 0;
    var headNo = bytes[0];
    decoded.headNo = headNo;
    pos += 1;
    while (pos < bytes.length) {
        var name = readName(bytes, pos);
        pos += 6;
        var len = bytes[pos];
        pos += 1;
        var value = readValue(len, bytes, pos);
        pos += len;
        if (len > 0) {
            var exponent = int8(bytes, pos);
            pos += 1;
        }
        if (len > 0) {
            var unit = int8(bytes, pos);
            pos += 1;
        }
        var val;
        if (len > 0) {
            val = {
                obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5],
                //len: len,
                value: toNumber(value) * Math.pow(10, exponent),
                unit: toNumber(unit),
                //valueHex: toHexString(value),
            }
        } else {
            val = {
                obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5],
                //len: len,
                value: toNumber(value),
                unit: toNumber(unit),
                //valueHex: toHexString(value),
            }
        }

        decoded.values.push(val);
    }
    return decoded;
}

function decodeSmlValuesV3(bytes) {
      var decoded = {
        values: [],
    };

    if (bytes.length === 1) {
        // No Data! Read error?
        return decoded;
    }

    var pos = 0;
    var headNo = bytes[0];
    decoded.headNo = headNo;
    pos += 1;
    
    decoded.time = int40_BE(bytes, 1) * 1000;
    pos+=5;
    
    while (pos < bytes.length) {
        var name = readName(bytes, pos);
        pos += 6;
        var len = bytes[pos];
        pos += 1;
        var value = readValue(len, bytes, pos);
        pos += len;
        if (len > 0) {
            var exponent = int8(bytes, pos);
            pos += 1;
        }
        if (len > 0) {
            var unit = int8(bytes, pos);
            pos += 1;
        }
        var val;
        if (len > 0) {
            val = {
                obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5],
                //nameHex: toHexString(name),
                //len: len,
                value: toNumber(value) * Math.pow(10, exponent),
                //valueHex: toHexString(value),
                unit: toNumber(unit),
            }
        }
        else {
            val = {
                obiscode: name[0] + "-" + name[1] +":" + name[2] + "." + name[3] + "." +name[4] + "*" +name[5],
                //len: len,
                value: toNumber(value),
                //valueHex: toHexString(value),
                unit: toNumber(unit),
            }
        }

        decoded.values.push(val);
    }
    return decoded;
}

function decode_status_code(code) {
  switch (code) {
    case 0:
      return "OK";
    default:
      return "UNKNOWN";
  }
}

function decode_reboot_reason(code) {
  // STM reboot code from our HAL:
  switch (code) {
    case 1:
      return "LOW_POWER_RESET";
    case 2:
      return "WINDOW_WATCHDOG_RESET";
    case 3:
      return "INDEPENDENT_WATCHDOG_RESET";
    case 4:
      return "SOFTWARE_RESET";
    case 5:
      return "POWER_ON_RESET";
    case 6:
      return "EXTERNAL_RESET_PIN_RESET";
    case 7:
      return "OBL_RESET";
    default:
      return "UNKNOWN";
  }
}

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);
  
  return {
    "firmware": firmware,
    "version": version,
    "status_code": status_code,
    "status_text": status_text,
    "reboot_code": reboot_code,
    "reboot_reason": reboot_reason,
    "final_code": final_code,
    "temperature": temp,
    "voltage": vcc,
    "error_state": error_state
  };
}

function Decoder(bytes, port) {
    // Decode an uplink message from a buffer
    // (array) of bytes to an object of fields.
    switch (port) {
        case 1:
            return decodeStatus(bytes);
        case 2:
            return decodeSmlValuesV1(bytes);
        case 3:
            return decodeSmlValuesV2(bytes);
        case 4:
            return decodeSmlValuesV3(bytes);
        case 64: 
            return DecoderPort64(bytes);
    }
}

function NB_ParseDeviceQuery(input) {
    for (var key in input.d) {
        var v = input.d[key];
        switch (key) {
            case "temperature":
                v = v / 10.0;
                break;
            case "vbat":
                v = v / 1000.0;
                //NB_SetBatteryStatus(v)
                break;
        }
        Device.setProperty("device." + key, v);
    }
    return null;
}

function NB_ParseConfigQuery(input) {
    for (var key in input.d) {
        Device.setConfig(key, input.d[key]);
    }
    return null;
}

function ParseV2(input) {
  var decoded = {
        values: [],
    };
  
  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;
  var val;
  
  if ((input.d.batch) && (input.d.batch.length > 0) ) {
    for( i = 0; i<input.d.batch.length; i++){
      val = {
          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);
    }
  }
  return decoded;
}

function NB_ParseStatusQuery(input) {
  for (var key in input.d) {
    var v = input.d[key];
      switch (key) {
          case "temperature":
              v = v / 10.0;
              break;
          case "vbat":
              v = v / 1000.0;
              //NB_SetBatteryStatus(v)
              continue;
      }
      Device.setProperty("device." + key, v);
  }
  return null;
}


function ParseNBiot(input) {
  var query = input.q || "data";
  var res = null;
  switch (query) {
        case "device":
            res = NB_ParseDeviceQuery(input); 
            break;
        case "config":
            res = NB_ParseConfigQuery(input); 
            break;
        case "data":
            res = ParseV2(input); 
            break;
        case "status":
            res = NB_ParseStatusQuery(input); 
            break;
              
        default:
            throw new Error("Unknown message type: '" + query + "'");
  }
  
  if (res != null) {
    res.addr = input.i; 
    res.fCnt = input.n; 
  }
  return res;
}

// Wrapper for Lobaro Platform
function Parse(input) {
    // Decode an incoming message to an object of fields.
    var b = bytes(atob(input.data));
    
    // NB-IoT
    if (input.d) {
      return ParseNBiot(input)
    }
    
    // LoRaWAN
    return Decoder(b, input.fPort);
}



  • No labels