using ZB.MOM.WW.OtOpcUa.Driver.FOCAS; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared.Drivers.Pickers; /// /// The addressing spaces the manual FOCAS picker can compose. Mirrors the parseable subset of /// : is deliberately absent because a /// fixed-tree path is discovered from the machine, not composed from parts — the picker's browse /// tree supplies those. /// public enum FocasManualAddressKind { /// PMC area — letter + byte + optional bit. Pmc, /// CNC parameter number + optional bit. Parameter, /// Macro variable number. Macro, } /// /// Pure static helper that composes a canonical FOCAS address from the manual picker's parts. /// Extracted so unit tests can call it without bUnit. /// /// /// /// It builds through rather than formatting a string, so what /// it emits is parseable by construction and the two cannot drift. /// /// /// It previously emitted $"{group}:{parameterId}" over a group list of /// axis/spindle/program/status — none of which is a FOCAS addressing space — so the /// picker's own default value, axis:5, was an address the driver rejects. Together with /// the browse-commit defect this meant every UI-assisted authoring path produced unusable tags, /// and only hand-typed addresses worked at all (Gitea #540). /// /// public static class FocasAddressBuilder { /// The PMC area letters the driver accepts, in the order the picker offers them. public static IReadOnlyList PmcLetters { get; } = ["X", "Y", "F", "G", "R", "D", "C", "K", "A", "E", "T"]; /// /// Composes the canonical FOCAS address string, or an empty string when the parts do not form a /// valid address (an out-of-range bit, a negative number, an unknown PMC letter). /// /// Which addressing space the address is in. /// The PMC area letter; ignored unless is /// . /// The PMC byte, parameter number or macro number. /// Optional bit index — 0–7 for PMC, 0–31 for a parameter. Null for a /// whole-word address; always ignored for a macro, which has no bit form. /// The canonical address, or an empty string when the parts are not addressable. public static string Build( FocasManualAddressKind kind, string? pmcLetter, int number, int? bitIndex) { FocasAddress? candidate = kind switch { FocasManualAddressKind.Pmc => new FocasAddress( FocasAreaKind.Pmc, (pmcLetter ?? "").Trim().ToUpperInvariant(), number, bitIndex), FocasManualAddressKind.Parameter => new FocasAddress( FocasAreaKind.Parameter, null, number, bitIndex), FocasManualAddressKind.Macro => new FocasAddress( FocasAreaKind.Macro, null, number, null), _ => null, }; // Round-trip rather than trust the parts: the record accepts any values, the parser is the // authority on which of them address anything, and an empty result is what the picker shows // as "no address yet" instead of a plausible-looking string the driver would silently skip. return candidate?.Canonical is { } text && FocasAddress.TryParse(text) is not null ? text : ""; } }