Skip to main content

Manual v0.3.x

Modbus LoRaWAN Bridge v0.3.x

Target Measurement and Purpose

The Lobaro Modbus LoRaWAN Bridge is a low-power device for reading values from external devices via Modbus ASCII or Modbus RTU over an RS-485 interface.

The collected data is transmitted via LoRaWAN and can be processed by an attached backend system.

Typical applications include:

  • Reading electricity meters
  • Reading water meters
  • Retrieving temperature and humidity measurements
  • Reading industrial sensors
  • Connecting building-management equipment

Modbus LoRaWAN system overview

The Modbus Bridge supports all four standard Modbus object types:

  • Coils
  • Discrete Inputs
  • Holding Registers
  • Input Registers

Multiple Modbus devices on the same RS-485 bus can be accessed by one Bridge. Readout intervals and register definitions can be configured according to the installation requirements.

Datasheet

The legacy technical datasheet can be provided as a downloadable PDF:

Download the Modbus LoRaWAN datasheet

Work Cycle

The Modbus LoRaWAN Bridge spends most of its operating time in deep sleep to conserve energy.

For each scheduled readout, the Bridge:

  1. Wakes up.
  2. Reads the configured Modbus devices.
  3. Uploads the collected data via LoRaWAN.
  4. Returns to deep sleep.

Modbus Bridge work cycle

Initialization

When the device starts after connecting a power supply or after a reboot, it enters the initialization state.

During initialization:

  • A short self-test is performed.
  • A successful self-test is indicated by one slow green LED flash.
  • The configuration is parsed and validated.
  • Invalid configuration values cause the LED to flash three times.
  • The device then reboots and repeats the configuration check.

The device does not continue normal operation until the configuration is valid.

Configuration

The initial configuration is performed using:

  • Lobaro Maintenance Tool
  • Lobaro USB Configuration Adapter

LoRaWAN Parameters

The LoRaWAN parameters must match the target LoRaWAN network.

The firmware supports:

  • Over-the-Air Activation, OTAA
  • Activation by Personalization, ABP

Byte-array values are entered as hexadecimal strings without a 0x prefix.

For example, an eight-byte DevEUI is represented by 16 hexadecimal characters:

0123456789abcdef

NameActivationTypeDescription
OTAABothbooltrue uses OTAA; false uses ABP
DevEUIOTAAhexbyte[8]Unique device identifier used during the join operation
AppEUIOTAAhexbyte[8]Identifier of the application in the LoRaWAN network
AppKeyOTAAhexbyte[16]Key used for LoRaWAN network activation
AppSKeyABPhexbyte[16]Application Session Key
NetSKeyABPhexbyte[16]Network Session Key
DevAdrABPhexbyte[4]Device address used by the LoRaWAN network
SFBothintInitial spreading factor from 7 to 12
ADRBothboolEnables or disables Adaptive Data Rate

The factory-defined DevEUI is globally unique and should normally not be changed.

Modbus and UART Parameters

The serial configuration must match the connected Modbus devices.

Refer to the documentation of the attached Modbus devices when selecting these values.

NameDescriptionExample values
ModbusProtocolModbus protocolRTU, ASCII
ModbusBaudUART baud rate9600, 19200, 38400
ModbusDataLendthUART data length7, 8, 9
ModbusStopBitsUART stop bits0.5, 1, 1.5, 2
ModbusParityUART parityNONE, EVEN, ODD

Operation

NameDescriptionExample
ModbusCronCron expression defining when the Modbus devices are read0 0/15 * * * * for every 15 minutes

For more information, see the Cron configuration.

Register and Coil Definitions

The firmware provides one configuration value for each Modbus object type.

ConfigurationModbus objectFunction code
CoilsOne-bit read/write values0x01
DiscreteInputsOne-bit read-only values0x02
HoldingRegisters16-bit read/write registers0x03
InputRegisters16-bit read-only registers0x04

Each individual definition consists of three values separated by colons:

device-address:start-address:count

The values have the following meaning:

  1. Device address Modbus address of the device to read. Valid addresses range from 1 to 247.

  2. Start address Address of the first coil or register. Valid values range from 0 to 65535.

  3. Count Number of consecutive coils or registers to read. The maximum supported value is 127.

Multiple definitions are separated by commas without spaces:

1:0:3,2:100:5

Unused object types should be configured as an empty string.

Invalid definitions prevent normal device operation. The Bridge reports the invalid configuration and reboots until the configuration is corrected.

Examples

DefinitionMeaning
1:0:3Read the first three coils or registers from device 1
2:40001:1,2:2000:10Read address 40001 and addresses 2000–2009 from device 2
4:0:2,5:0:2,5:20:1Read addresses 0–1 from device 4, and addresses 0–1 and 20 from device 5

Payload Formats

The Modbus Bridge uses different LoRaWAN ports for the different message types.

PortMessage
1Device status message
11Coil data, Modbus function 0x01
12Discrete Input data, Modbus function 0x02
13Holding Register data, Modbus function 0x03
14Input Register data, Modbus function 0x04

Status Message

Status messages are transmitted on LoRaWAN port 1.

A status message is sent together with measurement data, but no more than once per day.

The payload has a fixed length of 14 bytes.

FieldPositionLengthTypeDescription
version03uint8[3]Firmware version
flags31uint8Internal status flags
temperature42int16 BEInternal device temperature in 0.1 °C
voltage62uint16 BESupply voltage in millivolts
timestamp85uint40 BEUNIX timestamp at message creation
mode131uint8Device operating mode

Example values:

FieldRaw valueDecoded value
version[1, 0, 4]v1.0.4
temperature24624.6 °C
voltage35473.547 V
timestamp1533055905UNIX timestamp

Data Messages

The general message format is identical for all four Modbus object types.

The LoRaWAN port identifies the object type that was read.

Each message starts with a five-byte UNIX timestamp, followed by one or more data packages.

Each data package represents one configured Modbus read operation.

General Structure

timestamp | data package 1 | data package 2 | ...

The timestamp is encoded as an unsigned 40-bit big-endian value.

Data Package Structure

FieldPositionLengthTypeDescription
address01uint8Modbus address of the device
start12uint16 BEAddress of the first coil or register
countAndError31uint8Error flag and number of values
data4Variableuint8[]Raw data returned by the Modbus device

The highest bit of countAndError indicates whether an error occurred.

  • Bit 7 set to 0: readout successful
  • Bit 7 set to 1: readout failed
  • Bits 0–6: number of coils or registers requested

The count can be extracted with:

countAndError & 0x7f

The error state can be extracted with:

Boolean(countAndError & 0x80)

Data Length

For Coils and Discrete Inputs on ports 11 and 12:

dataLength = ceil(count / 8)

For Holding Registers and Input Registers on ports 13 and 14:

dataLength = count * 2

The data bytes contain the raw values returned by the Modbus device. Their interpretation depends on the attached device and its register documentation.

When an error occurs, the data bytes may still be present, but their content is undefined.

Complex Installations

The standard firmware reads all configured values according to one common schedule.

A custom firmware may be required for installations that need:

  • Different schedules for different registers
  • Multiple measurements followed by averaging
  • Conditional readouts based on a status register
  • Writing values to coils or registers
  • Custom payload processing
  • Alternative communication such as NB-IoT

Technical Characteristics

Product

PropertyValue
TypeModbus485-LoRaWAN
DescriptionModbus over LoRaWAN Bridge

RF Transceiver

PropertyValue
TransceiverSemtech SX1272
Frequency863–870 MHz
Maximum transmission power+14 dBm
Typical rangeUp to 2 km
Ideal rangeUp to 10 km with free line of sight

LoRaWAN

PropertyValue
ProtocolLoRaWAN 1.0.1 EU868
Device classClass A
ActivationOTAA or ABP
EncryptionAES-128

Modbus

PropertyValue
Physical interfaceRS-485 twisted pair, with optional GND
Supported protocolsModbus RTU and Modbus ASCII

Environmental Requirements

PropertyValue
Operating temperature-20 °C to +55 °C
Maximum installation height2 m

CE Declaration of Conformity

Download the CE Declaration of Conformity

Disposal and WEEE

Dispose of the device and its batteries according to the applicable local requirements for waste electrical and electronic equipment.

Reference Decoder

The following JavaScript decoder can be used as a starting point for The Things Network or similar LoRaWAN backends.

function readVersion(bytes) {
if (!bytes || bytes.length < 3) {
return null;
}

return `v${bytes[0]}.${bytes[1]}.${bytes[2]}`;
}

function readUInt16BE(bytes, offset = 0) {
return (bytes[offset] << 8) | bytes[offset + 1];
}

function readInt16BE(bytes, offset = 0) {
const value = readUInt16BE(bytes, offset);
return value & 0x8000 ? value - 0x10000 : value;
}

function readUInt40BE(bytes, offset = 0) {
return (
bytes[offset] * 0x100000000 +
bytes[offset + 1] * 0x1000000 +
bytes[offset + 2] * 0x10000 +
bytes[offset + 3] * 0x100 +
bytes[offset + 4]
);
}

function decodeStatus(bytes) {
if (bytes.length < 14) {
return {
error: 'Status payload is too short',
};
}

return {
port: 1,
version: readVersion(bytes),
flags: bytes[3],
temperature: readInt16BE(bytes, 4) / 10,
voltage: readUInt16BE(bytes, 6) / 1000,
timestamp: readUInt40BE(bytes, 8),
operationMode: bytes[13],
};
}

function decodeData(bytes, port) {
if (bytes.length < 5) {
return {
error: 'Data payload is too short',
port,
};
}

const functionCode = port - 10;
const values = [];
let offset = 5;

while (offset + 4 <= bytes.length) {
const device = bytes[offset];
const start = readUInt16BE(bytes, offset + 1);
const countAndError = bytes[offset + 3];
const count = countAndError & 0x7f;
const error = Boolean(countAndError & 0x80);

const dataLength =
functionCode <= 2
? Math.ceil(count / 8)
: count * 2;

if (offset + 4 + dataLength > bytes.length) {
return {
port,
function: functionCode,
timestamp: readUInt40BE(bytes, 0),
values,
error: 'Incomplete data package',
};
}

values.push({
device,
start,
count,
error,
data: bytes.slice(offset + 4, offset + 4 + dataLength),
});

offset += 4 + dataLength;
}

return {
port,
function: functionCode,
timestamp: readUInt40BE(bytes, 0),
values,
};
}

function Decoder(bytes, port) {
switch (port) {
case 1:
return decodeStatus(bytes);

case 11:
case 12:
case 13:
case 14:
return decodeData(bytes, port);

default:
return {
error: 'Invalid port',
port,
};
}
}