using System.Globalization; using CliFx.Attributes; using CliFx.Infrastructure; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Cli.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli.Commands; /// /// Write one value to a Logix tag by symbolic path. Mirrors 's /// flag shape + adds --value. Value parsing respects --type so you can /// write --value 3.14 --type Real without hex-encoding. GuardLogix safety tags /// are refused at the driver level (they're forced to ViewOnly by PR 12). /// [Command("write", Description = "Write a single Logix tag by symbolic path.")] public sealed class WriteCommand : AbCipCommandBase { [CommandOption("tag", 't', Description = "Logix symbolic path — same format as `read`.", IsRequired = true)] public string TagPath { get; init; } = default!; [CommandOption("type", Description = "Bool / SInt / Int / DInt / LInt / USInt / UInt / UDInt / ULInt / Real / LReal / " + "String / Dt (default DInt).")] public AbCipDataType DataType { get; init; } = AbCipDataType.DInt; [CommandOption("value", 'v', Description = "Value to write. Parsed per --type (booleans accept true/false/1/0).", IsRequired = true)] public string Value { get; init; } = default!; public override async ValueTask ExecuteAsync(IConsole console) { ConfigureLogging(); var ct = console.RegisterCancellationHandler(); if (DataType == AbCipDataType.Structure) throw new CliFx.Exceptions.CommandException( "Structure (UDT) writes need an explicit member layout — drop to the driver's " + "config JSON for those. The CLI covers atomic types only."); var tagName = ReadCommand.SynthesiseTagName(TagPath, DataType); var tag = new AbCipTagDefinition( Name: tagName, DeviceHostAddress: Gateway, TagPath: TagPath, DataType: DataType, Writable: true); var options = BuildOptions([tag]); var parsed = ParseValue(Value, DataType); await using var driver = new AbCipDriver(options, DriverInstanceId); try { await driver.InitializeAsync("{}", ct); var results = await driver.WriteAsync([new WriteRequest(tagName, parsed)], ct); await console.Output.WriteLineAsync(SnapshotFormatter.FormatWrite(TagPath, results[0])); } finally { await driver.ShutdownAsync(CancellationToken.None); } } /// /// Parse the operator's --value string into the CLR type the driver expects /// for the declared . Invariant culture everywhere. /// internal static object ParseValue(string raw, AbCipDataType type) => type switch { AbCipDataType.Bool => ParseBool(raw), AbCipDataType.SInt => sbyte.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.Int => short.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.DInt or AbCipDataType.Dt => int.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.LInt => long.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.USInt => byte.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.UInt => ushort.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.UDInt => uint.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.ULInt => ulong.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.Real => float.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.LReal => double.Parse(raw, CultureInfo.InvariantCulture), AbCipDataType.String => raw, _ => throw new CliFx.Exceptions.CommandException($"Unsupported DataType '{type}' for write."), }; private static bool ParseBool(string raw) => raw.Trim().ToLowerInvariant() switch { "1" or "true" or "on" or "yes" => true, "0" or "false" or "off" or "no" => false, _ => throw new CliFx.Exceptions.CommandException( $"Boolean value '{raw}' is not recognised. Use true/false, 1/0, on/off, or yes/no."), }; }