using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy; /// /// PCCC data types that map onto SLC / MicroLogix / PLC-5 files. Narrower than Logix — no /// symbolic UDTs; every type is file-typed and fixed-width. /// public enum AbLegacyDataType { /// B-file single bit (B3:0/0) or bit-within-N-file (N7:0/3). Bit, /// N-file integer (signed 16-bit). Int, /// L-file long integer — SLC 5/05+ only (signed 32-bit). Long, /// F-file float (32-bit IEEE-754). Float, /// A-file analog integer — some older hardware (signed 16-bit, semantically like N). AnalogInt, /// ST-file string (82-byte fixed-length + length word header). String, /// Timer sub-element — caller addresses .ACC, .PRE, .EN, .DN, .TT. TimerElement, /// Counter sub-element — caller addresses .ACC, .PRE, .CU, .CD, .DN. CounterElement, /// Control sub-element — caller addresses .LEN, .POS, .EN, .DN, .ER. ControlElement, /// /// MicroLogix 1100/1400 function-file sub-element (RTC/HSC/DLS/MMI/PTO/PWM/STI/EII/IOS/BHI). /// Sub-element catalogue lives in . /// MicroLogixFunctionFile, /// /// PD-file (PID) sub-element — caller addresses .SP, .PV, .CV, /// .KP, .KI, .KD, .MAXS, .MINS, .DB, .OUT /// (Float) and .EN, .DN, .MO, .PE, .AUTO, .MAN /// (Boolean status bits in word 0). /// PidElement, /// /// MG-file (Message) sub-element — caller addresses .RBE, .MS, .SIZE, /// .LEN (Int32) and .EN, .EW, .ER, .DN, .ST, /// .CO, .NR, .TO (Boolean status bits). /// MessageElement, /// /// PLS-file (Programmable Limit Switch) sub-element — caller addresses .LEN /// (Int32). Bit semantics vary by PLC; unknown sub-elements fall back to Int32. /// PlsElement, /// /// BT-file (Block Transfer) sub-element — caller addresses .RLEN, .DLEN /// (Int32) and .EN, .ST, .DN, .ER, .CO, .EW, /// .TO, .NR (Boolean status bits in word 0). /// BlockTransferElement, } /// /// MicroLogix function-file sub-element catalogue. Covers the most-commonly-addressed members /// per file — not exhaustive (Rockwell defines 30+ on RTC alone). Unknown sub-elements fall /// back to at the /// boundary so the driver never refuses a tag the customer happens to know about. /// public static class AbLegacyFunctionFile { /// /// Driver-surface type for .. /// Returns if the sub-element is unrecognised — keeps /// the driver permissive without forcing every quirk into the catalogue. /// public static DriverDataType SubElementType(string fileLetter, string? subElement) { if (subElement is null) return DriverDataType.Int32; var key = (fileLetter.ToUpperInvariant(), subElement.ToUpperInvariant()); return key switch { // Real-time clock — all stored as Int16 (year is 4-digit Int16). ("RTC", "HR") or ("RTC", "MIN") or ("RTC", "SEC") or ("RTC", "MON") or ("RTC", "DAY") or ("RTC", "YR") or ("RTC", "DOW") => DriverDataType.Int32, ("RTC", "DS") or ("RTC", "BL") or ("RTC", "EN") => DriverDataType.Boolean, // High-speed counter — accumulator/preset are Int32, status flags are bits. ("HSC", "ACC") or ("HSC", "PRE") or ("HSC", "OVF") or ("HSC", "UNF") => DriverDataType.Int32, ("HSC", "EN") or ("HSC", "UF") or ("HSC", "IF") or ("HSC", "IN") or ("HSC", "IH") or ("HSC", "IL") or ("HSC", "DN") or ("HSC", "CD") or ("HSC", "CU") => DriverDataType.Boolean, // Daylight saving + memory module info. ("DLS", "STR") or ("DLS", "STD") => DriverDataType.Int32, ("DLS", "EN") => DriverDataType.Boolean, ("MMI", "FT") or ("MMI", "LBN") => DriverDataType.Int32, ("MMI", "MP") or ("MMI", "MCP") => DriverDataType.Boolean, // Pulse-train / PWM output blocks. ("PTO", "ACC") or ("PTO", "OF") or ("PTO", "IDA") or ("PTO", "ODA") => DriverDataType.Int32, ("PTO", "EN") or ("PTO", "DN") or ("PTO", "EH") or ("PTO", "ED") or ("PTO", "RP") or ("PTO", "OUT") => DriverDataType.Boolean, ("PWM", "ACC") or ("PWM", "OF") or ("PWM", "PE") or ("PWM", "PD") => DriverDataType.Int32, ("PWM", "EN") or ("PWM", "DN") or ("PWM", "EH") or ("PWM", "ED") or ("PWM", "RP") or ("PWM", "OUT") => DriverDataType.Boolean, // Selectable timed interrupt + event input interrupt. ("STI", "SPM") or ("STI", "ER") or ("STI", "PFN") => DriverDataType.Int32, ("STI", "EN") or ("STI", "TIE") or ("STI", "DN") or ("STI", "PS") or ("STI", "ED") => DriverDataType.Boolean, ("EII", "PFN") or ("EII", "ER") => DriverDataType.Int32, ("EII", "EN") or ("EII", "TIE") or ("EII", "PE") or ("EII", "ES") or ("EII", "ED") => DriverDataType.Boolean, // I/O status + base hardware info — mostly status flags + a few counters. ("IOS", "ID") or ("IOS", "TYP") => DriverDataType.Int32, ("BHI", "OS") or ("BHI", "FRN") or ("BHI", "BSN") or ("BHI", "CC") => DriverDataType.Int32, _ => DriverDataType.Int32, }; } } /// Map a PCCC data type to the driver-surface . public static class AbLegacyDataTypeExtensions { public static DriverDataType ToDriverDataType(this AbLegacyDataType t) => t switch { AbLegacyDataType.Bit => DriverDataType.Boolean, AbLegacyDataType.Int or AbLegacyDataType.AnalogInt => DriverDataType.Int32, AbLegacyDataType.Long => DriverDataType.Int32, // matches Modbus/AbCip 64→32 gap AbLegacyDataType.Float => DriverDataType.Float32, AbLegacyDataType.String => DriverDataType.String, AbLegacyDataType.TimerElement or AbLegacyDataType.CounterElement or AbLegacyDataType.ControlElement => DriverDataType.Int32, AbLegacyDataType.MicroLogixFunctionFile => DriverDataType.Int32, // PD/MG/PLS/BT default to Int32 at the parent-element level. The sub-element-aware // EffectiveDriverDataType refines specific members (Float for PID gains, Boolean for // status bits). AbLegacyDataType.PidElement or AbLegacyDataType.MessageElement or AbLegacyDataType.PlsElement or AbLegacyDataType.BlockTransferElement => DriverDataType.Int32, _ => DriverDataType.Int32, }; /// /// Sub-element-aware driver type. Timer/Counter/Control elements expose Boolean status /// bits (.DN, .EN, .TT, .CU, .CD, .OV, /// .UN, .ER, etc.) and Int32 word members (.PRE, .ACC, /// .LEN, .POS). Unknown sub-elements fall back to /// so the driver remains permissive. /// public static DriverDataType EffectiveDriverDataType(AbLegacyDataType t, string? subElement) { if (subElement is null) return t.ToDriverDataType(); var key = subElement.ToUpperInvariant(); return t switch { AbLegacyDataType.TimerElement => key switch { "EN" or "TT" or "DN" => DriverDataType.Boolean, "PRE" or "ACC" => DriverDataType.Int32, _ => t.ToDriverDataType(), }, AbLegacyDataType.CounterElement => key switch { "CU" or "CD" or "DN" or "OV" or "UN" => DriverDataType.Boolean, "PRE" or "ACC" => DriverDataType.Int32, _ => t.ToDriverDataType(), }, AbLegacyDataType.ControlElement => key switch { "EN" or "EU" or "DN" or "EM" or "ER" or "UL" or "IN" or "FD" => DriverDataType.Boolean, "LEN" or "POS" => DriverDataType.Int32, _ => t.ToDriverDataType(), }, // PD-file (PID): SP/PV/CV/KP/KI/KD/MAXS/MINS/DB/OUT are 32-bit floats; EN/DN/MO/PE/ // AUTO/MAN/SP_VAL/SP_LL/SP_HL are status bits in word 0. AbLegacyDataType.PidElement => key switch { "SP" or "PV" or "CV" or "KP" or "KI" or "KD" or "MAXS" or "MINS" or "DB" or "OUT" => DriverDataType.Float32, "EN" or "DN" or "MO" or "PE" or "AUTO" or "MAN" or "SP_VAL" or "SP_LL" or "SP_HL" => DriverDataType.Boolean, _ => t.ToDriverDataType(), }, // MG-file (Message): RBE/MS/SIZE/LEN are control words; EN/EW/ER/DN/ST/CO/NR/TO are // status bits. AbLegacyDataType.MessageElement => key switch { "RBE" or "MS" or "SIZE" or "LEN" => DriverDataType.Int32, "EN" or "EW" or "ER" or "DN" or "ST" or "CO" or "NR" or "TO" => DriverDataType.Boolean, _ => t.ToDriverDataType(), }, // PLS-file (Programmable Limit Switch): LEN is a length word; bit semantics vary by // PLC so unknown sub-elements stay Int32. AbLegacyDataType.PlsElement => key switch { "LEN" => DriverDataType.Int32, _ => t.ToDriverDataType(), }, // BT-file (Block Transfer, PLC-5): RLEN/DLEN are length words; EN/ST/DN/ER/CO/EW/ // TO/NR are status bits in word 0. AbLegacyDataType.BlockTransferElement => key switch { "RLEN" or "DLEN" => DriverDataType.Int32, "EN" or "ST" or "DN" or "ER" or "CO" or "EW" or "TO" or "NR" => DriverDataType.Boolean, _ => t.ToDriverDataType(), }, _ => t.ToDriverDataType(), }; } /// /// Bit position within the parent control word for Timer/Counter/Control status bits. /// Returns null if the sub-element is not a known bit member of the given element /// type. Bit numbering follows Rockwell DTAM / PCCC documentation. /// public static int? StatusBitIndex(AbLegacyDataType t, string? subElement) { if (subElement is null) return null; var key = subElement.ToUpperInvariant(); return t switch { // T4 element word 0: bit 13=DN, 14=TT, 15=EN. AbLegacyDataType.TimerElement => key switch { "DN" => 13, "TT" => 14, "EN" => 15, _ => null, }, // C5 element word 0: bit 10=UN, 11=OV, 12=DN, 13=CD, 14=CU. AbLegacyDataType.CounterElement => key switch { "UN" => 10, "OV" => 11, "DN" => 12, "CD" => 13, "CU" => 14, _ => null, }, // R6 element word 0: bit 8=FD, 9=IN, 10=UL, 11=ER, 12=EM, 13=DN, 14=EU, 15=EN. AbLegacyDataType.ControlElement => key switch { "FD" => 8, "IN" => 9, "UL" => 10, "ER" => 11, "EM" => 12, "DN" => 13, "EU" => 14, "EN" => 15, _ => null, }, // PD element word 0 (SLC 5/02+ PID, 1747-RM001 / PLC-5 PID-RM): bit 0=EN, 1=PE, // 2=DN, 3=MO (manual mode), 4=AUTO, 5=MAN, 6=SP_VAL, 7=SP_LL, 8=SP_HL. Bits 4–8 are // the SP-validity / SP-limit flags exposed in RSLogix 5 / 500. AbLegacyDataType.PidElement => key switch { "EN" => 0, "PE" => 1, "DN" => 2, "MO" => 3, "AUTO" => 4, "MAN" => 5, "SP_VAL" => 6, "SP_LL" => 7, "SP_HL" => 8, _ => null, }, // MG element word 0 (PLC-5 MSG / SLC 5/05 MSG, 1785-6.5.12 / 1747-RM001): // bit 15=EN, 14=ST, 13=DN, 12=ER, 11=CO, 10=EW, 9=NR, 8=TO. AbLegacyDataType.MessageElement => key switch { "TO" => 8, "NR" => 9, "EW" => 10, "CO" => 11, "ER" => 12, "DN" => 13, "ST" => 14, "EN" => 15, _ => null, }, // BT element word 0 (PLC-5 chassis BTR/BTW, 1785-6.5.12): // bit 15=EN, 14=ST, 13=DN, 12=ER, 11=CO, 10=EW, 9=NR, 8=TO. Same layout as MG. AbLegacyDataType.BlockTransferElement => key switch { "TO" => 8, "NR" => 9, "EW" => 10, "CO" => 11, "ER" => 12, "DN" => 13, "ST" => 14, "EN" => 15, _ => null, }, _ => null, }; } /// /// PLC-set status bits — read-only from the OPC UA side. Operator-controllable bits /// (e.g. .EN on a timer/counter, .CU/.CD rung-driven inputs) are /// omitted so they keep default writable behaviour. /// public static bool IsPlcSetStatusBit(AbLegacyDataType t, string? subElement) { if (subElement is null) return false; var key = subElement.ToUpperInvariant(); return t switch { AbLegacyDataType.TimerElement => key is "DN" or "TT", AbLegacyDataType.CounterElement => key is "DN" or "OV" or "UN", AbLegacyDataType.ControlElement => key is "DN" or "EM" or "ER" or "FD" or "UL" or "IN", // PID: PE (PID-error), DN (process-done), SP_VAL/SP_LL/SP_HL are PLC-set status. // EN/MO/AUTO/MAN are operator-controllable via the .EN bit / mode select. AbLegacyDataType.PidElement => key is "PE" or "DN" or "SP_VAL" or "SP_LL" or "SP_HL", // MG/BT: ST (started), DN (done), ER (error), CO (continuous), EW (enabled-waiting), // NR (no-response), TO (timeout) are PLC-set. EN is operator-driven via the rung. AbLegacyDataType.MessageElement => key is "ST" or "DN" or "ER" or "CO" or "EW" or "NR" or "TO", AbLegacyDataType.BlockTransferElement => key is "ST" or "DN" or "ER" or "CO" or "EW" or "NR" or "TO", _ => false, }; } }