Zig library for working with Hamilton ARC probes
Find a file
2026-07-31 17:41:52 +02:00
examples initial commit 2026-07-31 04:41:34 +02:00
src initial commit 2026-07-31 04:41:34 +02:00
build.zig initial commit 2026-07-31 04:41:34 +02:00
build.zig.zon point to m_modbus repository 2026-07-31 17:41:52 +02:00
README.md initial commit 2026-07-31 04:41:34 +02:00

m_hamilton_arc

Zig library for working with Hamilton ARC probes through m_modbus.

The library provides a thread-safe manager that:

  • discovers probes in a configured Modbus unit-id range
  • polls primary and secondary measurements
  • publishes probe discovery and measurement changes through optional queues
  • exposes guarded direct access to the latest probe store
  • exits on unrecoverable transport errors such as connection loss

Status

This is an early library. The core manager API is in place, but details may still change.

The library currently reads:

  • probe serial-number presence for discovery
  • sensor identification fields
  • advertised primary and secondary measurement channels
  • primary measurement descriptions
  • primary measurement available physical units
  • secondary measurement descriptions
  • primary measurement values, units, and ranges
  • secondary measurement values, units, and standard deviations

Required Zig version:

0.16.0

Basic Usage

const std = @import("std");
const m_hamilton_arc = @import("m_hamilton_arc");
const m_modbus = m_hamilton_arc.m_modbus;

// 1) Pick a transport type
// The HamiltonArcManager requires a ModbusTransport type capable
// of timed requests. This in turn requires an Io instance capable
// of concurrent tasks.

// use modbus rtu
const Transport = m_modbus.TimedModbusRTU(
    2_000, // timeoutDurationMs
    30,    // timeoutTaskSleepDuration
);

// or use modbus tcp
const Transport = m_modbus.TimedModbusTCP(
    2_000, // timeoutDurationMs
    8,     // maxConcurrentTransactions
    4,     // responseQueueSize
    30,    // timeoutTaskSleepDuration
);

// Initialize the client with a reader and writer
var client: m_modbus.ModbusClient(Transport) = try .init(io, reader, writer);

// 2) Pick a manager strategy.
// The management strategy decides the behavior of the manager. 
// This includes when to scan, how to scan, when to perform measurements, etc.

// Currently, only the 'simple' strategy exists
const Strategy = m_hamilton_arc.simple_strategy.SimpleManagerStrategy;

// 3) Set options and Queues

// ManagerOptions define general options for the manager
const manager_options: m_hamilton_arc.ManagerOptions = .{
    // What modbus ids the manager considers the 'full range'.
    // If you are absolutely certain that the probes you are attempting
    // to find are within a certain range, setting this option to that 
    // range can help improve connection latency.
    .probe_scan_range = .{
        .first_unit_id = 1,
        .last_unit_id = 10,
    },
};

// StrategyOptions are specific to the strategy chosen.
const strategy_options: HamiltonArcManager.StrategyOptions = .{
    .loop_delay = .fromMilliseconds(100),
    .missing_probe_scan_chunk_size = 1,
    .primary_measurement_interval = .fromSeconds(1),
    .secondary_measurement_interval = .fromSeconds(5),
    .rediscover_after_measurement_failures = 3,
};

// ManagerQueues define what queues the manager sends it's events on.
// These queues are optional, simply reading the internal probe store also works.
// `ManagerQueues` can contain:
// - `discovery_queue`
// - `primary_measurement_queue`
// - `secondary_measurement_queue`
const manager_queues = .{};

// 4) Start the manager
// The manager has two entry points:
// - `start(...)` starts the manager loop in a concurrent task
// - `manage(...)` runs the manager loop in the current task

// First, initialize the manager
var manager: m_hamilton_arc.HamiltonArcManager(Transport, Strategy) = .init(io, &client);
// Then start it. This returns an Io.Future(anyerror!void)
const handle = try manager.start(io, manager_options, strategy_options, manager_queues);
defer future.cancel(io) catch {};

// 5) Wait for changes, and read out new data.

var generation: u64 = 0;

while (true) {
    var guard = try manager.wait_for_probe_store_change(generation);
    defer guard.unlock();

    generation = guard.generation;
    var probe_iterator = guard.store.iterator();
    while (probe_iterator.next()) |probe| {
        std.log.info("probe {d}: {s} serial {s}", .{
            probe.unit_id,
            &probe.sensor_identification.sensor_name,
            &probe.sensor_identification.sensor_sn,
        });
    }
}