using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus; /// /// Modbus TCP driver configuration. Bound from the driver's DriverConfig JSON at /// DriverHost.RegisterAsync. Every register the driver exposes appears in /// ; names become the OPC UA browse name + full reference. /// public sealed class ModbusDriverOptions { public string Host { get; init; } = "127.0.0.1"; public int Port { get; init; } = 502; public byte UnitId { get; init; } = 1; public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); /// Pre-declared tag map. Modbus has no discovery protocol — the driver returns exactly these. public IReadOnlyList Tags { get; init; } = []; /// /// Background connectivity-probe settings. When /// is true the driver runs a tick loop that issues a cheap FC03 at register 0 every /// and raises OnHostStatusChanged on /// Running ↔ Stopped transitions. The Admin UI / OPC UA clients see the state through /// . /// public ModbusProbeOptions Probe { get; init; } = new(); /// /// Maximum registers per FC03 (Read Holding Registers) / FC04 (Read Input Registers) /// transaction. Modbus-TCP spec allows 125; many device families impose lower caps: /// AutomationDirect DL205/DL260 cap at 128, Mitsubishi Q/FX3U cap at 64, /// Omron CJ/CS cap at 125. Set to the lowest cap across the devices this driver /// instance talks to; the driver auto-chunks larger reads into consecutive requests. /// Default 125 — the spec maximum, safe against any conforming server. Setting /// to 0 disables the cap (discouraged — the spec upper bound still applies). /// public ushort MaxRegistersPerRead { get; init; } = 125; /// /// Maximum registers per FC16 (Write Multiple Registers) transaction. Spec maximum is /// 123; DL205/DL260 cap at 100. Matching caller-vs-device semantics: /// exceeding the cap currently throws (writes aren't auto-chunked because a partial /// write across two FC16 calls is no longer atomic — caller must explicitly opt in /// by shortening the tag's StringLength or splitting it into multiple tags). /// public ushort MaxRegistersPerWrite { get; init; } = 123; /// /// When true (default) the built-in detects /// mid-transaction socket failures (, /// ) and transparently reconnects + /// retries the PDU exactly once. Required for DL205/DL260 because the H2-ECOM100 /// does not send TCP keepalives — intermediate NAT / firewall devices silently close /// idle sockets and the first send after the drop would otherwise surface as a /// connection error to the caller even though the PLC is up. /// public bool AutoReconnect { get; init; } = true; } public sealed class ModbusProbeOptions { public bool Enabled { get; init; } = true; public TimeSpan Interval { get; init; } = TimeSpan.FromSeconds(5); public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(2); /// Register to read for the probe. Zero is usually safe; override for PLCs that lock register 0. public ushort ProbeAddress { get; init; } = 0; } /// /// One Modbus-backed OPC UA variable. Address is zero-based (Modbus spec numbering, not /// the documentation's 1-based coil/register conventions). Multi-register types /// (Int32/UInt32/Float32 = 2 regs; Int64/UInt64/Float64 = 4 regs) respect the /// field — real-world PLCs disagree on word ordering. /// /// /// Tag name, used for both the OPC UA browse name and the driver's full reference. Must be /// unique within the driver. /// /// Coils / DiscreteInputs / InputRegisters / HoldingRegisters. /// Zero-based address within the region. /// /// Logical data type. See for the register count each encodes. /// /// When true and Region supports writes (Coils / HoldingRegisters), IWritable routes writes here. /// Word ordering for multi-register types. Ignored for Bool / Int16 / UInt16 / BitInRegister / String. /// For DataType = BitInRegister: which bit of the holding register (0-15, LSB-first). /// For DataType = String: number of ASCII characters (2 per register, rounded up). /// /// Per-register byte order for DataType = String. Standard Modbus packs the first /// character in the high byte (). /// AutomationDirect DirectLOGIC (DL205/DL260) and a few legacy families pack the first /// character in the low byte instead — see docs/v2/dl205.md §strings. /// public sealed record ModbusTagDefinition( string Name, ModbusRegion Region, ushort Address, ModbusDataType DataType, bool Writable = true, ModbusByteOrder ByteOrder = ModbusByteOrder.BigEndian, byte BitIndex = 0, ushort StringLength = 0, ModbusStringByteOrder StringByteOrder = ModbusStringByteOrder.HighByteFirst); public enum ModbusRegion { Coils, DiscreteInputs, InputRegisters, HoldingRegisters } public enum ModbusDataType { Bool, Int16, UInt16, Int32, UInt32, Int64, UInt64, Float32, Float64, /// Single bit within a holding register. selects 0-15 LSB-first. BitInRegister, /// ASCII string packed 2 chars per register, characters long. String, /// /// 16-bit binary-coded decimal. Each nibble encodes one decimal digit (0-9). Register /// value 0x1234 decodes as decimal 1234 — NOT binary 0x04D2 = 4660. /// DL205/DL260 and several Mitsubishi / Omron families store timers, counters, and /// operator-facing numerics as BCD by default. /// Bcd16, /// /// 32-bit (two-register) BCD. Decodes 8 decimal digits. Word ordering follows /// the same way does. /// Bcd32, } /// /// Word ordering for multi-register types. Modbus TCP standard is /// (ABCD for 32-bit: high word at the lower address). Many PLCs — Siemens S7, several /// Allen-Bradley series, some Modicon families — use (CDAB), which /// keeps bytes big-endian within each register but reverses the word pair(s). /// public enum ModbusByteOrder { BigEndian, WordSwap, } /// /// Per-register byte order for ASCII strings packed 2 chars per register. Standard Modbus /// convention is — the first character of each pair occupies /// the high byte of the register. AutomationDirect DirectLOGIC (DL205, DL260, DL350) and a /// handful of legacy controllers pack , which inverts that within /// each register. Word ordering across multiple registers is always ascending address for /// strings — only the byte order inside each register flips. /// public enum ModbusStringByteOrder { HighByteFirst, LowByteFirst, }