An allocation-free Modbus client library for Zig 0.16
Find a file
2026-07-31 17:27:08 +02:00
src initial commit 2026-07-31 17:27:08 +02:00
build.zig initial commit 2026-07-31 17:27:08 +02:00
build.zig.zon initial commit 2026-07-31 17:27:08 +02:00
README.md initial commit 2026-07-31 17:27:08 +02:00

m_modbus

m_modbus is an allocation-free Modbus client library for Zig 0.16. It provides Modbus TCP and RTU transports with either blocking request/response operation or background timeout handling. The Timed* variants require an Io instance capable of spawning concurrent tasks.

Transport Types

  • SimpleModbusTCP
  • TimedModbusTCP(default_timeout_ms, max_concurrent, response_queue_size, timeout_poll_ms)
  • SimpleModbusRTU
  • TimedModbusRTU(default_timeout_ms, timeout_poll_ms)

Client Setup

const std = @import("std");
const modbus = @import("m_modbus");

const Transport = modbus.TimedModbusTCP(2_000, 8, 8, 10);
const Client = modbus.ModbusClient(Transport);

fn read_register(
    io: std.Io,
    reader: *std.Io.Reader,
    writer: *std.Io.Writer,
) !void {
    var client: Client = undefined;
    try client.init(io, reader, writer);
    defer client.deinit() catch {};

    const result = try client.transact(.{
        .unit_id = 1,
        .pdu = .{
            .read_holding_registers = .{
                .address = 0x0100,
                .quantity = 1,
            },
        },
    }, .{});

    switch (result) {
        .ok => |response| switch (response.pdu) {
            .normal => |normal| {
                const payload = normal.data.slice();
                _ = payload;
            },
            .exception => |exception| {
                _ = exception.function_code;
                _ = exception.exception_code;
            },
        },
        .timeout => |failure| {
            _ = failure.timeout_ms;
            _ = failure.request;
        },
    }
}

Scanning

The modbus client has a minimal api for scanning a range of unit ids.

// Currently, only sliced and ranged are implemented.
var sliced = Client.ModbusScanStateSliced.init(unit_ids, request_pdu, .{});

// Both endpoints are inclusive.
var ranged = try Client.ModbusScanStateRanged.init(
    1,
    255,
    request_pdu,
    .{},
);

while (try client.next(&ranged)) |result| {
    _ = result;
}