Modbus
The Modbus page lets you discover Modbus devices on the network, scan for active data points, and map those points to GEM zones for monitoring and control.
Prerequisites
Before using this page, create a Modbus device in System > Devices with the driver set to modbus. If no Modbus device exists, the page displays a yellow warning message directing you to create one first.
If multiple Modbus devices exist, a device selector appears at the top of the page to choose which one to work with.
Tabs
The page has seven tabs: Register Browser, Watch List, Trending, Diagnostics, Zone Mapping, Network Tools, and Settings. The legacy Network Scan and Point Discovery flows are now consolidated under Network Tools and Register Browser respectively.
Register Browser — Discovery
The Register Browser includes a Discover button that walks the four Modbus address spaces (coils, discrete inputs, holding registers, input registers) from address 0 and reports which addresses are actually mapped on the device. Useful for sparsely-documented devices where the manual doesn't list the full register map.
- Read-only — never writes to the device
- Walks one register at a time, expands to 16-at-a-time chunked reads once it finds a contiguous block of 4+
- Bails per-space after a configurable run of consecutive gaps (default 32) — most small devices have a low-address mapped band and nothing above
- "Scan to" controls the upper bound (default 256). Raise if you suspect mapped addresses higher up.
- Bails on transport errors (timeout, port closed, ENETUNREACH) so a dead bus doesn't burn the whole probe budget
Results are grouped by space and show contiguous blocks with sample values. Click Load → on a block to populate the reader form and read that range immediately.
Network Scan Tab
The Network Scan tab finds Modbus devices (slaves) on the bus by sending test reads to a range of unit IDs.
Scan Configuration
| Field | Range | Default | Description |
|---|---|---|---|
| Start Unit ID | 1 - 254 | 1 | First Modbus unit address to scan. |
| End Unit ID | 1 - 254 | 254 | Last Modbus unit address to scan. |
| Timeout (ms) | 100 - 5000 | 500 | How long to wait for each unit to respond. |
Running a Scan
- Set the start and end unit IDs and timeout.
- Click Scan Network. The button changes to "Scanning..." while active.
- The page title updates to show scan progress (e.g., "Scanning 50/254").
- Click Cancel to stop the scan early. A message reports how many devices were found so far.
- When complete, a results table appears showing discovered devices.
Scan Results
The results table displays:
| Column | Description |
|---|---|
| Unit ID | The Modbus slave address that responded. |
| Status | Connection status (online). |
| Test Value | The value read from holding register 40001 on that unit. |
:::warning Device ignores unit_id Many TCP-only Modbus devices (Brainboxes ED-series, Wago I/O, etc.) have no upstream RS-485 bus to route to, so they answer every unit_id with the same payload. When the driver detects 5 consecutive identical responses it aborts the scan early and emits a single warning row in the results table — unit_ids are effectively meaningless on that device, pick any. :::
Point Discovery Tab
The Point Discovery tab reads specific register ranges on a Modbus unit to find active data points.
Discovery Configuration
| Field | Default | Description |
|---|---|---|
| Unit ID | 1 | The Modbus slave address to scan for points. |
| Coils (0x) | 1 - 100 | Start and end addresses for coil discovery. |
| Discrete Inputs (1x) | 10001 - 10100 | Start and end addresses for discrete input discovery. |
| Holding Registers (4x) | 400001 - 400100 | Start and end addresses for holding register discovery (Kohler extended format). |
| Input Registers (3x) | 300001 - 300100 | Start and end addresses for input register discovery (Kohler extended format). |
Address ranges use the Kohler extended format where the prefix digit indicates the register type (0x = coils, 1x = discrete inputs, 3x = input registers, 4x = holding registers). The driver handles the address conversion automatically.
Running Discovery
- Set the unit ID and adjust register ranges as needed.
- Click Discover Points. The button changes to "Discovering..." while active.
- Click Cancel to stop discovery early.
- When complete, a results table shows all points that returned valid data.
Discovery Results
The results table displays:
| Column | Description |
|---|---|
| Type | Register type with Modbus designation: Coil (0x), Discrete Input (1x), Holding Register (4x), or Input Register (3x). |
| Address | The register address. |
| Current Value | The value currently stored at that address. |
Each row has a link icon in the toolbox column. Click it to select that point for zone mapping.
Mapping a Point to a Zone
When you click the link icon on a discovered point, a mapping bar appears above the table with these fields:
| Field | Description |
|---|---|
| Point Type | The type of Modbus mapping to create. See Point Types below. |
| Subsystem | The GEM subsystem the target zone belongs to (e.g., climate, lighting). |
| Zone | The specific zone to map this point to. The list updates based on the selected subsystem. |
Click Map to create the mapping. The point's address is stored as a zone attribute with a prefix indicating the register type:
| Prefix | Register Type |
|---|---|
c_ | Coil |
i_ | Discrete Input |
hr_ | Holding Register |
ir_ | Input Register |
Point Types
The following mapping types are available:
| Point Type | Description |
|---|---|
modbus_setpoint_read | Read setpoint value |
modbus_setpoint_write | Write setpoint value |
modbus_temperature_read | Read temperature sensor |
modbus_humidity_read | Read humidity sensor |
modbus_humidity_write | Write humidity value |
modbus_fan_mode_read | Read fan mode setting |
modbus_fan_mode_write | Write fan mode setting |
modbus_system_mode_read | Read system mode (heat/cool/auto) |
modbus_system_mode_write | Write system mode |
modbus_occupancy_mode_read | Read occupancy mode |
modbus_occupancy_mode_write | Write occupancy mode |
If a point is already mapped to a zone, clicking its link icon will auto-populate the subsystem, zone, and point type fields with the existing mapping.
Zone Mappings Tab
The Zone Mappings tab shows all existing Modbus point-to-zone mappings across the system.
Mappings Table
| Column | Description |
|---|---|
| Zone | The zone name the point is mapped to. |
| Attribute | The Modbus point type attribute name (e.g., modbus_temperature_read). |
| Modbus Address | The register address with prefix (e.g., hr_400001). |
| Quality | Data quality indicator, if available. |
Each row has a delete icon to remove the mapping (the zone attribute is deleted).
If no mappings exist, the tab displays "No mapped Modbus points found."
Zone Mapping Tab
The Zone Mapping tab links a register selected in the Register Browser to a zone attribute. Selecting a register in the browser switches to this tab automatically.
Mapping Fields
| Field | Description |
|---|---|
| Subsystem | Filter for the Zone dropdown. |
| Zone | The target zone for this mapping. |
| Point Type | The Modbus point name (e.g., temperature, setpoint, system_mode). Stored as modbus_<point>_read / modbus_<point>_write. |
| Data Type | UINT16, INT16, UINT32, INT32, FLOAT32, FLOAT64, Bits, or String. Adopted automatically when the source register declares one. |
| Byte Order | big_endian, little_endian, big_endian_swap, little_endian_swap. Adopted from the source register when set. |
| Scale | Multiplier applied to the decoded value. Defaults to 1. |
| Offset | Added after scaling. Defaults to 0. |
| Poll Interval (ms) | Optional per-mapping override of the device-wide polling rate. Leave blank to inherit. Stored as the companion attribute modbus_<point>_poll_interval. |
Test Read
Click Test Read before saving to issue a one-shot read against the selected register using the chosen data type and byte order. The result panel shows:
- Raw — Raw register words returned by the device
- Decoded — Value after data-type / byte-order interpretation
- Scaled — Value after applying scale and offset (only shown when scale ≠ 1 or offset ≠ 0)
Useful for confirming the right data type / byte order before committing the mapping. Selecting a different register clears the result.
Click Map Point to persist the mapping. Test reads do not modify any attributes.
Network Tools Tab
The Network Tools tab consolidates network discovery, raw protocol access, and register-map import/export.
Network Scan
Sends test reads to a range of unit IDs to find Modbus slaves on the bus.
| Field | Range | Default | Description |
|---|---|---|---|
| Start Unit ID | 1 - 254 | 1 | First Modbus unit address to scan. |
| End Unit ID | 1 - 254 | 254 | Last Modbus unit address to scan. |
| Timeout (ms) | 100 - 5000 | 500 | How long to wait for each unit to respond. |
| Test Register | — | 40001 | Register read on each unit to detect a response. |
Device Identification
Sends FC 43 (Read Device Identification) to a chosen unit ID. Returns vendor name, product code, revision, and any extended object IDs the device exposes. If the device doesn't support FC 43, a notice is shown.
Raw FC Inspector
Sends a raw Modbus function code and shows the parsed response alongside a hex view. Useful for vendor-specific gear and FCs the higher-level browser doesn't wrap.
| Function Code | Wrapped | Inputs |
|---|---|---|
| 1 — Read Coils | yes | address, length |
| 2 — Read Discrete Inputs | yes | address, length |
| 3 — Read Holding Registers | yes | address, length |
| 4 — Read Input Registers | yes | address, length |
| 5 — Write Single Coil | yes | address, ON/OFF |
| 6 — Write Single Register | yes | address, integer |
| 15 — Write Multiple Coils | yes | address, comma-separated booleans |
| 16 — Write Multiple Registers | yes | address, comma-separated integers |
| 17 — Report Server ID | yes | — |
| 22 — Mask Write Register | yes | address, AND mask, OR mask |
| 43 — Read Device Identification | yes | read code (1-4), object ID |
| 65-72, 100-110 — User-Defined | raw bytes | hex byte string (e.g. AA BB CC DD) |
FCs outside the listed set (e.g. FC 7, 8, 11) are not supported by the underlying library and return an explanatory error rather than hanging the connection.
The response panel shows the raw bytes (hex) followed by the parsed body. Errors render in a red panel with the function code that failed.
Register Map Templates
Saves a JSON array of register definitions on the device as the register_map attribute. The map drives Register Browser column hints, default data types, and address labels.
| Action | Description |
|---|---|
| Load | Reads the saved register map from the device. |
| Save | Validates and stores the textarea contents back to the device. |
| Export JSON | Downloads the textarea as modbus_register_map_<device>.json. |
| Export CSV | Downloads the textarea as a CSV using the standard column order. |
| Import CSV | Parses a CSV file (header row required) and replaces the textarea with equivalent JSON. Click Save afterwards to persist. |
CSV columns (header names, lowercase): address, name, description, data_type, byte_order, scale, offset, writable
The address column is required. writable accepts 1/true/yes/y (any case). scale, offset, and address are coerced to numbers when parseable. Empty cells are dropped from the row object.
Diagnostics Tab
Surfaces the Modbus driver's runtime counters and a recent communication log. Useful for narrowing down whether a problem is on the wire, in the device, or in GEM's coercion path.
Summary Cards
- Reads / Writes — total + success / error counters since boot or last clear
- Timeouts — count of timeouts (subset of errors)
- Conn Drops — how many times the underlying TCP socket has closed
- Client Recycles — how many times the driver has torn down and rebuilt the modbus client (the watchdog escalates here when consecutive errors cross the threshold AND no successful op has happened in ~2 min)
- Last Success — relative time since the most recent successful read/write
- Uptime — time since the diagnostics counters were last reset
A red Last Client Error card appears below the summary when the underlying TCP/RTU client has emitted a transport-level error (distinct from app-layer Modbus exceptions, which mean the device responded).
Communication Log
Last 200 entries. Each row shows command, function code, address, unit_id, the coerced wire value (so you can see exactly what was sent), duration, status, and any error. A Class:2 error in the error column means the device returned a Modbus exception — app-layer, not transport — so the socket is fine.
Per-installation Watchdog Tuning
op_failure_threshold (default 5) and op_staleness_ms (default 120000) are device attributes. Raise the threshold on long RS-485 segments where ~5 consecutive timeouts is normal; lower the staleness in latency-sensitive deployments where a wedged socket needs to be caught faster.
Value Coercion
Writes coerce stringified arguments at the driver boundary — 'false' from a saved macro is normalized to JS false before reaching the underlying library. Garbage values (e.g. 'maybe' for a coil) return an explicit error and do not send the write, so a typo never silently energizes a relay.