Skip to content

Commit a282533

Browse files
committed
fix: Addressed the complex tag address issues. Now the full testsuite passes on a real device.
1 parent 72aaff5 commit a282533

11 files changed

Lines changed: 1866 additions & 348 deletions

File tree

plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/AdsTcpConnection.java

Lines changed: 309 additions & 345 deletions
Large diffs are not rendered by default.
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.plc4x.java.ads.resolution;
20+
21+
import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
22+
23+
import java.util.ArrayList;
24+
import java.util.Collections;
25+
import java.util.List;
26+
27+
/**
28+
* Parses TwinCAT-style symbolic addresses into a recursive tree of segments.
29+
*
30+
* <p>An address like {@code MAIN.g_plant.channels[1].setpoints[4]} parses into:
31+
* <pre>
32+
* AddressPart(baseSegment="MAIN.g_plant", indices=[],
33+
* child=AddressPart("channels", [1],
34+
* child=AddressPart("setpoints", [4], child=null)))
35+
* </pre>
36+
* The first {@code .} separates the namespace prefix (typically {@code MAIN}) from
37+
* the variable name; both join to form the symbol-table lookup key.
38+
*/
39+
public final class AddressParser {
40+
41+
private AddressParser() {
42+
}
43+
44+
/**
45+
* Address segment: a field name, zero or more {@code [i]} indices, and an optional child.
46+
*
47+
* @param baseSegment Field-name part of the segment (or the symbol name for the root).
48+
* @param arrayIndices Indices in source order — {@code [1][2][3]} → {@code [1, 2, 3]}.
49+
* @param child Child segment, or {@code null} when this is the leaf.
50+
*/
51+
public record AddressPart(String baseSegment, List<Integer> arrayIndices, AddressPart child) {
52+
}
53+
54+
/**
55+
* Parse a full symbolic address. Throws {@link PlcInvalidTagException} on syntactic problems.
56+
*/
57+
public static AddressPart parse(String symbolicAddress) {
58+
if (symbolicAddress == null || symbolicAddress.isEmpty()) {
59+
throw new PlcInvalidTagException("Empty symbolic address");
60+
}
61+
int dot = symbolicAddress.indexOf('.');
62+
if (dot < 0) {
63+
// No namespace prefix — uncommon, but accept it.
64+
return new AddressPart(extractField(symbolicAddress), extractIndices(symbolicAddress), null);
65+
}
66+
String rootName = symbolicAddress.substring(0, dot);
67+
AddressPart rest = parseRest(symbolicAddress.substring(dot + 1));
68+
// Glue the namespace onto the first segment so that the symbol-table lookup key is
69+
// formed (e.g. "MAIN" + "." + "g_plant" → "MAIN.g_plant").
70+
return new AddressPart(rootName + "." + rest.baseSegment, rest.arrayIndices, rest.child);
71+
}
72+
73+
private static AddressPart parseRest(String s) {
74+
int dot = s.indexOf('.');
75+
if (dot < 0) {
76+
return new AddressPart(extractField(s), extractIndices(s), null);
77+
}
78+
String head = s.substring(0, dot);
79+
AddressPart child = parseRest(s.substring(dot + 1));
80+
return new AddressPart(extractField(head), extractIndices(head), child);
81+
}
82+
83+
private static String extractField(String segment) {
84+
int bracket = segment.indexOf('[');
85+
return bracket >= 0 ? segment.substring(0, bracket) : segment;
86+
}
87+
88+
private static List<Integer> extractIndices(String segment) {
89+
int bracket = segment.indexOf('[');
90+
if (bracket < 0) {
91+
return Collections.emptyList();
92+
}
93+
List<Integer> indices = new ArrayList<>();
94+
int start = bracket;
95+
while (true) {
96+
int open = segment.indexOf('[', start);
97+
if (open < 0) {
98+
return indices;
99+
}
100+
int close = segment.indexOf(']', open);
101+
if (close < 0) {
102+
throw new PlcInvalidTagException("Unmatched '[' in address segment: " + segment);
103+
}
104+
String num = segment.substring(open + 1, close);
105+
try {
106+
indices.add(Integer.parseInt(num.trim()));
107+
} catch (NumberFormatException e) {
108+
throw new PlcInvalidTagException("Non-integer array index in segment: " + segment);
109+
}
110+
start = close + 1;
111+
}
112+
}
113+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.plc4x.java.ads.resolution;
20+
21+
import org.apache.plc4x.java.ads.readwrite.AdsDataTypeArrayInfo;
22+
import org.apache.plc4x.java.api.types.PlcValueType;
23+
24+
import java.util.List;
25+
26+
/**
27+
* Result of resolving a symbolic ADS tag against the symbol- and data-type tables.
28+
*
29+
* <p>Carries everything needed to issue the AdsRead/AdsWrite and to decode/encode the
30+
* payload — no further table walking is needed at I/O time.
31+
*
32+
* @param indexGroup ADS index group of the resolved location.
33+
* @param indexOffset Absolute byte offset of the resolved location.
34+
* @param sizeInBytes Total bytes to read/write at this location.
35+
* @param dataTypeName Name of the leaf data type at the resolved location (e.g.
36+
* {@code "DINT"}, {@code "STRING(80)"}, {@code "TSimpleStruct"},
37+
* {@code "ARRAY [1..2,1..3] OF INT"}). For partial-array reads,
38+
* this is still the name of the array type — {@code remainingArrayInfo}
39+
* describes the remaining dimensions.
40+
* @param plcValueType PLC4X value type the leaf decodes to. {@code List} when the
41+
* resolved address has remaining array dimensions; {@code Struct}
42+
* when the leaf is a struct; otherwise the scalar leaf type.
43+
* @param stringLength Character count for STRING/WSTRING leaves; {@code 0} otherwise.
44+
* @param remainingArrayInfo Dimensions still un-indexed at the resolved location. Empty
45+
* for fully-indexed leaves; non-empty when the user asked for e.g.
46+
* {@code arr[1]} on a 2-D array, which yields a row.
47+
*/
48+
public record ResolvedAdsTag(
49+
long indexGroup,
50+
long indexOffset,
51+
long sizeInBytes,
52+
String dataTypeName,
53+
PlcValueType plcValueType,
54+
int stringLength,
55+
List<AdsDataTypeArrayInfo> remainingArrayInfo
56+
) {
57+
}

0 commit comments

Comments
 (0)