c89f5bb3b9
Closes #246
212 lines
9.6 KiB
C#
212 lines
9.6 KiB
C#
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public enum AbLegacyDataType
|
|
{
|
|
/// <summary>B-file single bit (<c>B3:0/0</c>) or bit-within-N-file (<c>N7:0/3</c>).</summary>
|
|
Bit,
|
|
/// <summary>N-file integer (signed 16-bit).</summary>
|
|
Int,
|
|
/// <summary>L-file long integer — SLC 5/05+ only (signed 32-bit).</summary>
|
|
Long,
|
|
/// <summary>F-file float (32-bit IEEE-754).</summary>
|
|
Float,
|
|
/// <summary>A-file analog integer — some older hardware (signed 16-bit, semantically like N).</summary>
|
|
AnalogInt,
|
|
/// <summary>ST-file string (82-byte fixed-length + length word header).</summary>
|
|
String,
|
|
/// <summary>Timer sub-element — caller addresses <c>.ACC</c>, <c>.PRE</c>, <c>.EN</c>, <c>.DN</c>, <c>.TT</c>.</summary>
|
|
TimerElement,
|
|
/// <summary>Counter sub-element — caller addresses <c>.ACC</c>, <c>.PRE</c>, <c>.CU</c>, <c>.CD</c>, <c>.DN</c>.</summary>
|
|
CounterElement,
|
|
/// <summary>Control sub-element — caller addresses <c>.LEN</c>, <c>.POS</c>, <c>.EN</c>, <c>.DN</c>, <c>.ER</c>.</summary>
|
|
ControlElement,
|
|
/// <summary>
|
|
/// MicroLogix 1100/1400 function-file sub-element (RTC/HSC/DLS/MMI/PTO/PWM/STI/EII/IOS/BHI).
|
|
/// Sub-element catalogue lives in <see cref="AbLegacyFunctionFile.SubElementType"/>.
|
|
/// </summary>
|
|
MicroLogixFunctionFile,
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="DriverDataType.Int32"/> at the <see cref="AbLegacyDataTypeExtensions"/>
|
|
/// boundary so the driver never refuses a tag the customer happens to know about.
|
|
/// </summary>
|
|
public static class AbLegacyFunctionFile
|
|
{
|
|
/// <summary>
|
|
/// Driver-surface type for <paramref name="fileLetter"/>.<paramref name="subElement"/>.
|
|
/// Returns <see cref="DriverDataType.Int32"/> if the sub-element is unrecognised — keeps
|
|
/// the driver permissive without forcing every quirk into the catalogue.
|
|
/// </summary>
|
|
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,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>Map a PCCC data type to the driver-surface <see cref="DriverDataType"/>.</summary>
|
|
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,
|
|
_ => DriverDataType.Int32,
|
|
};
|
|
|
|
/// <summary>
|
|
/// Sub-element-aware driver type. Timer/Counter/Control elements expose Boolean status
|
|
/// bits (<c>.DN</c>, <c>.EN</c>, <c>.TT</c>, <c>.CU</c>, <c>.CD</c>, <c>.OV</c>,
|
|
/// <c>.UN</c>, <c>.ER</c>, etc.) and Int32 word members (<c>.PRE</c>, <c>.ACC</c>,
|
|
/// <c>.LEN</c>, <c>.POS</c>). Unknown sub-elements fall back to
|
|
/// <see cref="ToDriverDataType"/> so the driver remains permissive.
|
|
/// </summary>
|
|
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(),
|
|
},
|
|
_ => t.ToDriverDataType(),
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bit position within the parent control word for Timer/Counter/Control status bits.
|
|
/// Returns <c>null</c> if the sub-element is not a known bit member of the given element
|
|
/// type. Bit numbering follows Rockwell DTAM / PCCC documentation.
|
|
/// </summary>
|
|
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,
|
|
},
|
|
_ => null,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// PLC-set status bits — read-only from the OPC UA side. Operator-controllable bits
|
|
/// (e.g. <c>.EN</c> on a timer/counter, <c>.CU</c>/<c>.CD</c> rung-driven inputs) are
|
|
/// omitted so they keep default writable behaviour.
|
|
/// </summary>
|
|
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",
|
|
_ => false,
|
|
};
|
|
}
|
|
}
|