using CliFx.Attributes; using CliFx.Infrastructure; using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli.Commands; /// /// Read one Modbus register / coil. Operator specifies the address via /// --region + --address + --type; the CLI synthesises a single /// , spins up the driver, reads once, prints the snapshot, /// and shuts down. Multi-register types (Int32 / Float32 / String / BCD32) respect /// --byte-order the same way real driver configs do. /// [Command("read", Description = "Read a single Modbus register or coil.")] public sealed class ReadCommand : ModbusCommandBase { [CommandOption("region", 'r', Description = "Coils / DiscreteInputs / InputRegisters / HoldingRegisters", IsRequired = true)] public ModbusRegion Region { get; init; } [CommandOption("address", 'a', Description = "Zero-based address within the region.", IsRequired = true)] public ushort Address { get; init; } [CommandOption("type", 't', Description = "Bool / Int16 / UInt16 / Int32 / UInt32 / Int64 / UInt64 / Float32 / Float64 / " + "BitInRegister / String / Bcd16 / Bcd32", IsRequired = true)] public ModbusDataType DataType { get; init; } [CommandOption("byte-order", Description = "BigEndian (default, spec ABCD) or WordSwap (CDAB). Ignored for single-register types.")] public ModbusByteOrder ByteOrder { get; init; } = ModbusByteOrder.BigEndian; [CommandOption("bit-index", Description = "For type=BitInRegister: bit 0-15 LSB-first.")] public byte BitIndex { get; init; } [CommandOption("string-length", Description = "For type=String: character count (2 per register, rounded up).")] public ushort StringLength { get; init; } [CommandOption("string-byte-order", Description = "For type=String: HighByteFirst (standard) or LowByteFirst (DirectLOGIC et al).")] public ModbusStringByteOrder StringByteOrder { get; init; } = ModbusStringByteOrder.HighByteFirst; public override async ValueTask ExecuteAsync(IConsole console) { ConfigureLogging(); var ct = console.RegisterCancellationHandler(); var tagName = SynthesiseTagName(Region, Address, DataType); var tag = new ModbusTagDefinition( Name: tagName, Region: Region, Address: Address, DataType: DataType, Writable: false, ByteOrder: ByteOrder, BitIndex: BitIndex, StringLength: StringLength, StringByteOrder: StringByteOrder); var options = BuildOptions([tag]); await using var driver = new ModbusDriver(options, DriverInstanceId); try { await driver.InitializeAsync("{}", ct); var snapshot = await driver.ReadAsync([tagName], ct); await console.Output.WriteLineAsync(SnapshotFormatter.Format(tagName, snapshot[0])); } finally { await driver.ShutdownAsync(CancellationToken.None); } } /// /// Builds a human-readable tag name matching the operator's conceptual model /// (HR[100], Coil[5], IR[42]) — the driver treats the name /// purely as a lookup key, so any stable string works. /// internal static string SynthesiseTagName( ModbusRegion region, ushort address, ModbusDataType type) { var prefix = region switch { ModbusRegion.Coils => "Coil", ModbusRegion.DiscreteInputs => "DI", ModbusRegion.InputRegisters => "IR", ModbusRegion.HoldingRegisters => "HR", _ => "Reg", }; return $"{prefix}[{address}]:{type}"; } }