fix(twincat): map TwinCAT 2 ADS base-type names in symbol browse

A TC2 PLC reports top-level integer globals as INT16/UINT16 (354 symbols on
the Elma cleaner Z33856) and the Beckhoff type system keeps that spelling as
a built-in primitive, so MapSymbolTypeName returned null and discovery dropped
them silently. INT16/UINT16/INT32/UINT32/UINT8/FLOAT/DOUBLE now map like their
IEC twins; padded member names ("REAL ") are trimmed; a parenthesised
non-string form ("INT (VAR_IN_OUT)", a reference) stays unsupported.
This commit is contained in:
Joseph Doherty
2026-10-07 13:59:40 -04:00
parent b7b754fd98
commit 1a74c3c2e0
2 changed files with 80 additions and 11 deletions
@@ -376,27 +376,36 @@ internal sealed class AdsTwinCATClient : ITwinCATClient
return (MapSymbolTypeName(dataType.Name), null);
}
private static TwinCATDataType? MapSymbolTypeName(string? typeName)
internal static TwinCATDataType? MapSymbolTypeName(string? typeName)
{
if (typeName is null) return null;
// A TwinCAT 2 data-type upload pads some member type names with a trailing blank
// ("REAL ", "DINT " on a TC 2.11 CX) — trim before matching.
var name = typeName.Trim();
// SymbolLoader emits STRING(80) / WSTRING(80) with the declared bound baked into
// the type name — strip the "(...)" suffix so sized strings map onto the bare
// String/WString atom the driver speaks.
var paren = typeName.IndexOf('(');
var bare = paren > 0 ? typeName[..paren] : typeName;
// String/WString atom the driver speaks. Only for the string kinds: any other
// parenthesised form ("INT (VAR_IN_OUT)" — a 4-byte reference on TC2) is not the
// scalar its prefix names and stays unsupported.
var paren = name.IndexOf('(');
var bare = paren > 0 ? name[..paren] : name;
if (paren > 0 && bare is not ("STRING" or "WSTRING")) return null;
return bare switch
{
"BOOL" or "BIT" => TwinCATDataType.Bool,
"SINT" or "BYTE" => TwinCATDataType.SInt,
"USINT" => TwinCATDataType.USInt,
"INT" or "WORD" => TwinCATDataType.Int,
"UINT" => TwinCATDataType.UInt,
"DINT" or "DWORD" => TwinCATDataType.DInt,
"UDINT" => TwinCATDataType.UDInt,
// TwinCAT 2 reports top-level globals by their ADS base-type name (INT16 / UINT16 / …),
// and the Beckhoff type system keeps that spelling as a built-in primitive
// (AdsSymbolProvider.CreateBuildInTypes) — map each like its IEC twin.
"USINT" or "UINT8" => TwinCATDataType.USInt,
"INT" or "WORD" or "INT16" => TwinCATDataType.Int,
"UINT" or "UINT16" => TwinCATDataType.UInt,
"DINT" or "DWORD" or "INT32" => TwinCATDataType.DInt,
"UDINT" or "UINT32" => TwinCATDataType.UDInt,
"LINT" or "LWORD" => TwinCATDataType.LInt,
"ULINT" => TwinCATDataType.ULInt,
"REAL" => TwinCATDataType.Real,
"LREAL" => TwinCATDataType.LReal,
"REAL" or "FLOAT" => TwinCATDataType.Real,
"LREAL" or "DOUBLE" => TwinCATDataType.LReal,
"STRING" => TwinCATDataType.String,
"WSTRING" => TwinCATDataType.WString,
"TIME" => TwinCATDataType.Time,