Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Docs

Modbus source

Status: Available. The modbus source polls one Modbus TCP server on a fixed interval. Every poll reads each configured range, in order, and emits one message whose payload is a JSON object.

Built as the thadam-modbus crate on tokio-modbus (default-features = false, tcp feature only). Only Modbus TCP is supported; RTU and RTU-over-TCP are not.

sources:
  - id: boiler-plc
    kind: modbus
    config:
      address: 192.0.2.10:502
      unit_id: 1
      interval: 1s
      timeout: 2s
      backoff_max: 1m
      reads:
        - name: supply_temp
          kind: input
          start: 0
          count: 2
          data_type: f32
        - name: setpoint
          kind: holding
          start: 100
          count: 2
          data_type: f32
          word_order: little
          scale: 0.1
        - name: pump_running
          kind: coil
          start: 0
          count: 1
          data_type: bool

Configuration

Unknown keys are rejected at every level. Durations are whole seconds (30) or a unit string (500ms, 10s, 5m, 1h). Limits are enforced by thadam validate.

KeyTypeDefaultDescription
addresshost:port—Required. The Modbus TCP server. The port must be non-zero. Bracketed IPv6 hosts are accepted ([::1]:502).
unit_idinteger 0–2551Unit identifier sent in every request.
intervalduration10sTime between the starts of successful polls. Must be > 0.
timeoutduration5sDeadline for connecting and for each read. Must be > 0.
backoff_maxduration5mLongest wait after consecutive failures. Must be ≥ interval.
readslist—Required, non-empty. Performed in order on every poll.

reads

KeyTypeDefaultDescription
namestring—Required. The JSON key for this read’s value. Unique within the source. ts_us is reserved.
kindholding, input, coil, or discrete—Required. Holding registers (function 03), input registers (04), coils (01), or discrete inputs (02).
startinteger 0–65535—Required. First address (0-based). start + count must not pass 65536.
countinteger—Required. Registers or bits to read. At most 125 registers or 2000 bits per read.
data_typesee below—Required. How the registers are interpreted.
word_orderbig or littlebigFor 32-bit types: big puts the first register in the high 16 bits; little swaps them.
scalenumbernoneMultiplier applied to numeric values. Makes the value a float.
offsetnumbernoneAdded after scaling. Makes the value a float.
data_typeRegistersKindValue
u161 per valueholding, inputUnsigned 16-bit integer.
i161 per valueholding, inputSigned 16-bit integer.
u322 per valueholding, inputUnsigned 32-bit integer. count must be even.
i322 per valueholding, inputSigned 32-bit integer. count must be even.
f322 per valueholding, inputIEEE 754 single precision. count must be even.
raw1 per elementholding, inputThe registers as unsigned integers, one array element each.
bool1 per bitcoil, discreteJSON true or false.

scale and offset apply to u16, i16, u32, i32, and f32. They are rejected for bool and raw. A read with several values produces a JSON array. A read with one value produces a scalar, except raw, which is always an array.

Payload format

Each poll produces one message:

{"supply_temp": 71.5, "setpoint": 42.0, "pump_running": true, "ts_us": 1791628800123456}
  • Each read’s name maps to its value: a scalar, an array, or null (see Exception handling).
  • ts_us is the poll time in microseconds since the Unix epoch. It is written on every message.
  • Non-finite floats (NaN, infinities) have no JSON form and become null.
  • f32 values use their shortest decimal form, so 3.14 stays 3.14.

Because the payload is ordinary JSON, dataset fields and the JSON decode step in the transform chain read it like any other JSON source.

Message attributes

AttributeValue
modbus.addressThe configured address.
modbus.unit_idThe configured unit_id.
topicmodbus/<unit_id>. Dataset match.topic filters can select on it.
modbus.exceptionsComma-separated names of reads the device refused. Present only when there is at least one.

Delivery semantics

Polling is at-most-once per poll. The source sets ack_mode: none: it never reports commit to the device, and nothing is redelivered.

EventResult
Poll succeeds and the message is committedRecorded once.
Crash after a poll, before the message is committedThat poll’s reading is not committed. The next poll reads the device again.
Poll failsNo message for that pass. The source reconnects and retries.

Modbus has no way to replay a past register value, so a lost poll cannot be recovered. A reading that matters should be polled at a rate that tolerates losing one.

Errors and retries

FailureClassificationBehavior
Connection refused, unreachable, or name not resolvedTransientDrops the session and retries with jittered backoff.
Connect or read exceeds timeoutTransientDrops the session and retries with jittered backoff.
Connection closed or I/O error mid-pollTransientDrops the session and retries with jittered backoff.
Malformed or mismatched Modbus responseTransientDrops the session and retries with jittered backoff.
Modbus exception response for one readPermanent for that readValue is null; see Exception handling. The session stays up.
Invalid configurationFatal at buildReported by thadam validate.

Backoff starts at interval and doubles after each consecutive failure, capped at backoff_max. Each wait is jittered within the upper half of its window. A successful poll resets the backoff to interval.

Exception handling

A Modbus exception response (for example IllegalDataAddress for an address the device does not have) refuses one read. It does not indicate a broken connection, so the source does not reconnect:

  • The read’s value is null in that poll’s payload.
  • The read’s name is listed in modbus.exceptions.
  • A warning is logged with the read name and exception code.
  • Every other read in the same poll is still performed and decoded.

The message is still emitted, so consumers can tell a refused read (null plus the attribute) from a read that was never configured.

Shutdown

On shutdown the poll task is stopped and the source closes its connection. A poll in progress is abandoned. The source does not wait for an in-flight read beyond the poll task’s stop signal and a short grace period.