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.TwinCAT.Cli.Commands; /// /// Write one value to a TwinCAT symbol. Structure writes refused — drop to driver config /// JSON for those. /// [Command("write", Description = "Write a single TwinCAT symbol.")] public sealed class WriteCommand : TwinCATCommandBase { [CommandOption("symbol", 's', Description = "Symbol path — same format as `read`.", IsRequired = true)] public string SymbolPath { get; init; } = default!; [CommandOption("type", 't', Description = "Bool / SInt / USInt / Int / UInt / DInt / UDInt / LInt / ULInt / Real / LReal / " + "String / WString / Time / Date / DateTime / TimeOfDay (default DInt).")] public TwinCATDataType DataType { get; init; } = TwinCATDataType.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 == TwinCATDataType.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(SymbolPath, DataType); var tag = new TwinCATTagDefinition( Name: tagName, DeviceHostAddress: Gateway, SymbolPath: SymbolPath, DataType: DataType, Writable: true); var options = BuildOptions([tag]); var parsed = ParseValue(Value, DataType); await using var driver = new TwinCATDriver(options, DriverInstanceId); try { await driver.InitializeAsync("{}", ct); var results = await driver.WriteAsync([new WriteRequest(tagName, parsed)], ct); await console.Output.WriteLineAsync(SnapshotFormatter.FormatWrite(SymbolPath, results[0])); } finally { await driver.ShutdownAsync(CancellationToken.None); } } /// Parse --value per , invariant culture. internal static object ParseValue(string raw, TwinCATDataType type) => type switch { TwinCATDataType.Bool => ParseBool(raw), TwinCATDataType.SInt => sbyte.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.USInt => byte.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.Int => short.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.UInt => ushort.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.DInt => int.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.UDInt => uint.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.LInt => long.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.ULInt => ulong.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.Real => float.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.LReal => double.Parse(raw, CultureInfo.InvariantCulture), TwinCATDataType.String or TwinCATDataType.WString => raw, // IEC 61131-3 time/date types are stored as UDINT on the wire — accept a numeric raw // value + let the caller handle the encoding semantics. TwinCATDataType.Time or TwinCATDataType.Date or TwinCATDataType.DateTime or TwinCATDataType.TimeOfDay => uint.Parse(raw, CultureInfo.InvariantCulture), _ => 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."), }; }