5.3. Modbus Protocol (RS-485)#

Protocol Overview#

The FX200 instrument responds to Modbus RTU requests over the RS-485 data line. Communication parameters such as baud rate, parity, stop bits, and device ID are configured through the PC application or via Modbus itself. The default settings are:

Baud rate: 9600
Data bits: 8
Parity: Even
Stop bits: 1
Slave ID: 1

Modbus is a request-response protocol. A master device (such as a data logger or PLC) sends requests, and the slave device (the FX200 instrument) responds. The master can either read the value of one or more holding registers (using Modbus function code 0x03), or write the value of a holding register (function code 0x06), or write more holding registers (function code 0x10). All registers in the FX200 are Holding Registers (40001-range in Modbus convention).

IMPORTANT: Register Address vs. Register Number

The Modbus protocol uses zero-based register addressing in the actual request and response frames transmitted on the wire. This means that the first holding register is addressed as 0, the second as 1, and so on. However, many tools and documentation refer to one-based register numbers.

register number = register address + 1

For example, the first register has address 0 and register number 1. The modpoll utility and most Modbus configuration tools use the 1-based register number.

To further complicate things, certain PLC software packages use 40001-based addressing. In this convention, holding registers are labeled starting from 40001, so that the register number 40001 corresponds to the first holding register at address 0. It is important to understand that the leading “4” in addresses such as 40001 does not form part of the address transmitted over Modbus. Instead, it indicates the register type (4 = Holding Register, not to be confused with function 3 for reading holding registers). The actual address sent in the Modbus frame is zero-based and does not include this prefix.

PLC register number = register address + 40001

Modbus is a very old protocol, so please do not judge its quirks too harshly.

Throughout this document, both the zero-based register address and the corresponding one-based register number are shown for clarity.

Each Modbus register holds a single 16-bit (2-byte) value. The FX200 uses the following data types:

  • uint16 / int16 (1 register)

    A single 16-bit value occupying one register. Unsigned values (uint16) range from 0 to 65535. Signed values (int16) range from -32768 to 32767.

  • uint32 (2 registers)

    A 32-bit unsigned integer spanning two consecutive registers. The low word is in the first (even-addressed) register, and the high word is in the second (odd-addressed) register.

    Example: Value 0x00000003 would be transmitted as pair of registers 0x0003 and 0x0000.

  • float32 (2 registers)

    A 32-bit IEEE 754 floating-point value spanning two consecutive registers. The encoding uses little-endian word order (low word first, high word second), which is common in industrial Modbus devices.

    Example: The Value 1.5 is encoded per IEEE 754 as 0x3FC00000, and would be transmitted as pair of registers 0x0000 and 0x3FC0.
    A fixed endianness check register at address 62 (register 63) always returns the value
    -123.265625. Read this register to verify your decoder handles the byte/word order correctly. The expected raw register values are 0x8800, 0xC2F6.
  • string[n] (n/2 registers)

    The string data type allows a Modbus master to read and write textual data on the device. The parameter n (must be an even number) defines the maximum number of characters the string can contain. If the stored text is shorter than n characters, the remaining positions are filled with null characters (ASCII value 0). Each Modbus register is 16 bits (2 bytes) long and stores two characters, so the total number of registers used is n/2. Characters are stored in big-endian order within each register, so that the high byte contains the first (preceding) character and the low byte contains the second character.

Reading Registers with modpoll#

modpoll is a command-line utility for reading and writing Modbus registers from a PC.

To read the first four float32 measurement values (discharge, water level, surface velocity, flow direction) from the FX200, run modpoll with the following arguments:

modpoll -m rtu -a 1 -r 1 -c 8 -t 4:float -e -b 9600 -d 8 -p even -s 1 COM3

To read a single uint16 configuration register (e.g., measurement unit at register 130):

modpoll -m rtu -a 1 -r 130 -c 1 -t 4 -b 9600 -d 8 -p even -s 1 COM3

These examples were tested with modpoll version 3.10.

Flag

Meaning

-a 1

Slave address 1

-r 1

Start at register number 1 (address 0x00)

-c 8

Read 8 registers (= 4 float32 values)

-t 4:float

Interpret as 32-bit float

-e

Little-endian word order (low word first)

-b 9600

Baud rate 9600

-d 8

8 data bits

-p even

Even parity

-s 1

1 stop bit

COM3

Serial port (use /dev/ttyUSB0 on Linux)

Registers Overview#

The register address space is divided into three distinct blocks:

  • Block 1 provides high-resolution measurement data as 32-bit floats. This is the recommended block for modern data loggers and SCADA systems.

  • Block 2 provides the same measurement data encoded as 16-bit integers for compatibility with legacy data loggers that cannot decode float32 values. Values are scaled as documented in the register table.

  • Block 3 contains all user-configurable device parameters. Some addresses are reserved, and will read as zero and must not be written.

Block

Address Range

Register Range

Purpose

1

0 - 63
0x00 - 0x3F
1 - 64
0x01 - 0x40

Measurement data (float32, read-only)

2

64 - 127
0x40 - 0x7F
65 - 128
0x41 - 0x80

Measurement data (int16 compatibility, read-only)

3

128 - 194
0x80 - 0xC2
129 - 195
0x81 - 0xC3

Configuration parameters (read/write)

Block 1: Measurement Registers (float32)#

Address(es)

Reg #

Data Type

Description

0, 1
0x00, 0x01
1, 2
0x01, 0x02

float32

Flow discharge in the selected measurement unit. The unit is set by configuration register 0x83.

2, 3
0x02, 0x03
3, 4
0x03, 0x04

float32

Water level in the selected level measurement unit. The unit is set by configuration register 0x82.

4, 5
0x04, 0x05
5, 6
0x05, 0x06

float32

Absolute surface velocity in the selected measurement unit (always >= 0). The unit is set by configuration register 0x81.

6, 7
0x06, 0x07
7, 8
0x07, 0x08

float32

Flow direction:
+1.0 = incoming (towards sensor),
-1.0 = outgoing (away from sensor),
0.0 = no flow detected.
8, 9
0x08, 0x09
9, 10
0x09, 0x0A

float32

Discharge in m³/s. Not unit-converted, always in m³/s.

10, 11
0x0A, 0x0B
11, 12
0x0B, 0x0C

float32

Water level in m. Not unit-converted, always in m.

12, 13
0x0C, 0x0D
13, 14
0x0D, 0x0E

float32

Surface velocity in m/s. The value can be positive or negative. Positive values – incoming flow, negative values – outgoing flow. Not unit-converted, always in m/s.

14, 15
0x0E, 0x0F
15, 16
0x0F, 0x10

float32

Signal-to-noise ratio (SNR) of the surface velocity radar, in dB. Higher values indicate stronger signal quality.

16, 17
0x10, 0x11
17, 18
0x11, 0x12

float32

Signal-to-noise ratio (SNR) of the water level radar in dB. Higher values indicate stronger signal quality.

18, 19
0x12, 0x13
19, 20
0x13, 0x14

float32

Forward tilt angle of the sensor in degrees. Ideally the sensor should be mounted at 0°.

20, 21
0x14, 0x15
21, 22
0x15, 0x16

float32

Side tilt angle of the sensor in degrees. This angle should be very close to 0°.

22, 23
0x16, 0x17
23, 24
0x17, 0x18

float32

Device temperature in units of degrees Celsius.

24, 25
0x18, 0x19
25, 26
0x19, 0x1A

float32

Relative humidity inside the device enclosure in percent (%).

26, 27
0x1A, 0x1B
27, 28
0x1B, 0x1C

uint32

Status flags (0 = all OK), as error bitmask:
Bit 0: forward tilt angle error (tilt angle outside of expected range).
Bit 1: side tilt angle error (tilt is too big).
Bit 2: radar co-processor update failed.
Bit 3: surface velocity radar co-processor not responding.
Bit 4: radar co-processor needs to be updated.
Bit 5: tilt sensor is not working.
Bit 6: water level radar co-processor not responding.
Bit 7: DFP data corrupt, needs reset.
Bit 8: standby mode is active.
Bit 9: radar is initializing.
28
0x1C
29
0x1D

uint16

Firmware upgrade status:
0 = idle,
1 = pending,
2 = active,
3 = done,
4 = failed.
29, 30
0x1D, 0x1E
30, 31
0x1E, 0x1F

float32

Total accumulated volume, in selected units.

31, 32
0x1F, 0x20
32, 33
0x20, 0x21

float32

Total accumulated volume, in m³.

33, 34
0x21, 0x22
34, 35
0x22, 0x23

float32

Distance to water, in the selected level measurement unit. The unit is set by configuration register 0x82.

35, 36
0x23, 0x24
36, 37
0x24, 0x25

float32

Distance to water in mm. Not unit-converted, always in mm.

37 - 61
0x25-0x3D
38 - 62
0x26-0x3E

Reserved. Reads as 0.

62, 63
0x3E, 0x3F
63, 64
0x3F, 0x40

float32

Endianness check: This register will always return constant -123.265625, as a pair of values 0x8800, 0xC2F6. Use this to verify float32 decoding in your system.

Block 2: Measurement Registers (int16)#

These registers provide the same measurement data as Block 1, but encoded as 16-bit integers for compatibility with data loggers that do not support float32 decoding. All values are read-only.

Address(es)

Reg #

Data Type

Description

64
0x40
65
0x41

uint16

Discharge, integer part, in selected measurement unit. Example: if discharge is 12.3456, this register returns 12.

65
0x41
66
0x42

uint16

Discharge, fractional part (4 decimal digits), in selected measurement units. Example: if discharge is 12.3456 this register returns 3456; if discharge is 12.34 then the register returns 3400.

66
0x42
67
0x43

int16

Water level, integer part, in selected measurement unit. Example: if the level is 12.3456, this register returns 12.

67
0x43
68
0x44

int16

Water level, fractional part (4 decimal digits), in selected measurement units. Example: if the level is 12.3456 this register returns 3456; if the level is 12.34 then the register returns 3400.

68
0x44
69
0x45

uint16

Absolute surface velocity, integer part, in selected measurement unit. Example: if velocity is 12.3456, this register returns 12.

69
0x45
70
0x46

int16

Absolute surface velocity, fractional part (4 decimal digits), in selected measurement units. Example: if velocity is 12.3456 this register returns 3456; if velocity is 12.34 then the register returns 3400.

70
0x46
71
0x47

int16

Flow direction:
+1 = incoming,
-1 = outgoing,
0 = no flow.
71
0x47
72
0x48

int16

Surface velocity in mm/s, integer part only. Positive values – incoming flow, negative values – outgoing flow. Clamped to range -16000 .. +16000 mm/s.

72
0x48
73
0x49

int16

Distance to water in mm, integer part only.

73
0x49
74
0x4A

int16

Signal-to-noise ratio (SNR), of the returned radar signal of the surface velocity radar in dB integer part only. Higher values indicate stronger signal quality.

74
0x4A
75
0x4B

int16

Signal-to-noise ratio (SNR), of the returned radar signal of the water level radar in dB integer part only. Higher values indicate stronger signal quality.

75
0x4B
76
0x4C

int16

Forward tilt angle of the sensor in degrees. Ideally the sensor should be mounted at 0°.

76
0x4C
77
0x4D

int16

Side tilt angle of the sensor in degrees. This angle should be very close to 0°.

77
0x4D
78
0x4E

int16

Device temperature in degrees Celsius × 100. Example: if temperature is 23.456 °C then this register holds 2345.

78
0x4E
79
0x4F

int16

Relative humidity inside the device enclosure in percent (%) × 100. Example: if relative humidity is 24.17 % then this register holds 2417.

79, 80
0x4F, 0x50
80, 81
0x50, 0x51

uint32

Status flags (same encoding as Block 1).

81
0x51
82
0x52

uint16

Firmware upgrade status (same as Block 1 register 0x1C).

Block 3: Configuration Registers (R/W)#

Addr.

Reg #

Data Type

Description

128
0x80
129
0x81

uint16

Current Firmware Version Number. Read-only. Example: if the firmware version is 1.2.3 this register holds 123.

129
0x81
130
0x82

uint16

Velocity Measurement Unit. Affects values in registers 0x04, 0x05, 0x44, 0x45. See Surface Velocity Output Unit for the detailed setting description.
Available values:
0 = mm/s,
1 = m/s,
2 = mph,
3 = km/h,
4 = ft/s,
5 = ft/min,
6 = cm/s.
Default value: 1 (m/s)
130
0x82
131
0x83

uint16

Water Level Measurement Unit. Affects values in registers 0x02, 0x03, 0x21, 0x22, 0x42, 0x43. See Water Level Output Unit for the detailed setting description.
Available values:
0 = mm,
1 = cm,
2 = m,
3 = ft,
4 = in.
Default value: 2 (m)
131
0x83
132
0x84

uint16

Discharge Measurement Unit. Affects values in registers 0x00, 0x01, 0x40, 0x41. See Discharge Output Unit for the detailed setting description.
Available values:
0 = m³/s,
1 = m³/h,
2 = l/s,
3 = l/h,
4 = cfs (ft³/s),
5 = gal/min,
6 = ML/day (megaliters per day).
Default value: 0 (m³/s)
132
0x84
133
0x85

uint16

Velocity Filter Length. In seconds. Longer values produce smoother but slower-responding readings. See Filter Length for the detailed setting description. Valid range: 1 - 120. Default value: 10

133
0x85
134
0x86

uint16

Direction Filter. See Direction Filter for the detailed setting description.
Available values:
0 = measure both directions,
1 = incoming flow only,
2 = outgoing flow only.
Default value: 0 (both)
134
0x86
135
0x87

uint16

Radar Beam Width Configuration. See Beam Width for the detailed setting description.
Available values:
0 = wide,
1 = narrow.
Default value: 0 (wide)
135
0x87
136
0x88

uint16

Dynamic Flow Profiler (DFP). Enable DFP calculation. See Dynamic Flow Profiler for the detailed setting description.
Available values:
0 = disabled,
1 = enabled.
Default value: 0 (off)
136, 137
0x88, 0x89
137, 138
0x89, 0x8A

float32

Maximum Velocity Threshold. Velocity readings above this threshold are rejected. See Maximum Velocity for the detailed setting description. Default value: 16.0

138
0x8A
139
0x8B

uint16

Maximum Velocity Unit. See Maximum Velocity for the detailed setting description.
Available values:
0 = mm/s,
1 = m/s,
2 = mph,
3 = km/h,
4 = ft/s,
5 = ft/min,
6 = cm/s.
Default value: 1 (m/s)
139
0x8B
140
0x8C

uint16

Radar Beam Offset Configuration. See Beam Position for the detailed setting description.
Available values:
0 = near,
1 = far.
Default value: 0 (near)
140, 141
0x8C, 0x8D
141, 142
0x8D, 0x8E

float32

Minimum Valid Distance. Reflections detected closer than this distance are ignored. Use this parameter to exclude near-field returns e.g., mounting hardware, bridge elements, or other objects close to the sensor. See Minimum Distance for the detailed setting description. Default value: 0

142
0x8E
143
0x8F

uint16

Minimum Valid Distance Unit. See Minimum Distance for the detailed setting description.
Available values:
0 = mm,
1 = cm,
2 = m,
3 = ft,
4 = in.
Default value: 2 (m)
143
0x8F
144
0x90

uint16

Radar Receiver Mode. Selects how the signals from the radar receive antennas are combined.
Available values:
0 = coherent,
1 = sum.
Default value: 0 (coherent)
144, 145
0x90, 0x91
145, 146
0x91, 0x92

float32

Maximum Valid Distance. Reflections detected beyond this distance are ignored. Use this parameter to exclude far-field returns outside the intended measurement zone e.g., opposite bank, structures, or background targets. See Maximum Distance for the detailed setting description. Default value: 30.0

146
0x92
147
0x93

uint16

Maximum Valid Distance Unit. See Maximum Distance for the detailed setting description.
Available values:
0 = mm,
1 = cm,
2 = m,
3 = ft,
4 = in.
Default value: 2 (m)
147
0x93
148
0x94

uint16

SNR Threshold in dB. Reflections with a signal-to-noise ratio below this threshold are ignored. See SNR Threshold for the detailed setting description. Valid range: 0 - 255. Default value: 0

148
0x94
149
0x95

uint16

RS-232 Serial Port Baud Rate. See RS-232 Baud Rate for the detailed setting description.
Available values:
0 = 4800,
1 = 9600,
2 = 14400,
3 = 19200,
4 = 38400,
5 = 57600,
6 = 115200.
Default value: 6 (115200)
149
0x95
150
0x96

uint16

Modbus RS-485 Baud Rate. Caution: changing this will require the master to reconnect at the new baud rate. See RS-485 Baud Rate for the detailed setting description.
Available values:
0 = 4800,
1 = 9600,
2 = 14400,
3 = 19200,
4 = 38400,
5 = 57600,
6 = 115200.
Default value: 1 (9600)
150
0x96
151
0x97

uint16

Modbus Parity Setting. See Modbus Parity for the detailed setting description.
Available values:
0 = none,
1 = odd,
2 = even.
Default value: 2 (even)
151
0x97
152
0x98

uint16

Modbus Stop Bits. See RS-485 Stop Bits for the detailed setting description.
Available values:
0 = one stop bit,
1 = two stop bits.
Default value: 0 (one)
152
0x98
153
0x99

uint16

Modbus Slave Address. Must be unique on the RS-485 bus. See Modbus ID for the detailed setting description. Valid range: 1 - 247. Default value: 1

153
0x99
154
0x9A

uint16

SDI-12 Sensor Address. The value is stored as a numeric integer (0 - 61) in the register. SDI-12 addressing is character-based, so the numeric value from the register is converted to the corresponding SDI-12 address character according to the following mapping: 0 - 9 -> characters 0-9, 10 - 35 -> characters A-Z, 36 - 61 -> characters a-z. See SDI-12 ID for the detailed setting description. Default value: 0

154
0x9A
155
0x9B

uint16

Power Management Mode. Gets / sets the automatic SDI-12 sleep mode parameter. When SDI-12 sleep is enabled, the device enters low-power sleep between SDI-12 transactions. When disabled, the sensor runs continuously and is ready at all times. See Power Management for the detailed setting description.
Available values:
0 = sleep enabled (low power),
1 = sleep disabled (continuous scanning).
Default value: 1 (off)
155
0x9B
156
0x9C

uint16

Analog output selection. Gets / sets the type of the measurement that will be sent to the analog output. See 4 - 20 mA Output Type for the detailed setting description.
Available values:
0 = none,
1 = discharge,
2 = water level,
3 = distance to water,
4 = surface velocity,
5 = total volume.
Default value: 1 (discharge)
Note: writing this register resets both the minimum value unit (register 0x9E) and the maximum value unit (register 0xA2) to 0, because the set of valid units depends on the selected output type. When writing several of these registers in a single request, write this register first.
156, 157
0x9C, 0x9D
157, 158
0x9D, 0x9E

float32

Minimum Value for Analog Output. Measurements at or below this value produce minimum current. See 4 - 20 mA Minimum Value for the detailed setting description. Default value: 0

158
0x9E
159
0x9F

uint16

Minimum Value Unit for Analog Output. See 4 - 20 mA Minimum Value for the detailed setting description.
Available values, if analog output is set to discharge:
0 = m³/s,
1 = m³/h,
2 = l/s,
3 = l/h,
4 = cfs (ft³/s),
5 = gal/min,
6 = ML/day.
Available values, if analog output is set to water level or distance to water:
0 = mm,
1 = cm,
2 = m,
3 = ft,
4 = in.
Available values, if analog output is set to surface velocity:
0 = mm/s,
1 = m/s,
2 = mph,
3 = km/h,
4 = ft/s,
5 = ft/min,
6 = cm/s.
Available values, if analog output is set to total volume:
0 = m³,
1 = l,
2 = ft³,
3 = gal,
4 = ML.
Default value: 1 (with the factory default analog output of discharge, this is m³/h)
159
0x9F
160
0xA0

Reserved.

160, 161
0xA0, 0xA1
161, 162
0xA1, 0xA2

float32

Maximum Value for Analog Output. Measurements at or above this value produce maximum current. See 4 - 20 mA Maximum Value for the detailed setting description. Default value: 16.0

162
0xA2
163
0xA3

uint16

Maximum Value Unit for Analog Output. See 4 - 20 mA Maximum Value for the detailed setting description.
Available values, if analog output is set to discharge:
0 = m³/s,
1 = m³/h,
2 = l/s,
3 = l/h,
4 = cfs (ft³/s),
5 = gal/min,
6 = ML/day.
Available values, if analog output is set to water level or distance to water:
0 = mm,
1 = cm,
2 = m,
3 = ft,
4 = in.
Available values, if analog output is set to surface velocity:
0 = mm/s,
1 = m/s,
2 = mph,
3 = km/h,
4 = ft/s,
5 = ft/min,
6 = cm/s.
Available values, if analog output is set to total volume:
0 = m³,
1 = l,
2 = ft³,
3 = gal,
4 = ML.
Default value: 1 (with the factory default analog output of discharge, this is m³/h)
163
0xA3
164
0xA4

uint16

Total Volume Measurement Unit. Selects the unit used for the total accumulated volume reported in registers 0x1D, 0x1E.
Available values:
0 = m³,
1 = l,
2 = ft³,
3 = gal,
4 = ML (megaliters).
Default value: 0 (m³)
164
0xA4
165
0xA5

uint16

Operation Mode. When the operation mode is set to standby, the device will communicate on all communication interfaces, but the radar transmitters will be turned off to reduce the power, and the instrument will not be making any measurements. See Operation Mode for the detailed setting description.
Available values:
0 = off (normal operation),
1 = on (standby operation).
Default value: 0 (off)
165
0xA5
166
0xA6

uint16

Disable NMEA Sentence Output. On RS-232.
Available values:
0 = NMEA enabled,
1 = NMEA disabled.
Default value: 0 (off)
166
0xA6
167
0xA7

uint16

Show Data on Error. Continue reporting velocity data even when angle errors are detected.
Available values:
0 = clear velocity on error (send 0 as velocity instead of measured value),
1 = keep velocity.
Default value: 0 (off)
167
0xA7
168
0xA8

uint16

Fixed Mounting Angle. Override measured forward angle with a fixed value (degrees). Changing this parameter will affect the reported velocity as cosine correction behaviour will be changed. Use this parameter only during tow-tank testing. 0 = use measured angle. Valid range: 0 - 89. Default value: 0 (off)

168, 169
0xA8, 0xA9
169, 170
0xA9, 0xAA

float32

Radar Vertical Position. Defines the vertical position of the radar sensor above the hydrological zero (gauge zero). This value is used to calculate the water level from the measured distance to the water surface and to determine the sensor position within the channel cross-section for discharge calculations. The unit is set by configuration register 0xAB. Default value: 0

170
0xAA
171
0xAB

Reserved.

171
0xAB
172
0xAC

uint16

Unit for radar vertical position.
0 = mm,
1 = cm,
2 = m,
3 = ft,
4 = in.
Default value: 2 (m)
172
0xAC
173
0xAD

uint16

Enable Wi-Fi.
0 = off,
1 = on.
Default value: 0 (off)
173 - 180
0xAD - 0xB4
174 - 181
0xAE - 0xB5

string[16]

Wi-Fi password, up to 16 characters. Shorter passwords are padded with null characters.

181
0xB5
182
0xB6

Reserved. Reads as 0, writes are ignored.

182
0xB6
183
0xB7

uint16

LiDAR Mode. Enables automatic scanning of the channel cross-section with the built-in LiDAR sensor. See LiDAR Mode for the detailed setting description.
Available values:
0 = off (automatic scanning disabled),
1 = automatic.
Default value: 0 (off)
183
0xB7
184
0xB8

uint16

LiDAR Left Limit Angle. Defines how far the LiDAR sweep extends towards the left side of the channel, in degrees measured from the vertical axis of the instrument. See Left Limit Angle for the detailed setting description. Valid range: 0 - 180; values above 70 have no additional effect, since 70 corresponds to the full mechanical sweep range of the sensor. Default value: 70

184
0xB8
185
0xB9

uint16

LiDAR Right Limit Angle. Defines how far the LiDAR sweep extends towards the right side of the channel, in degrees measured from the vertical axis of the instrument. See Right Limit Angle for the detailed setting description. Valid range: 0 - 180; values above 70 have no additional effect, since 70 corresponds to the full mechanical sweep range of the sensor. Default value: 70

185
0xB9
186
0xBA

uint16

Command register. Write value 0xAD10 to this register to perform a software reset. The device will restart immediately. The magic value prevents accidental resets.

186
0xBA
187
0xBB

uint16

Command register. Write value 0xBAC1 to this register to restore all parameters to factory defaults and restart. The magic value prevents accidental resets.

187
0xBB
188
0xBC

uint16

Command register. Write value 0xCDF0 to this register to clear the DFP (Dynamic Flow Profiler) learned data. The magic value prevents accidental resets.

188
0xBC
189
0xBD

Reserved.

189, 190
0xBD, 0xBE
190, 191
0xBE, 0xBF

float

Command register. Write the value of the current staff gauge reading to automatically adjust the vertical position of the radar. The units of the measurement are defined in register 171 (172). Writing to this register will automatically adjust the value in register 168-169 (169-170).

191
0xBF
192
0xC0

uint16

Command register. Write value 0xC003 to this register, to reset the totalizer to zero.

192, 193
0xC0, 0xC1
193, 194
0xC1, 0xC2

Reserved.

194
0xC2
195
0xC3

uint16

Command register. Write value 0xB61D to this register to start a single (manual) LiDAR scan of the channel cross-section. The scan takes 30 to 45 seconds to complete, and the command is ignored if a scan is already running. The magic value prevents accidental scans. See Manual LiDAR Scan for the detailed description.

Reading LiDAR Scan#

The channel cross-section produced by the most recent manual LiDAR scan (the same data shown on the LiDAR page of the configurator, see 6.3. Using LiDAR) can be read out over Modbus.

The scan is a relatively large data set (up to 1000 measured points) and does not fit into the holding-register map. It is therefore transferred using a set of custom Modbus function codes that move an arbitrary-length byte stream (a “file”) between the master and the device, rather than through registers. The same mechanism is used for firmware upgrades and echo-curve download.

The LiDAR scan is exposed as file number 40.

Custom Function Codes#

Three custom function codes are used. In all of them the first byte of the frame is the slave address and the last two bytes are the standard Modbus RTU CRC (CRC-16/MODBUS, low byte first). All multi-byte fields inside the frames are big-endian.

Code

Meaning

0x46 (70)

File operation: open for read, open for write, or close.

0x47 (71)

Read a block of bytes from the currently open file.

0x48 (72)

Write a block of bytes to the currently open file (not used when reading a scan).

File operation (0x46) – request:

Field

Size

Value

Address

1

Slave address

Function

1

0x46

Operation

1

0x01 = open read, 0x02 = open write, 0xFF = close

File number

1

40 (0x28) for the LiDAR scan

File size

4

Ignored when opening the LiDAR scan for read; send 0

CRC

2

Modbus RTU CRC

File operation (0x46) – response to open read: the device prepares the scan and returns its total size in bytes:

Field

Size

Value

Address

1

Slave address

Function

1

0x46

File size

4

Total size of the scan payload, in bytes

CRC

2

Modbus RTU CRC

File read (0x47) – request:

Field

Size

Value

Address

1

Slave address

Function

1

0x47

Start address

4

Byte offset within the file to start reading from

Byte count

2

Number of bytes to read (keep within a single Modbus frame, e.g. 240)

CRC

2

Modbus RTU CRC

File read (0x47) – response:

Field

Size

Value

Address

1

Slave address

Function

1

0x47

Byte count

2

Number of data bytes that follow

Data

n

The requested block of file bytes

CRC

2

Modbus RTU CRC

If the requested offset is at or beyond the end of the file, the device returns a byte count of 0. This marks the end of the transfer.

An exception response (function code with the high bit set, e.g. 0xC6) with exception code 6 (busy) is returned if the device cannot prepare the scan at that moment; retry after a short delay.

Transfer sequence#

To read the scan:

  1. Send an open read request for file 40 and read the returned file size.

  2. Repeatedly send read requests, advancing the start address by the number of bytes returned each time, until the returned byte count is 0 or the accumulated length reaches the file size.

  3. Send a close request (operation 0xFF) to release the buffer on the device.

Example frames for slave address 1:

open read : 01 46 01 28 00 00 00 00 C7 D4
read      : 01 47 00 00 00 00 00 F0 B6 87   (240 bytes from offset 0)
close     : 01 46 FF 28 00 00 00 00 D2 0A

Payload format#

The bytes returned for file 40 are the scanned profile serialized as a CBOR (RFC 8949) map and then compressed with Zstandard (zstd). Unlike the servicing protocol, the Modbus transfer carries the raw compressed bytes directly: there is no byte-stuffing and no extra application CRC, since the Modbus frame CRC already protects each block.

After concatenating all the blocks, decompress the payload with zstd and parse the resulting CBOR map. The relevant keys are:

Key

Type

Description

unt

text string

Unit of the coordinates. Always mm.

x

array of float32

X coordinates of the profile points, left to right.

y

array of float32

Y coordinates of the profile points. Values are negative, measured downward from the sensor.

The x and y arrays have the same length and together define the cross-section points.

Python example#

The following example reads the scan over an RS-485 adapter and saves the profile as a CSV file with x and y columns in millimeters. Because the transfer uses custom function codes that standard Modbus libraries do not implement, the frames are built directly on top of a serial port. It requires the pyserial, zstandard, and cbor2 packages (pip install pyserial zstandard cbor2).

import csv
import struct
import time
import serial
import zstandard
import cbor2

PORT = "COM3"          # RS-485 adapter port (e.g. "/dev/ttyUSB0")
BAUD = 9600
SLAVE = 1
LIDAR_FILE = 40
CHUNK = 240            # bytes per read request
OUTPUT_CSV = "lidar_profile.csv"

def crc16_modbus(frame: bytes) -> int:
    crc = 0xFFFF
    for byte in frame:
        crc ^= byte
        for _ in range(8):
            if crc & 1:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

def send(port: serial.Serial, pdu: bytes) -> bytes:
    frame = bytes([SLAVE]) + pdu
    frame += struct.pack("<H", crc16_modbus(frame))   # CRC low byte first
    port.write(frame)
    time.sleep(0.05)
    resp = port.read(512)
    if len(resp) < 4 or crc16_modbus(resp[:-2]) != struct.unpack("<H", resp[-2:])[0]:
        raise IOError("bad or missing response: " + resp.hex(" "))
    if resp[1] & 0x80:
        raise IOError(f"Modbus exception {resp[2]}")
    return resp

with serial.Serial(PORT, BAUD, parity=serial.PARITY_EVEN, timeout=1) as port:
    # 1. Open file 40 for read; response carries the total size (4 bytes).
    resp = send(port, bytes([0x46, 0x01, LIDAR_FILE]) + struct.pack(">I", 0))
    file_size = struct.unpack(">I", resp[2:6])[0]

    # 2. Read the file in chunks until the whole payload is received.
    payload = bytearray()
    while len(payload) < file_size:
        pdu = bytes([0x47]) + struct.pack(">I", len(payload)) + struct.pack(">H", CHUNK)
        resp = send(port, pdu)
        count = struct.unpack(">H", resp[2:4])[0]
        if count == 0:
            break
        payload += resp[4:4 + count]

    # 3. Close the file to release the device buffer.
    send(port, bytes([0x46, 0xFF, LIDAR_FILE]) + struct.pack(">I", 0))

cbor_bytes = zstandard.ZstdDecompressor().decompress(bytes(payload))
profile = cbor2.loads(cbor_bytes)

with open(OUTPUT_CSV, "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["x_mm", "y_mm"])
    for x, y in zip(profile["x"], profile["y"]):
        writer.writerow([x, y])

print(f"Saved {len(profile['x'])} points to {OUTPUT_CSV}")