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;
}