Skip to main content

Wireless M-Bus Gateway V4 with 4-20 mA Add-On

Description

Gateway for remote reading of wireless M-Bus meters and external sensors (4–20 mA and 1-Wire temperature sensors) via NB-IoT, LTE-M mobile radio or LoRaWAN to the Internet. Suitable for indoor and weather-protected outdoor installation.

In addition to all the features of the Wireless M-Bus Gateway V4, this variant provides:

  • 2 analogue 4–20 mA input channels for industrial sensors. Each channel is supplied with an extra 12V (1W) supply voltage for sensor supply. Connection with each 4-pin channel (I+, I-, 12V, GND) can be established over a 22 cm cable connection.

  • A 1-Wire interface for up to 20 temperature sensors (DS18B20). Connection with each 3-pin channel (3V, GND, DQ) can be established over a 22 cm cable connection.

Product Components

Component Info

This product is manufactured by Lobaro using the components listed below. Detailed information about the firmware and other components can be found in their respective descriptions. Please use the links provided to access comprehensive product details.

ComponentManual / DescriptionAdditional note
Firmwareapp-nrf91-originSee here for available firmware updates
HousingLoCube151 mm (l) x 125 mm (w) x 62 mm (h)
Battery38 Ah, includedLi-SOCl2 (3.6 V)

Firmware Updates

The latest firmware can be found here:

note

A remote firmware update over LoRaWAN is currently not possible.

Product Identification

  • Name: Gateway V4 + 4-20 mA
  • Type: LOB-GW4-420-1W
  • MPN / Ordering code: 1200588

Datasheet & Quickstart

info

Please contact our sales team.

Product specific details

LED signal patterns

The device has 3 three LEDs (B, G, R = Blue, Green, Red), labeled Status on the PCB: 🔵🟢🔴.

Patterns during booting/restart

  • 🔵⚪🔴 B and R on: Device is in Bootloader Mode (not actively running, remove config adapter press reset to leave Bootloader Mode).
  • 🔵->🟢->🔴 Quick cycle B, G, R for ~0.5s: Device just booted, either after power on, reset b button or software, or after a hard failure.
  • ⚪⚪🔴 R flashes repeatedly on and off in 1s interval: critical failure during boot (failed to start application).

Patterns during normal operation

  • 🔵⚪⚪ B flashes 1s on and 1s off in loop: building LTE connection to mobile provider. Followed by:
    • ⚪🟢⚪ Short G flash on success.
    • ⚪⚪🔴 Short R flash after connection failed.
  • ⚪🟢⚪ G flash during installation mode while collecting wireless M-Bus telegrams.

Exceptional patterns

  • 🔵⚪🔴/🔵🟢⚪ Quickly changing between R & B and G & B every 5s in a loop: Modem is in connection restriction mode - keep device on power, will fix itself after 30min.

Configuration

The device is shipped with default configuration parameters. The configuration can be changed via the 6-pin config port using the Lobaro USB Configuration Adapter. More information about the usage of the configuration tools can be found in our documentation.

info

Remote Configuration is also supported after initial network connection.

warning

Default Values are the firmware defaults. Depending on the application the device may be delivered with different settings.

General Parameters

DescriptionKeyTypePossible ValuesDefault
Uplink channel selectionWANStringlte or lorawanlte
Days without connectivity until device resetLostRebootNumberAny, e.g., 35
Detailed Description

WAN Parameter

LPWAN technology is used for connection and data communication with the backend server. This can be either cellular LTE (NB-IoT, LTE-M) or LoRaWAN.

  • lte: Utilizes cellular technologies, either NB-IoT or LTE-M.
  • lorawan: Employs LoRaWAN with OTAA (Over-The-Air Activation).
Limitations in LoRaWAN
  • LoRaWAN uplinks and downlinks are limited to 52–222 bytes, depending on the DataRate (connection quality):
  • Uplinks containing longer wMBUS telegrams are split across multiple LoRaWAN messages.
  • Uplinks with numerous wMBUS telegrams may take significant time due to LoRaWAN's duty-cycle limitations.
  • Downlinks with large configuration values (e.g., long whitelists) must be split into multiple messages, which can be challenging to implement.
  • Limited metadata:
  • Status telegrams include less information due to reduced uplink channel capacity.
  • No remote firmware update capabilities.

Wireless M-Bus Meter Reading

DescriptionKeyTypePossible ValuesDefault
WMBUS Listen Cron [UTC+0]listenCronStringAny CRON String0 0 12 * * *
WMBUS C1/T1 Listen Duration [s]cmodeDurSecNumberNumber of seconds
0 = Do not collect C1/T1
Max Value=36000
300
WMBUS S1 Listen Duration [s]smodeDurSecNumberNumber of seconds
0 = Do not collect S1
Max Value=36000
0
Sensus RF Listen Duration [s]xmodeDurSecNumberNumber of seconds
0 = Do not collect X-Mode
Max Value=36000
0
Müller-Funk Listen Duration [s]umodeDurSecNumberNumber of seconds
0 = Do not collect U-Mode
Max Value=36000
0
WMBUS ID Filter ListdevFilterStringList, e.g.
88009035,13456035
[not set]
WMBUS Type Filter ListtypFilterStringList, e.g.
08,07
[not set]
WMBUS M-Field Filter ListmFilterStringList, e.g.
DME,ITW,SEN,QDS
[not set]
WMBUS CI-Field Filter ListciFilterStringList, e.g.
8a,72
[not set]
WMBUS Telegram Upload LimitmaxTelegramsNumberAny number of max. Telegrams
0 = no limit.
0
Detailed Description

listenCron Parameter

The listen cron specifies when the device wakes up to receive data via enabled wireless M-Bus and other radio protocols. Each listening period is followed by data upload using the configured WAN technology. The ideal interval largely depends on the device's power supply (battery-powered vs. externally powered) and the application's need for new metering data. Typical intervals range from every 15 minutes to 14 days between consecutive readouts.

CRON parameter

*modeDurSec Parameters

Parameters: cmodeDurSec smodeDurSec xmodeDurSec umodeDurSec

The duration, specified in seconds (e.g., 300 for 5 minutes), defines how long the device collects metering data for each corresponding wireless protocol. Each listening period is executed sequentially based on the configured durations. Once all listening periods are complete, the collected meter telegrams are transmitted using the configured WAN technology.

warning

The maximum duration that can be specified is 10 hours, i.e. 36,000 seconds. Please note that such long listening periods will drain the battery very quickly. If in doubt, please consult Lobaro.


*Filter Parameters

Parameters: devFilter ciFilter mFilter typFilter

Filters are applied to meters based on wireless M-Bus-related fields to determine which telegrams are collected and uploaded. These filters include:

  • mFilter: Manufacturer filter – filters telegrams by the 3-letter manufacturer code included in every telegram ( e.g., LOB for Lobaro GmbH).
  • typFilter: Device Type filter – filters telegrams by the 2-hex-digit code that defines the type of sending device (e.g., 07 for water meters).
  • devFilter: Device filter – filters telegrams by the 8-digit ID that is mandatory for each sending device (e.g., 87654321).
  • ciFilter: CI-Field filter – filters telegrams by the 2-hex-digit CI-Field included in every telegram. This field is a technical code that describes the purpose of the telegram (e.g., 8a).

These filters help refine data collection to target specific devices or device types.

More Details

For a detailed explanation, read more about telegram filter parameterization.

Blacklist

Each filter parameter can be preceded with an exclamation mark (!) to convert the whitelist into a blacklist.

This applies to the entire filter parameter, not to individual entries within the list. For example:

  • mFilter=LOB will collect only telegrams from Lobaro GmbH (whitelist).
  • mFilter=!LOB will exclude telegrams from Lobaro GmbH (blacklist).

maxTelegrams Parameter

Set a hard limit on the number of telegrams to be collected and uploaded. The firmware will stop collecting once this limit is reached, regardless of the elapsed time. This can help conserve battery life and data volume, especially if the device is located in an area with a high density of meters.

tip

When filtering for a single telegram, the parameter can be set to 1, causing the device to stop listening immediately after the desired telegram is received.

LTE Connection

DescriptionKeyTypePossible ValuesDefault
LTE Lobaro Platform HostHostIP / URLList of various Endpointscoaps://platform.lobaro.com
LTE MCC+MNC CodeOperatorNumbere.g. 26201 (Dt. Telekom)[not set]
LTE BandBandNumber3 or 8,20 or 3,8,203,8,20
LTE APNAPNStringany APN*
LTE SIM PinPINNumber4 digits pin, e.g. 1234[not set]
LTE NB-IoT on/offUseNbiotBooltrue or falsetrue
LTE M1 on/offUseLtemBooltrue or falsetrue
LTE DNS Servers usedDNSIPList of DNS server IPs9.9.9.9,1.1.1.1
Plain UDP HostUdpHostIPany, e.g 94.130.20.37[not set]
Plain UDP PortUdpPortNumberany, e.g 3333[not set]
Detailed Description

Host Parameter

Hostname or IP of the Lobaro Platform instance CoAP endpoint to which the gateway communicates using UDP.

  • Using DTLS: coaps://platform.lobaro.com
  • No DTLS: coap://platform.lobaro.com
  • Plain IP: 94.130.20.37 (platform.lobaro.com)
Host with fallback

It's also possible to configure a list of URLs to implement a fallback mechanism. This is particulary helpful for combining DTLS and non DTLS connection attempts: coaps://platform.lobaro.com,coap://platform.lobaro.com


LTE Parameters

Parameters: APN Operator Band, PIN

Basic parameters are required to configure the NB-IoT or LTE-M connection. These settings must align with the SIM card and network provider used. Typically, the default parameters work well, as they enable the modem to automatically select and join the network using SIM card information. However, if the APN (Access Point Name) is known, it is recommended to configure it for optimal performance.

More Details

Read more about LTE network configuration parameters.


UseNbiot UseLtem

The modem supports both NB-IoT and LTE-M technologies. By default, both are enabled, allowing the cellular modem to automatically select the most suitable network type based on the location. However, you can force the modem to use a specific technology by setting one parameter to false, ensuring the other remains enabled (set to true). At least one technology must always be active.


UdpHost UdpPort

Deprecated

This feature is deprecated and not recommended for production use. Lobaro provides limited support for errors caused by using raw UDP upload.

Instead of sending metering data to the Lobaro IoT Platform, the data can alternatively be sent to an external UDP socket. This option is useful if you prefer to keep metering data off external servers while still using the Lobaro Platform to control your gateways.

  • UdpHost: IP address to upload plain telegrams via UDP.
  • [not set]: Defaults to uploading data to the Lobaro IoT Platform using the configured Host parameter.
  • UdpPort: Port number to upload plain telegrams via UDP.
  • Only used if UdpHost is set.
info

Even when metering data is sent to an external server, the firmware typically still requires a connection to a Lobaro Platform instance to send status information, perform remote configurations, or handle firmware updates.

For solutions that allow all communication without the Lobaro Platform, such as direct MQTT to an external broker, contact Lobaro for potential options.

LoRaWAN Connection

DescriptionKeyTypePossible ValuesDefault
DevEUIDevEUIbyte[8]anyDevice EUI64
AppEUI / JoinEUI (1.1)AppEUI/JoinEUIbyte[8]anyrandom
AppKeyAppKeybyte[16]anyrandom
NwkKey (1.1)NwkKeybyte[16]any00000000000000000000000000000000
Days between TimesyncTimeSyncNumberany3
Payload FormatPayloadFormatNumber0, 1, 20
Use OTAAOTAABooltrue or falsetrue
Random TX Delay [s]RndDelayNumberany10
Spreading FactorSFNumber7-1212
Transmission PowerTxPowerNumber2-1414
Adaptiv Data RateADRBooltrue or falsetrue
LoRaWAN max. Payload LengthloraPLMaxNumber10 to 241100
Detailed Description
LoRaWAN 1.1

LoRaWAN 1.1 is experimental and not certified. For production environments, we recommend using LoRaWAN 1.0.2.

note

Depending on configuration some parameters are not available.

NwkKey Parameter

The Network Key (NwkKey) is used for LoRaWAN 1.1. If set to zeros (00000000000000000000000000000000) or to the same value as the AppKey, the device will stick to LoRaWAN 1.0.2 which is the recommended mode of operation.

PayloadFormat Parameter

Used encoding of the wmBus LoRaWAN uplink payload packets.

  • 0 = Encoding in ports with static message length
  • 1 = prefix bytes and time
  • 2 = prefix bytes, time, and rssi
More Details

For a detailed specification of the payload formats, please refer to the LoRaWAN Communication page.

Special

DescriptionKeyTypePossible ValuesDefault
Verbose UART LogverboseBooltrue or falsefalse
Addon RAM configurationextRamStringLobaro Internal[not set]
Live ModeliveModeString[not set]
Operation ModeopModeNumberLobaro Internal1
Detailed Description

liveMode Parameter

An empty string disables the live mode. suppression=<seconds> enable live mode with suppression of duplicate telegrams for seconds

Analog Channel Parameters

DescriptionKeyTypePossible ValuesDefault
Enable measurement on 4–20 mA input channel 1 (CH1)ch1EnableBooltrue / false
false = channel disabled
true
Number of seconds to wait for the CH1 sensor to get ready after power onch1PreReadSecNumberSeconds (integer)
0…
1
Number samples to take for a single CH1 measurementch1SamplesNumber1-1000
200
Scaling factor applied to the raw CH1 valuech1FactorNumberNumeric (float)
e.g. 1.0, 0.1
1
Offset added to the CH1 value after scalingch1OffsetNumberNumeric (float)
e.g. 0, -5.0
0
High alarm threshold for CH1 measurement valuech1AlarmHighNumberNumeric (float)
empty = disabled
[not set]
Low alarm threshold for CH1 measurement valuech1AlarmLowNumberNumeric (float)
empty = disabled
[not set]
Enable measurement on 4–20 mA input channel 2 (CH2)ch2EnableBooltrue / false
false = channel disabled
true
Number of seconds to wait for the CH2 sensor to get ready after power-onch2PreReadSecNumberSeconds (integer)
0…
1
Number of samples taken for a single CH2 measurementch2SamplesNumber1-1000
200
Scaling factor applied to the raw CH2 valuech2FactorNumberNumeric (float)1
Offset added to the CH2 value after scalingch2OffsetNumberNumeric (float)0
High alarm threshold for CH2 measurement valuech2AlarmHighNumberNumeric (float)
empty = disabled
[not set]
Low alarm threshold for CH2 measurement valuech2AlarmLowNumberNumeric (float)
empty = disabled
[not set]
Cron schedule used while an alarm is active, overriding the normal device cronalarmCronStringCron expression (string)
empty = no override
[not set]
Number of seconds CH1 stays powered after first power-on or brownoutch1PwrAfterBrownoutDurIntegerSeconds (integer)
0 = disabled
0
Detailed Description

chXFactor and chXOffset Parameters

The measured raw value is multiplied by chXFactor and then chXOffset is added. All further calculations and alarm checks are done with this derived value.


chXAlarmLow and chXAlarmHigh Parameters

If the calculated measurement value is above chXAlarmHigh or below chXAlarmLow, an alarm is triggered. The number of triggered alarms and the time in the alarm state is measured and transmitted.


alarmCron Parameter

In case of an active alarm, this cron is used instead of the regular cron. If empty, the regular sensor cron will be used.

Upload Formats LoRaWAN

note

Communication with this gateway version via LoRaWAN is currently not possible.

Upload Formats LTE

CoAP Protocol

The protocol details of the CoAP implementation are not publicly disclosed and are intended for use exclusively with the Lobaro IoT Platform. For integration with third-party systems, MQTT is the preferred protocol.

If you require access to this and MQTT is not an option, please contact Lobaro directly.

MQTT Protocol

info

Please contact our sales team.

Example JS Parser

Lobaro Platform / TheThingsNetwork (TTN) / ChirpStack
// Version 0.0.2
// Source: github.com/lobaro-parsers-private/lobaro/lob-gw4-420-1w.js
//
// Changelog
//
// v0.0.2 - 2025-04-28
// - Add gwImei field to 4-20 mA parsed data

////////////////////////////
// Helper
////////////////////////////

function now() {
return typeof (nowMs) == 'function' ? nowMs() : new Date().getTime();
}

/**
* Trim leading zeros from a string
* @param {string} str Input string, e.g. '00096117836alt'
* @returns {string} Input tring with leading zeros removed, e.g. '96117836alt'
*/
function trimLeadingZeros(str) {
for (var i = 0; i < str.length; i += 1) {
if (str[i] !== '0') {
return str.slice(i);
}
}
return str;
}

////////////////////////////
// START - Status Functions
////////////////////////////

var receivedMetersCached = null;

function Status_GetReceivedMeters() {
if (receivedMetersCached !== null) {
return receivedMetersCached;
}
var received = Device.getProperty("platform.wmbus.receivedMeters") || [];
if (received) {
return received;
}
return [];
}

// Add a single id to the list if not exists
function Status_AddReceivedMeterIdIfNotExists(mbusId) {
var received = Status_GetReceivedMeters();

if (received.indexOf(mbusId) >= 0) {
// Already in list
return;
}
received = [mbusId].concat(received)

// Update cache and actual properties
receivedMetersCached = received;
Device.setProperty("platform.wmbus.receivedMeters", received);
Device.setProperty("platform.wmbus.receivedMetersCount", received.length);
}

// Add a single meter with multiple IDs
// idLL = LinkLayer
// idAL = ApplicationLayer
function Status_AddReceivedMeter(idLL, idAL) {
idLL = trimLeadingZeros(idLL);
if (!idLL) return;

if (idAL !== undefined) {
idAL = trimLeadingZeros(idAL);
if (!idAL) return;
}

// Only add expected meters
var expected = Status_GetExpectedMeters();

if (expected.length === 0) {
// If we expect nothing, just add the Link Layer ID to the list to avoid double count of one meter
Status_AddReceivedMeterIdIfNotExists(idLL);
}

var idLLExpected = expected.some(function (x) {
return x === idLL;
});
if (idLLExpected) {
Status_AddReceivedMeterIdIfNotExists(idLL);
}

var idALExpected = expected.some(function (x) {
return x === idAL;
});
if (idALExpected) {
Status_AddReceivedMeterIdIfNotExists(idAL);
}
}

function Status_ClearReceivedMeters() {
var received = [];
receivedMetersCached = received;
Device.setProperty("platform.wmbus.receivedMeters", received);
Device.setProperty("platform.wmbus.receivedMetersCount", received.length);
}

function Status_GetWhitelist(input) {
var whiteList = "";
if (input && input.q === "config") {
whiteList = input.d.devFilter
} else {
whiteList = Device.getConfig("devFilter");
}
return whiteList;
}

function Status_GetExpectedMeters(input) {
var whiteList = Status_GetWhitelist(input);

if (whiteList) {
return whiteList.split(",").map(trimLeadingZeros);
}
return [];
}

function Status_UpdateExpectedMeters(input) {
var expectedMeters = Status_GetExpectedMeters(input);
Device.setProperty("platform.wmbus.expectedMeters", expectedMeters);
Device.setProperty("platform.wmbus.expectedMetersCount", expectedMeters.length);
}


function Status_UpdateMissingMeters(input) {
var expected = Status_GetExpectedMeters(input);
var received = Status_GetReceivedMeters();

var missingMeters = expected
.map(trimLeadingZeros)
.filter(function (x) {
// Remove all that are not in received meters
return received.indexOf(x) === -1
});


Device.setProperty("platform.wmbus.missingMeters", missingMeters);
Device.setProperty("platform.wmbus.missingMetersCount", missingMeters.length);
}

function Status_UpdateStautusDate(date) {
Device.setProperty("platform.wmbus.statusDate", date.toISOString());
}

// Handle a single meter with multiple IDs
// idLL = LinkLayer
// idAL = ApplicationLayer
function Status_HandleReceivedMeterById(idLL, idAL) {
Status_AddReceivedMeter(idLL, idAL);
Status_UpdateMissingMeters(null);
}

function Status_ResetWmbusMetersStatistics(input) {
Status_ClearReceivedMeters();
Status_UpdateMissingMeters(input);

var statusDate = new Date(now());
if (input && input.d && input.d.time) {
statusDate = new Date(input.d.time * 1000);
}
Status_UpdateStautusDate(statusDate);
}

////////////////////////////
// END - Status Functions
////////////////////////////


function NB_SetBatteryStatus(vbat) {
Device.setProperty("device.vbat", vbat);
Device.setProperty("platform.powerType", "battery");
//powerStatus Score:
if (vbat > 3.4) {
Device.setProperty("platform.powerStatus", 9);
} else if (vbat > 3.2) {
Device.setProperty("platform.powerStatus", 5);
} else {
Device.setProperty("platform.powerStatus", 2);
}
}


function NB_ESM_reason(esm) {

switch (esm) {
case 0:
return "-";
case 255:
return "-";
case parseInt("00001000", 2):
return "Operator Determined Barring" + " (" + esm + ")";
case parseInt("00011010", 2):
return "Insufficient resources" + " (" + esm + ")";
case parseInt("00011011", 2):
return "Missing or unknown APN" + " (" + esm + ")";
case parseInt("00011100", 2):
return "Unknown PDN type" + " (" + esm + ")";
case parseInt("00011101", 2):
return "User authentication failed" + " (" + esm + ")";
case parseInt("00011110", 2):
return "Request rejected by Serving GW or PDN GW" + " (" + esm + ")";
case parseInt("00011111", 2):
return "Request rejected, unspecified" + " (" + esm + ")";
case parseInt("00100000", 2):
return "Service option not supported" + " (" + esm + ")";
case parseInt("00100001", 2):
return "Requested service option not subscribed" + " (" + esm + ")";
case parseInt("00100010", 2):
return "Service option temporally out of order" + " (" + esm + ")";
case parseInt("00100011", 2):
return "PTI already in use" + " (" + esm + ")";
case parseInt("00100100", 2):
return "Regular deactivation" + " (" + esm + ")";
case parseInt("00100101", 2):
return "EPS QoS not accepted" + " (" + esm + ")";
case parseInt("00100110", 2):
return "Network failure" + " (" + esm + ")";
case parseInt("00100111", 2):
return "Reactivation requested" + " (" + esm + ")";
case parseInt("00101001", 2):
return "Semantic error in the TFT operation" + " (" + esm + ")";
case parseInt("00101010", 2):
return "Syntactical error in the TFT operation" + " (" + esm + ")";
case parseInt("00101011", 2):
return "Invalid EPS bearer identity" + " (" + esm + ")";
case parseInt("00101100", 2):
return "Semantic errors in packet filters" + " (" + esm + ")";
case parseInt("00101101", 2):
return "Syntactical errors in packet filters" + " (" + esm + ")";
case parseInt("00101110", 2):
return "Unused, protocol error, unspecified" + " (" + esm + ")";
case parseInt("00101111", 2):
return "PTI mismatch" + " (" + esm + ")";
case parseInt("00110001", 2):
return "Last PDN disconnection not allowed" + " (" + esm + ")";
case parseInt("00110010", 2):
return "PDN type IPv4 only allowed" + " (" + esm + ")";
case parseInt("00110011", 2):
return "PDN type IPv6 only allowed" + " (" + esm + ")";
case parseInt("00111001", 2):
return "PDN type IPv4v6 only allowed" + " (" + esm + ")";
case parseInt("00111010", 2):
return "PDN type non IP only allowed" + " (" + esm + ")";
case parseInt("00110100", 2):
return "Single address bearers only allowed" + " (" + esm + ")";
case parseInt("00110101", 2):
return "ESM information not received" + " (" + esm + ")";
case parseInt("00110110", 2):
return "PDN connection does not exist" + " (" + esm + ")";
case parseInt("00110111", 2):
return "Multiple PDN connections for a given APN not allowed" + " (" + esm + ")";
case parseInt("00111000", 2):
return "Collision with network initiated request" + " (" + esm + ")";
case parseInt("00111011", 2):
return "Unsupported QCI value" + " (" + esm + ")";
case parseInt("00111100", 2):
return "Bearer handling not supported" + " (" + esm + ")";
case parseInt("01000001", 2):
return "Maximum number of EPS bearers reached" + " (" + esm + ")";
case parseInt("01000010", 2):
return "Requested APN not supported in current RAT and PLMN combination" + " (" + esm + ")";
case parseInt("01010001", 2):
return "Invalid PTI value" + " (" + esm + ")";
case parseInt("01011111", 2):
return "Semantically incorrect message" + " (" + esm + ")";
case parseInt("01100000", 2):
return "Invalid mandatory information" + " (" + esm + ")";
case parseInt("01100001", 2):
return "Message type non-existent or not implemented" + " (" + esm + ")";
case parseInt("01100010", 2):
return "Message type not compatible with the protocol state" + " (" + esm + ")";
case parseInt("01100011", 2):
return "Information element non-existent or not implemented" + " (" + esm + ")";
case parseInt("01100100", 2):
return "Conditional IE error" + " (" + esm + ")";
case parseInt("01100101", 2):
return "Message not compatible with the protocol state" + " (" + esm + ")";
case parseInt("01101111", 2):
return "Protocol error, unspecified" + " (" + esm + ")";
case parseInt("01110000", 2):
return "APN restriction value incompatible with active EPS bearer context" + " (" + esm + ")";
case parseInt("01110001", 2):
return "Multiple accesses to a PDN connection not allowed" + " (" + esm + ")";
default:
return "esm unknown value" + " (" + esm + ")";
}

}

function NB_EMM_reason(emm) {

switch (emm) {
case 0:
return "-";
case 255:
return "-";
case parseInt("00000010", 2):
return "IMSI unknown in HSS" + " (" + emm + ")";
case parseInt("00000011", 2):
return "Illegal UE" + " (" + emm + ")";
case parseInt("00000101", 2):
return "IMEI not accepted" + " (" + emm + ")";
case parseInt("00000110", 2):
return "Illegal ME" + " (" + emm + ")";
case parseInt("00000111", 2):
return "EPS services not allowed" + " (" + emm + ")";
case parseInt("00001000", 2):
return "EPS services and non-EPS services not allowed" + " (" + emm + ")";
case parseInt("00001001", 2):
return "UE identity cannot be derived by the network" + " (" + emm + ")";
case parseInt("00001010", 2):
return "Implicitly detached" + " (" + emm + ")";
case parseInt("00001011", 2):
return "PLMN not allowed" + " (" + emm + ")";
case parseInt("00001100", 2):
return "Tracking Area not allowed" + " (" + emm + ")";
case parseInt("00001101", 2):
return "Romaing not allowed in this Tracking Area" + " (" + emm + ")";
case parseInt("00001110", 2):
return "EPS services not allowed in this tracking area" + " (" + emm + ")";
case parseInt("00001111", 2):
return "No Suitable Cells in tracking area" + " (" + emm + ")";
case parseInt("00010000", 2):
return "MSC temporarily not reachable" + " (" + emm + ")";
case parseInt("00010001", 2):
return "Network failure" + " (" + emm + ")";
case parseInt("00010010", 2):
return "CS domain not available" + " (" + emm + ")";
case parseInt("00010011", 2):
return "ESM failure" + " (" + emm + ")";
case parseInt("00010100", 2):
return "MAC failure" + " (" + emm + ")";
case parseInt("00010101", 2):
return "Synch failure" + " (" + emm + ")";
case parseInt("00010110", 2):
return "Congestion" + " (" + emm + ")";
case parseInt("00010111", 2):
return "UE security capabilities mismatch" + " (" + emm + ")";
case parseInt("00011000", 2):
return "Security mode rejected, unspecified" + " (" + emm + ")";
case parseInt("00011001", 2):
return "Not authorized for this CSG" + " (" + emm + ")";
case parseInt("00011010", 2):
return "Non-EPS authentication unacceptable" + " (" + emm + ")";
case parseInt("00100011", 2):
return "Requested service option not authorized in this PLMN" + " (" + emm + ")";
case parseInt("00100111", 2):
return "CS service temporarily not available" + " (" + emm + ")";
case parseInt("00101000", 2):
return "No EPS bearer context activated" + " (" + emm + ")";
case parseInt("00101010", 2):
return "Severe network failure" + " (" + emm + ")";
case parseInt("01011111", 2):
return "Semantically incorrect message" + " (" + emm + ")";
case parseInt("01100000", 2):
return "Invalid mandatory information" + " (" + emm + ")";
case parseInt("01100001", 2):
return "Message type non-existent or not implemented" + " (" + emm + ")";
case parseInt("01100010", 2):
return "Message type not compatible with the protocol state" + " (" + emm + ")";
case parseInt("01100011", 2):
return "Information element non-existent or not implemented" + " (" + emm + ")";
case parseInt("01100100", 2):
return "Conditional IE error" + " (" + emm + ")";
case parseInt("01100101", 2):
return "Message not compatible with the protocol state" + " (" + emm + ")";
case parseInt("01101111", 2):
return "Protocol error, unspecified" + " (" + emm + ")";
default:
return "EMM unknown" + " (" + emm + ")";
}
}

// TODO: The statistics are accessing "old" data via getProperty. The prser must use the current values from input.d
function NB_UpdateDerivedStatistics(input) {
var count = Device.getProperty("device.txCount");
if (count) {
var rsrp = Device.getProperty("device.txRsrp") || 0;
Device.setProperty("platform.avgRsrp", (-1 * rsrp / count).toFixed(2));
var rsrq = Device.getProperty("device.txRsrq") || 0;
Device.setProperty("platform.avgRsrq", (-1 * rsrq / count).toFixed(2));
}
var rsrpRaw = Device.getProperty("device.rsrp");
if (rsrpRaw !== undefined) {
Device.setProperty("platform.rsrp", rsrpRaw > 97 ? -100 : rsrpRaw - 140);
}
var rsrqRaw = Device.getProperty("device.rsrq");
if (rsrqRaw !== undefined) {
Device.setProperty("platform.rsrq", rsrqRaw > 34 ? -10 : (rsrqRaw - 39) / 2.0);
}

var awakeSecs = Device.getProperty("device.awakeTime");
var sleepSecs = Device.getProperty("device.sleepTime");

if (awakeSecs && sleepSecs) {
Device.setProperty("platform.sleepRatioPercent", parseFloat((1 - (sleepSecs / (awakeSecs + sleepSecs))) * 100).toFixed(2));
}

// extract sector and tower id from cell-id
// maybe used for lookup with online lte cell map services like www.cellmapper.net
var cellID = Device.getProperty("device.ci");
if (cellID) {
var ci = parseInt(cellID, 16);
var towerID = parseInt(ci / 256, 10);
Device.setProperty("platform.eNodeB.towerID", towerID);
var sector = ci & 255
Device.setProperty("platform.eNodeB.sectorID", sector);
}
}

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)
continue;
case "EMM":
Device.setProperty("device.EMM", NB_EMM_reason(v));
continue;
case "EMM_r":
Device.setProperty("device.EMM_r", NB_EMM_reason(v));
continue;
case "ESM":
Device.setProperty("device.ESM", NB_ESM_reason(v));
continue;
case "ESM_r":
Device.setProperty("device.ESM_r", NB_ESM_reason(v));
continue;
case "hFree":
Device.setProperty("platform.device.heapAllocs", input.d.hAlloc - input.d.hFree);
Device.setProperty("device." + key, v);
continue;
}
Device.setProperty("device." + key, v);
}
NB_UpdateDerivedStatistics(input);
return null;
}

function NB_ExtractMainWmbusValue(wmbus) {

// Generic
if (wmbus.Body && wmbus.Body.DataRecords) {
var len = wmbus.Body.DataRecords.length;
for (var i = 0; i < len; i++) {
if (wmbus.Body.DataRecords[i].StorageNo === 0
&& wmbus.Body.DataRecords[i].VifQuantity !== "Time & Date"
&& wmbus.Body.DataRecords[i].VifQuantity !== "Date"
&& wmbus.Body.DataRecords[i].VifQuantity !== "Averaging Duration"
&& wmbus.Body.DataRecords[i].DifFunctionString === "Current Value"
&& wmbus.Body.DataRecords[i].VifUnit
&& wmbus.Body.DataRecords[i].VifUnit !== "s") {
return wmbus.Body.DataRecords[i].ValueString + " " + wmbus.Body.DataRecords[i].VifUnit;
}
}
}

return undefined;
}

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

function NB_ExtractStatus(input) {
if (input.d) {
var d = input.d;
if (d.vbat) {
var vbat = d.vbat / 1000.0
NB_SetBatteryStatus(vbat)
}
if (d.monitor) {
Device.setProperty("debug.monitor", d.monitor);
}
if (d.temperature) {
Device.setProperty("device.temperature", d.temperature / 10.0);
}
}
}


// NB_UpdateDeviceStatus takes any upload and updates the device status for the customer
// The device status shows:
// "Empfangs Status" = (platform.wmbus.receivedMetersCount / platform.wmbus.expectedMetersCount) (e.g. = "1/20")
// Sort Property for the column: "platform.wmbus.missingMetersCount"
// "Status Datum" = platform.wmbus.receivedMetersCount.updatedAt // Date of last wMbus Telegram (or empty upload)
function NB_UpdateDeviceStatus(input) {

// TODO: Write test for install telegram
if (input && input.q === "install") {
if (input.d.telegrams === input.d.uploading) {
Status_UpdateExpectedMeters(input);
Status_ResetWmbusMetersStatistics(input);
}
}

if (input && input.q === "status") {
// Pre Upload Status (collected = true && telegrams == uploading) - also with uploading == 0
if (input.d && input.d.collected && input.d.telegrams === input.d.uploading) {
Status_UpdateExpectedMeters(input);
Status_ResetWmbusMetersStatistics(input);
}
// Nightly Upload (collected = false && telegrams == uploading && uploading != 0)
// Nightly Upload with uploading == 0 is the normal one,
// Reset stats only if no telegrams are uploaded already (telegrams == uploading && uploading != 0)
else if (input.d && input.d.telegrams === input.d.uploading && input.d.uploading !== 0) {
Status_UpdateExpectedMeters(input);
Status_ResetWmbusMetersStatistics(input);
}
}

Status_UpdateMissingMeters(input);
}

function NB_ParseTelegram(input, item) {

var wmbus = Parser.parseWmbus(parseBase64(item.telegram));
var parsed = {
"mainValue": NB_ExtractMainWmbusValue(wmbus),
"mbus": wmbus,
"time": (item.timestamp || 0) * 1000,
"rssi": item.rssi,
"gwImei": input.i,
};

var oldLastReadout = Device.getProperty("platform.wmbus.lastReadout")
var newLastReadout = new Date(item.timestamp * 1000 || now())
if (!oldLastReadout || new Date(oldLastReadout) < newLastReadout) {
Device.setProperty("platform.wmbus.lastReadout", newLastReadout.toISOString());
}

var idLL = undefined;
var idAL = undefined;
if (wmbus && wmbus.IdString) {
idLL = wmbus.IdString;
if (wmbus.Header && wmbus.Header.IdString) {
idAL = wmbus.Header.IdString;
}
Status_UpdateStautusDate(newLastReadout);
Status_HandleReceivedMeterById(idLL, idAL);
}


return parsed;
}

function NB_ParseDataQuery(input) {
NB_ExtractStatus(input);

Record.setTimeField("time", "UNIXMS");

// batch upload
if (input.d.batch) {
var d = input["d"] || {};
var batch = d["batch"] || [];
var out = [];
batch.forEach(function (item, index) {
var t = NB_ParseTelegram(input, item);
out.push(t);
});

return out;
} else if (input.d.telegram) {
return NB_ParseTelegram(input, input.d);
}

return null;
}

function NB_Parse420Entry(input, item) {
item.gwImei = input.i;
return item;
}

function NB_Parse420Query(input) {
Record.setTimeField("time", "UNIXS");

// batch upload
if (input.d.batch) {
var d = input["d"] || {};
var batch = d["batch"] || [];
var out = [];
batch.forEach(function (item, index) {
var t = NB_Parse420Entry(input, item);
out.push(t);
});

return out;
} else if (input.d.telegram) {
return NB_Parse420Entry(input, input.d);
}

return null;
}

function NB_ParseInstallQuery(input) {
NB_UpdateDeviceStatus(input)
}

function NB_ParseStatusQuery(input) {
// Device Query and Status Query are handled the same here
NB_ParseDeviceQuery(input);

// Update the device status regarding the device table config values
NB_UpdateDeviceStatus(input);
}

function NB_Parse(input) {
var query = input.q || "data";
switch (query) {
case "device":
return NB_ParseDeviceQuery(input);
case "install":
return NB_ParseInstallQuery(input);
case "config":
return NB_ParseConfigQuery(input);
case "data":
return NB_ParseDataQuery(input);
case "420":
return NB_Parse420Query(input);
case "status":
return NB_ParseStatusQuery(input);
default:
return null;
// Führt nur zu Verwirrung
//throw new Error("Unknown message type: '" + query + "'");
}
}

function LW_Parse(input) {
// Decode an incoming message to an object of fields.
var bytes = parseBase64(input.data);
var port = input.fPort;
var decoded = null;


// TODO: LoRaWAN Parser not implemented

return decoded;
}

function Parse(input) {
if (input.i) {
//IMEI = nb iot input
return NB_Parse(input)
} else if (input.devEUI) {
//devEUI = lorawan input
return LW_Parse(input)
} else {
throw new Error("Neither NB IoT nor LORWAN Msg detected.");
}

}

Declaration of Conformity

Download CE declaration of conformity