namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; /// /// Serves a bit-indexed BOOL tag (MAIN.Flags.3, .ADSSTATE_RUN.0) from its PARENT word. /// ADS has no symbol for WORD.N — live on a TwinCAT 2.11 target a notification registered on /// .ADSSTATE_RUN.0 failed with DeviceSymbolNotFound — so a native notification registers /// the parent as raw bytes at its resolved width and reads the bit here, exactly as a read already /// reads the parent word and extracts the bit. /// internal static class TwinCATBitAccess { /// The parent word's path: the bit path without its trailing .N. /// The bit path (MAIN.Flags.3). /// The parent path, or when it has no numeric last segment. public static string ParentPath(string symbolPath) { var lastDot = symbolPath.LastIndexOf('.'); if (lastDot < 0) return symbolPath; return int.TryParse(symbolPath.AsSpan(lastDot + 1), out _) ? symbolPath[..lastDot] : symbolPath; } /// Checks that addresses a bit of an integer parent word. /// The bit index. /// The parent symbol's shape. /// null when addressable; otherwise the reason. public static string? CheckParent(int bit, TwinCATPlcSymbolShape parent) { if (parent.Kind != TwinCATPlcKind.Scalar || parent.IsFloat) return $"bit {bit} of {parent.TypeName} ({parent.ByteSize} B) — only an integer word has addressable bits"; return bit >= 0 && bit < parent.ByteSize * 8 ? null : $"bit {bit} is outside the {parent.ByteSize * 8}-bit parent {parent.TypeName}"; } /// Reads bit of a little-endian parent word. /// The parent word's raw bytes. /// The bit index (0 = least significant bit of the first byte). /// The bit's value. public static bool ReadBit(ReadOnlySpan parent, int bit) => (parent[bit / 8] & (1 << (bit % 8))) != 0; }