Summary
When a TwinCAT ENUM type has {attribute '...'} pragmas on individual members and the PLC runtime provides ExtendedEnumInfos (TC ≥ 4026), getDataTypes() incorrectly maps all per-member attributes to the first member (enumInfos[0]) instead of the member they belong to.
Root cause
In ads-client.js (v2.2.0) around line 2338, the attribute-parsing inner loop reuses let i as its loop variable:
// outer loop — member index
for (let i = 0; i < enumInfoCount; i++) {
// ...
dataType.enumInfos[i].attributes = [];
// inner loop — attribute index — shadows outer `i` !
for (let i = 0; i < attributeCount; i++) {
// ...
dataType.enumInfos[i].attributes.push(attr); // <-- inner i, not outer i
}
}
Inside the inner loop i refers to the attribute index, not the member index. So dataType.enumInfos[i].attributes.push(attr) pushes to enumInfos[0], enumInfos[1], … based on how many attributes the current member has — never to the correct member.
Reproduction
TwinCAT ENUM definition:
{attribute 'qualified_only'}
TYPE E_Color :
(
{attribute 'cold'}
White := 0,
Blue := 2,
{attribute 'warm'}
Red := 3
) INT;
END_TYPE
Expected getDataTypes() result for E_Color:
[
{ "name": "White", "value": 0, "attributes": [{ "name": "cold", "value": "" }] },
{ "name": "Blue", "value": 2, "attributes": [] },
{ "name": "Red", "value": 3, "attributes": [{ "name": "warm", "value": "" }] }
]
Actual result (ads-client v2.2.0):
[
{ "name": "White", "value": 0, "attributes": [{ "name": "cold", "value": "" }, { "name": "warm", "value": "" }] },
{ "name": "Blue", "value": 2, "attributes": [] },
{ "name": "Red", "value": 3, "attributes": [] }
]
Both attributes land on White (index 0) because the inner i cycles 0…1 for the two total attributes found, always addressing enumInfos[0] and enumInfos[1].
The raw ADS DataType binary is correct — confirmed by manually parsing the 0xF00E upload with independent code: White has attributeCount=1 (cold), Red has attributeCount=1 (warm).
Fix
Rename the inner loop variable so it doesn't shadow the outer member index:
- for (let i = 0; i < attributeCount; i++) {
+ for (let ai = 0; ai < attributeCount; ai++) {
const attr = {};
// ...
- dataType.enumInfos[i].attributes.push(attr);
+ dataType.enumInfos[i].attributes.push(attr); // outer i (member index) is now correct
}
Environment
- ads-client: 2.2.0
- TwinCAT: 3.1.4026.x
- Node.js: 20 LTS
Summary
When a TwinCAT ENUM type has
{attribute '...'}pragmas on individual members and the PLC runtime providesExtendedEnumInfos(TC ≥ 4026),getDataTypes()incorrectly maps all per-member attributes to the first member (enumInfos[0]) instead of the member they belong to.Root cause
In
ads-client.js(v2.2.0) around line 2338, the attribute-parsing inner loop reuseslet ias its loop variable:Inside the inner loop
irefers to the attribute index, not the member index. SodataType.enumInfos[i].attributes.push(attr)pushes toenumInfos[0],enumInfos[1], … based on how many attributes the current member has — never to the correct member.Reproduction
TwinCAT ENUM definition:
Expected
getDataTypes()result forE_Color:[ { "name": "White", "value": 0, "attributes": [{ "name": "cold", "value": "" }] }, { "name": "Blue", "value": 2, "attributes": [] }, { "name": "Red", "value": 3, "attributes": [{ "name": "warm", "value": "" }] } ]Actual result (ads-client v2.2.0):
[ { "name": "White", "value": 0, "attributes": [{ "name": "cold", "value": "" }, { "name": "warm", "value": "" }] }, { "name": "Blue", "value": 2, "attributes": [] }, { "name": "Red", "value": 3, "attributes": [] } ]Both attributes land on
White(index 0) because the innericycles 0…1 for the two total attributes found, always addressingenumInfos[0]andenumInfos[1].The raw ADS DataType binary is correct — confirmed by manually parsing the
0xF00Eupload with independent code:WhitehasattributeCount=1(cold),RedhasattributeCount=1(warm).Fix
Rename the inner loop variable so it doesn't shadow the outer member index:
Environment