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:
@@ -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,
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins <c>AdsTwinCATClient.MapSymbolTypeName</c> — the symbol-browse type mapper — against the type
|
||||
/// names a real TwinCAT 2 PLC reports. A name the mapper does not know drops the symbol from
|
||||
/// discovery silently, so every TC2 spelling below was a symbol the browse never offered.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class TwinCATSymbolTypeNameTests
|
||||
{
|
||||
/// <summary>
|
||||
/// TwinCAT 2 reports top-level integer globals by their ADS base-type name, not the IEC name:
|
||||
/// the Elma cleaner Z33856 (TC 2.11, symbol upload 2026-09-24) lists 233 symbols as
|
||||
/// <c>INT16</c> and 121 as <c>UINT16</c> (e.g. <c>.ACTUALOPERATIONMODE</c>). The Beckhoff
|
||||
/// type system carries these as built-in primitives (<c>AdsSymbolProvider.CreateBuildInTypes</c>:
|
||||
/// INT16, UINT16, INT32, UINT32, UINT8, FLOAT, DOUBLE), so <c>IDataType.Name</c> arrives spelled
|
||||
/// that way and must map like its IEC twin.
|
||||
/// </summary>
|
||||
/// <param name="typeName">The type name as the PLC / Beckhoff type system reports it.</param>
|
||||
/// <param name="expected">The driver atomic type it must map to.</param>
|
||||
[Theory]
|
||||
[InlineData("INT16", TwinCATDataType.Int)]
|
||||
[InlineData("UINT16", TwinCATDataType.UInt)]
|
||||
[InlineData("INT32", TwinCATDataType.DInt)]
|
||||
[InlineData("UINT32", TwinCATDataType.UDInt)]
|
||||
[InlineData("UINT8", TwinCATDataType.USInt)]
|
||||
[InlineData("FLOAT", TwinCATDataType.Real)]
|
||||
[InlineData("DOUBLE", TwinCATDataType.LReal)]
|
||||
public void TwinCAT2_ads_base_type_names_map_like_their_iec_twins(string typeName, TwinCATDataType expected)
|
||||
=> AdsTwinCATClient.MapSymbolTypeName(typeName).ShouldBe(expected);
|
||||
|
||||
/// <summary>
|
||||
/// The same TC2 data-type upload spells struct members with a trailing blank — <c>"REAL "</c>
|
||||
/// (54 members on Z33856) and <c>"DINT "</c> (24) — so the name is trimmed before matching.
|
||||
/// </summary>
|
||||
/// <param name="typeName">A padded type name.</param>
|
||||
/// <param name="expected">The driver atomic type it must map to.</param>
|
||||
[Theory]
|
||||
[InlineData("REAL ", TwinCATDataType.Real)]
|
||||
[InlineData("DINT ", TwinCATDataType.DInt)]
|
||||
[InlineData(" STRING(80) ", TwinCATDataType.String)]
|
||||
public void Padded_type_names_are_trimmed(string typeName, TwinCATDataType expected)
|
||||
=> AdsTwinCATClient.MapSymbolTypeName(typeName).ShouldBe(expected);
|
||||
|
||||
/// <summary>The IEC spellings, sized strings and unsupported names are unchanged.</summary>
|
||||
[Fact]
|
||||
public void Existing_iec_mappings_are_unchanged()
|
||||
{
|
||||
AdsTwinCATClient.MapSymbolTypeName("INT").ShouldBe(TwinCATDataType.Int);
|
||||
AdsTwinCATClient.MapSymbolTypeName("STRING(8)").ShouldBe(TwinCATDataType.String);
|
||||
AdsTwinCATClient.MapSymbolTypeName("WSTRING(20)").ShouldBe(TwinCATDataType.WString);
|
||||
AdsTwinCATClient.MapSymbolTypeName("ST_LOGDATA").ShouldBeNull();
|
||||
AdsTwinCATClient.MapSymbolTypeName("INT (VAR_IN_OUT)").ShouldBeNull();
|
||||
AdsTwinCATClient.MapSymbolTypeName(null).ShouldBeNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user