Skip to content
Plugins (190)

modbus

stable

Encode and decode Modbus TCP and RTU protocol frames for industrial device communication, with support for coils, discrete inputs, and holding/input registers.

use plugin modbus
14 functions Systems
Runs with the native Zolo host

Plugin snippets can be edited in the Playground, but native capabilities are executed locally with the Zolo CLI.

/ filter jk navigate Esc clear
Functions (14)
  1. encode_read_coils Encode Read Coils request (FC 0x01)
  2. encode_read_discrete_inputs Encode Read Discrete Inputs request (FC 0x02)
  3. encode_read_holding_registers Encode Read Holding Registers request (FC 0x03)
  4. encode_read_input_registers Encode Read Input Registers request (FC 0x04)
  5. encode_write_single_coil Encode Write Single Coil request (FC 0x05)
  6. encode_write_single_register Encode Write Single Register request (FC 0x06)
  7. encode_write_multiple_coils Encode Write Multiple Coils request (FC 0x0F)
  8. encode_write_multiple_registers Encode Write Multiple Registers request (FC 0x10)
  9. decode_response Decode a Modbus TCP response frame
  10. decode_registers Decode register values from response PDU data
  11. decode_coils Decode coil/discrete-input bits from response data
  12. encode_rtu_request Encode a Modbus RTU request with CRC-16
  13. crc16_modbus Compute CRC-16/Modbus checksum
  14. function_name Return human-readable name for a function code

From import to result

  1. 01Import only what you use
  2. 02Copy the smallest example
  3. 03Run it with the Zolo CLI

Overview

modbus is a stateless codec for the Modbus protocol: it does not open sockets or talk to devices itself, it only turns request parameters into the exact bytes a device expects and turns response bytes back into Zolo values. You pair it with a transport plugin (TCP for Modbus TCP, a serial link for Modbus RTU) — encode a request, send the returned bytes over the wire, then feed the reply back into the decoders. Every encoder returns a bytes frame and every decoder takes bytes, so the plugin is just a pure translation layer between Zolo numbers/booleans and the on-wire frame format.

Two framings are supported. Modbus TCP frames carry a 7-byte MBAP header (auto-assigned transaction id, zero protocol id, length, and unit id) and are produced by all the encode_* functions and parsed by decode_response. Modbus RTU frames are raw unit_id + function + payload with a trailing little-endian CRC-16, built by encode_rtu_request and verifiable with crc16_modbus. Coil and register payloads from a response body are unpacked with decode_coils and decode_registers.

Common patterns

Build a Read Holding Registers request, then parse the device reply and unpack the 16-bit values:

use plugin modbus::{encode_read_holding_registers, decode_response, decode_registers}

let request = encode_read_holding_registers(1, 0, 2)
// ...send `request` over TCP and read the reply bytes...
let reply = decode_response(request)
if reply["is_error"] {
  print("device returned exception")
} else {
  let regs = decode_registers(reply["data"])
  print("reg1={regs[1]} reg2={regs[2]}")
}

Toggle a coil and confirm the function code by name:

use plugin modbus::{encode_write_single_coil, decode_response, function_name}

let frame = encode_write_single_coil(1, 0, true)
let reply = decode_response(frame)
print("{function_name(reply["function_code"])}: unit {reply["unit_id"]}")

Build a serial RTU request and check its trailing CRC:

use plugin modbus::{encode_rtu_request, crc16_modbus}

let frame = encode_rtu_request(1, 0x03, 0, 10)
let body = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]
print("expected CRC: {crc16_modbus(body)}")
print("RTU frame: {frame}")

Encode Read Coils request (FC 0x01)

Encodes a Modbus TCP Read Coils request (function code 0x01) with a full MBAP header. unit_id identifies the slave device; address is the starting coil address; count is the number of coils to read.

use plugin modbus::{encode_read_coils, decode_response, decode_coils}

let frame = encode_read_coils(1, 0, 8)
print("frame bytes: {frame}")

Read 16 coils starting at a higher address — count is the number of bits, not bytes, so the device packs the result into two bytes:

use plugin modbus::{encode_read_coils}

let frame = encode_read_coils(2, 200, 16)
print("requesting 16 coils from unit 2")

Encode Read Discrete Inputs request (FC 0x02)

Encodes a Read Discrete Inputs request (function code 0x02). Discrete inputs are read-only single-bit values, typically connected to physical sensors.

use plugin modbus::{encode_read_discrete_inputs}

let frame = encode_read_discrete_inputs(1, 100, 4)

Encode Read Holding Registers request (FC 0x03)

Encodes a Read Holding Registers request (function code 0x03) for reading 16-bit read/write registers from a slave device.

use plugin modbus::{encode_read_holding_registers, decode_response, decode_registers}

let frame = encode_read_holding_registers(1, 0, 10)

Encode Read Input Registers request (FC 0x04)

Encodes a Read Input Registers request (function code 0x04) for reading 16-bit read-only registers, typically from sensors or analog inputs.

use plugin modbus::{encode_read_input_registers}

let frame = encode_read_input_registers(1, 200, 2)

Encode Write Single Coil request (FC 0x05)

Encodes a Write Single Coil request (function code 0x05). value is a boolean: true turns the coil ON (0xFF00), false turns it OFF (0x0000).

use plugin modbus::{encode_write_single_coil}

let turn_on = encode_write_single_coil(1, 0, true)
let turn_off = encode_write_single_coil(1, 0, false)

Encode Write Single Register request (FC 0x06)

Encodes a Write Single Register request (function code 0x06) to write a single 16-bit value to a holding register.

use plugin modbus::{encode_write_single_register}

let frame = encode_write_single_register(1, 40001, 1234)

Encode Write Multiple Coils request (FC 0x0F)

Encodes a Write Multiple Coils request (function code 0x0F). values is a table of booleans, packed into bytes LSB-first per the Modbus spec.

use plugin modbus::{encode_write_multiple_coils}

let frame = encode_write_multiple_coils(1, 0, [true, false, true, true, false, false, true, false])

Encode Write Multiple Registers request (FC 0x10)

Encodes a Write Multiple Registers request (function code 0x10). values is a table of integers, each written as a 16-bit big-endian word.

use plugin modbus::{encode_write_multiple_registers}

let frame = encode_write_multiple_registers(1, 40000, [100, 200, 300])

Writing a single-element table still uses function code 0x10 (multiple), which some PLCs require even for one register:

use plugin modbus::{encode_write_multiple_registers}

let setpoint = encode_write_multiple_registers(1, 40010, [4200])
print("setpoint frame ready")

Decode a Modbus TCP response frame

Decodes a Modbus TCP response frame into {transaction_id, unit_id, function_code, is_error, data}. If is_error is true, function_code has the high bit set and the error code is in data[1].

use plugin modbus::{encode_read_holding_registers, decode_response}

let request = encode_read_holding_registers(1, 0, 2)
let response = decode_response(request)
print("fc={response["function_code"]} error={response["is_error"]}")

The transaction_id field lets you correlate a reply with the request that produced it when several are in flight:

use plugin modbus::{encode_read_coils, decode_response}

let req = encode_read_coils(1, 0, 4)
let resp = decode_response(req)
print("tx {resp["transaction_id"]} from unit {resp["unit_id"]}")

Decode register values from response PDU data

Decodes the PDU data portion of a read-registers response into a table of integer register values. Pass the data field from decode_response.

use plugin modbus::{decode_registers}

let pdu_data = [0x04, 0x00, 0x0A, 0x00, 0x14]
let regs = decode_registers(pdu_data)
print(regs[1])
print(regs[2])

Decode coil/discrete-input bits from response data

Decodes the PDU data portion of a read-coils or read-discrete-inputs response into a table of booleans. count specifies how many coil values to extract.

use plugin modbus::{decode_coils}

let pdu_data = [0x01, 0b10110101]
let coils = decode_coils(pdu_data, 8)
print(coils[1])
print(coils[2])

Encode a Modbus RTU request with CRC-16

Encodes a Modbus RTU request frame: unit_id + function + address (2 BE) + count (2 BE) + CRC-16 (2 LE). Use for serial (RS-485/RS-232) Modbus communication instead of TCP.

use plugin modbus::{encode_rtu_request}

let frame = encode_rtu_request(1, 0x03, 0, 10)
print("RTU frame: {frame}")

Compute CRC-16/Modbus checksum

Computes the CRC-16/Modbus checksum (polynomial 0xA001, initial value 0xFFFF) for the given byte sequence. Use to verify RTU frame integrity.

use plugin modbus::{crc16_modbus}

let data = [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A]
let crc = crc16_modbus(data)
print("CRC: {crc}")

Split the checksum into its on-wire little-endian bytes (low byte first) to match how encode_rtu_request appends it:

use plugin modbus::{crc16_modbus}

let crc = crc16_modbus([0x01, 0x06, 0x00, 0x01, 0x00, 0x03])
let lo = crc % 256
let hi = crc / 256
print("CRC bytes: {lo} {hi}")

Return human-readable name for a function code

Returns the human-readable name for a Modbus function code integer. Returns "Unknown" for unrecognised codes.

use plugin modbus::{function_name}

print(function_name(0x03))
print(function_name(0x10))
print(function_name(0xFF))

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

en