You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2026/08/26 15:50:13 UTC
(plc4x) branch develop updated: feat(plc4go/ads): enable the ADS driver testsuite, align value rendering
This is an automated email from the ASF dual-hosted git repository.
sruehl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/develop by this push:
new 4de8bf1a45 feat(plc4go/ads): enable the ADS driver testsuite, align value rendering
4de8bf1a45 is described below
commit 4de8bf1a45d950a54e082a6b375b73f0f281a8d8
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Wed Aug 26 17:47:35 2026 +0200
feat(plc4go/ads): enable the ADS driver testsuite, align value rendering
TestAdsDriver was skipped since the symbol-table commands were ported;
the port is long done, but the driver, the Go testsuite runner, and the
cross-language value rendering had all drifted. With this the shared
protocols/ads DriverTestsuite.xml passes STRICTLY in both plc4j
(AdsDriverIT, auto-migrate now off) and plc4go (t.Skip removed).
ADS driver (Go):
- accept the plc4j kebab-case connection parameters (source-ams-net-id,
...) as aliases of the camelCase ones, and fix the copy-paste check
that reported a missing targetAmsNetId as sourceAmsNetId
- implement SetAwaitSetupComplete/SetAwaitDisconnectComplete so the
testsuite runner can feed the connect handshake itself (S7 pattern);
clear() no longer wipes those configuration flags
- the device-info request uses the configured AMS ports instead of the
hard-coded target 851/source 800
- the connect handshake reads online- before symbol-version and
subscribes online- before offline-version, matching plc4j
- multi-tag subscriptions execute sequentially in request order (the
map iteration made the wire order random), honor the requested
interval as the notification cycle time, no longer panic on a
data-type-table miss or on the single-survivor shortcut after a
failed resolution, and log failed symbolic resolutions
- the data type table is keyed by the entry's main name - the name
symbols reference - matching plc4j; value-type/string-length mapping
reads the main name too (the secondary name is only set for aliases)
- generated ads DataItem: WCHAR/WSTRING encode as UTF16LE per the mspec
(the data-io codegen path loses stringEncoding - template follow-up),
and the stray ')' in the write-side encodings is gone
SPI (Go):
- ReadBufferByteBased.ReadString honors the encoding reader-arg:
UTF-16 strings decode per code unit instead of stopping at the first
NUL byte of the first character
- WriteBufferXmlBased renders float64 as the shortest round-trip
decimal (like float32) instead of a fixed 32-digit expansion
- PlcStruct keeps a deterministic member order (declaration order via
NewPlcStructOrdered, sorted otherwise) for GetKeys/serialization
- PlcTIME/PlcLTIME render ISO-8601 with the H/M/S decomposition and
sub-second fraction instead of truncating to whole seconds;
PlcDATE_AND_TIME renders the UTC wall time in ISO-8601 instead of
Go's local-zone default format
- the value handler parses temporal values from their ISO-8601 string
forms (TIME/LTIME/DATE/TIME_OF_DAY/DATE_AND_TIME)
- PlcDWORD/PlcSINT/PlcULINT/PlcWSTRING serialize under their own names
(previously PlcDINT/PlcINT/PlcUINT/PlcSTRING); string-ish values
carry encoding="UTF-8"; PlcWSTRING's bit length counts UTF-16 bytes
including the BOM, matching plc4j
Testsuite runners:
- the Go runner compares only the values/responseCodes sections of api
responses (matching the Java runner - the request serialization is
implementation-specific), canonicalizes sibling order (response
values are map-keyed), and supports struct-valued write requests
- the Java runner's hand-built api-response rendering now matches the
agreed conventions: BYTE as a byte bit-string (0x..), temporal and
string values as dataType="string" with a text-derived bit length
plc4j values: the unsigned bit-string types WORD/DWORD/LWORD serialize
with the unsigned writers (dataType="uint"), PlcBYTE as a bit string,
and PlcTIME as an ISO-8601 string like PlcLTIME.
Testsuite XMLs (ads, modbus all-datatypes + manual-factory, canopen):
unsigned integers carry dataType="uint", api-response BYTEs the 0x..
byte form, the ads TIME its ISO-8601 form, and the ads struct members
their declaration order (the old order was a 2020 HashMap artifact).
plc4go/assets copies synced.
Verified: full plc4go test sweep green; plc4j verify (incl. jacoco)
green for spi values/fields/buffers-xml, test-utils, and the ads, s7,
eip, slmp, modbus, canopen and iec-60870 drivers.
---
.../testing/protocols/ads/DriverTestsuite.xml | 194 ++++++++++-----------
.../ManualFactoryModbusTCPDriverTest-testsuite.xml | 4 +-
.../tcp/Modbus-all-datatypes-little-endian.xml | 28 +--
.../protocols/modbus/tcp/Modbus-all-datatypes.xml | 28 +--
plc4go/internal/ads/Connection.go | 71 +++++---
plc4go/internal/ads/Driver.go | 24 ++-
plc4go/internal/ads/DriverContext.go | 9 +-
plc4go/internal/ads/MessageTemplates.go | 7 +-
plc4go/internal/ads/Reader.go | 6 +-
plc4go/internal/ads/Subscriber.go | 46 +++--
plc4go/internal/ads/model/Configuration.go | 31 ++--
plc4go/protocols/ads/readwrite/model/DataItem.go | 12 +-
plc4go/spi/testutils/DriverTestRunner.go | 79 ++++++++-
plc4go/spi/utils/ReadBufferByteBased.go | 38 +++-
plc4go/spi/utils/WriteBufferXmlBased.go | 11 +-
plc4go/spi/values/PlcCHAR.go | 2 +-
plc4go/spi/values/PlcDATE.go | 2 +-
plc4go/spi/values/PlcDATE_AND_LTIME.go | 2 +-
plc4go/spi/values/PlcDATE_AND_TIME.go | 6 +-
plc4go/spi/values/PlcDWORD.go | 2 +-
plc4go/spi/values/PlcLDATE.go | 2 +-
plc4go/spi/values/PlcLDATE_AND_TIME.go | 2 +-
plc4go/spi/values/PlcLTIME.go | 4 +-
plc4go/spi/values/PlcLTIME_OF_DAY.go | 2 +-
plc4go/spi/values/PlcSINT.go | 2 +-
plc4go/spi/values/PlcSTRING.go | 2 +-
plc4go/spi/values/PlcStruct.go | 48 ++++-
plc4go/spi/values/PlcTIME.go | 75 +++++++-
plc4go/spi/values/PlcTIME_OF_DAY.go | 2 +-
plc4go/spi/values/PlcULINT.go | 2 +-
plc4go/spi/values/PlcValueHandler.go | 33 +++-
plc4go/spi/values/PlcWSTRING.go | 6 +-
plc4go/tests/drivers/tests/ads_driver_test.go | 1 -
.../org/apache/plc4x/java/ads/AdsDriverIT.java | 2 +-
.../org/apache/plc4x/java/spi/values/PlcBYTE.java | 3 +-
.../org/apache/plc4x/java/spi/values/PlcDWORD.java | 2 +-
.../org/apache/plc4x/java/spi/values/PlcLWORD.java | 2 +-
.../org/apache/plc4x/java/spi/values/PlcTIME.java | 5 +-
.../org/apache/plc4x/java/spi/values/PlcWORD.java | 2 +-
.../internal/handlers/ApiResponseHandler.java | 32 +++-
.../resources/protocols/ads/DriverTestsuite.xml | 194 ++++++++++-----------
.../protocols/canopen/CANOpenDriverNMTIT.xml | 2 +-
.../protocols/canopen/CANOpenDriverSDOIT.xml | 6 +-
.../ManualFactoryModbusTCPDriverTest-testsuite.xml | 4 +-
.../tcp/Modbus-all-datatypes-little-endian.xml | 28 +--
.../protocols/modbus/tcp/Modbus-all-datatypes.xml | 28 +--
46 files changed, 698 insertions(+), 395 deletions(-)
diff --git a/plc4go/assets/testing/protocols/ads/DriverTestsuite.xml b/plc4go/assets/testing/protocols/ads/DriverTestsuite.xml
index 33d0248a5e..021ce08bfc 100644
--- a/plc4go/assets/testing/protocols/ads/DriverTestsuite.xml
+++ b/plc4go/assets/testing/protocols/ads/DriverTestsuite.xml
@@ -1206,72 +1206,72 @@
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
<PlcStruct>
- <hurz_STRING>
- <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
- </hurz_STRING>
- <hurz_LTIME>
- <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
- </hurz_LTIME>
+ <hurz_BOOL>
+ <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
+ </hurz_BOOL>
+ <hurz_BYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x01</PlcBYTE>
+ </hurz_BYTE>
+ <hurz_WORD>
+ <PlcWORD dataType="uint" bitLength="16">2</PlcWORD>
+ </hurz_WORD>
+ <hurz_DWORD>
+ <PlcDWORD dataType="uint" bitLength="32">3</PlcDWORD>
+ </hurz_DWORD>
<hurz_LWORD>
- <PlcLWORD dataType="int" bitLength="64">4</PlcLWORD>
+ <PlcLWORD dataType="uint" bitLength="64">4</PlcLWORD>
</hurz_LWORD>
- <hurz_REAL>
- <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
- </hurz_REAL>
- <hurz_ULINT>
- <PlcULINT dataType="int" bitLength="64">12</PlcULINT>
- </hurz_ULINT>
- <hurz_WSTRING>
- <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
- </hurz_WSTRING>
- <hurz_LINT>
- <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
- </hurz_LINT>
- <hurz_DATE_AND_TIME>
- <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
- </hurz_DATE_AND_TIME>
- <hurz_UINT>
- <PlcUINT dataType="int" bitLength="16">8</PlcUINT>
- </hurz_UINT>
<hurz_SINT>
<PlcSINT dataType="int" bitLength="8">5</PlcSINT>
</hurz_SINT>
<hurz_USINT>
- <PlcUSINT dataType="int" bitLength="8">6</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">6</PlcUSINT>
</hurz_USINT>
- <hurz_LREAL>
- <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
- </hurz_LREAL>
- <hurz_TIME_OF_DAY>
- <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
- </hurz_TIME_OF_DAY>
- <hurz_BOOL>
- <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
- </hurz_BOOL>
+ <hurz_INT>
+ <PlcINT dataType="int" bitLength="16">7</PlcINT>
+ </hurz_INT>
+ <hurz_UINT>
+ <PlcUINT dataType="uint" bitLength="16">8</PlcUINT>
+ </hurz_UINT>
<hurz_DINT>
<PlcDINT dataType="int" bitLength="32">9</PlcDINT>
</hurz_DINT>
<hurz_UDINT>
- <PlcUDINT dataType="int" bitLength="32">10</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">10</PlcUDINT>
</hurz_UDINT>
- <hurz_BYTE>
- <PlcBYTE dataType="int" bitLength="8">1</PlcBYTE>
- </hurz_BYTE>
+ <hurz_LINT>
+ <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
+ </hurz_LINT>
+ <hurz_ULINT>
+ <PlcULINT dataType="uint" bitLength="64">12</PlcULINT>
+ </hurz_ULINT>
+ <hurz_REAL>
+ <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
+ </hurz_REAL>
+ <hurz_LREAL>
+ <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
+ </hurz_LREAL>
<hurz_TIME>
- <PlcTIME dataType="int" bitLength="32">1234</PlcTIME>
+ <PlcTIME dataType="string" bitLength="64" encoding="UTF-8">PT1.234S</PlcTIME>
</hurz_TIME>
- <hurz_DWORD>
- <PlcDWORD dataType="int" bitLength="32">3</PlcDWORD>
- </hurz_DWORD>
- <hurz_INT>
- <PlcINT dataType="int" bitLength="16">7</PlcINT>
- </hurz_INT>
- <hurz_WORD>
- <PlcWORD dataType="int" bitLength="16">2</PlcWORD>
- </hurz_WORD>
+ <hurz_LTIME>
+ <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
+ </hurz_LTIME>
<hurz_DATE>
<PlcDATE dataType="string" bitLength="80" encoding="UTF-8">1978-03-28</PlcDATE>
</hurz_DATE>
+ <hurz_TIME_OF_DAY>
+ <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
+ </hurz_TIME_OF_DAY>
+ <hurz_DATE_AND_TIME>
+ <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
+ </hurz_DATE_AND_TIME>
+ <hurz_STRING>
+ <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
+ </hurz_STRING>
+ <hurz_WSTRING>
+ <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
+ </hurz_WSTRING>
</PlcStruct>
</value>
</PlcResponseItem>
@@ -1305,72 +1305,72 @@
<address>MAIN.hurz_Struct</address>
<value>
<PlcStruct>
- <hurz_STRING>
- <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
- </hurz_STRING>
- <hurz_LTIME>
- <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
- </hurz_LTIME>
+ <hurz_BOOL>
+ <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
+ </hurz_BOOL>
+ <hurz_BYTE>
+ <PlcBYTE dataType="int" bitLength="8">1</PlcBYTE>
+ </hurz_BYTE>
+ <hurz_WORD>
+ <PlcWORD dataType="uint" bitLength="16">2</PlcWORD>
+ </hurz_WORD>
+ <hurz_DWORD>
+ <PlcDWORD dataType="uint" bitLength="32">3</PlcDWORD>
+ </hurz_DWORD>
<hurz_LWORD>
- <PlcLWORD dataType="int" bitLength="64">4</PlcLWORD>
+ <PlcLWORD dataType="uint" bitLength="64">4</PlcLWORD>
</hurz_LWORD>
- <hurz_REAL>
- <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
- </hurz_REAL>
- <hurz_ULINT>
- <PlcULINT dataType="int" bitLength="64">12</PlcULINT>
- </hurz_ULINT>
- <hurz_WSTRING>
- <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
- </hurz_WSTRING>
- <hurz_LINT>
- <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
- </hurz_LINT>
- <hurz_DATE_AND_TIME>
- <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
- </hurz_DATE_AND_TIME>
- <hurz_UINT>
- <PlcUINT dataType="int" bitLength="16">8</PlcUINT>
- </hurz_UINT>
<hurz_SINT>
<PlcSINT dataType="int" bitLength="8">5</PlcSINT>
</hurz_SINT>
<hurz_USINT>
- <PlcUSINT dataType="int" bitLength="8">6</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">6</PlcUSINT>
</hurz_USINT>
- <hurz_LREAL>
- <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
- </hurz_LREAL>
- <hurz_TIME_OF_DAY>
- <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
- </hurz_TIME_OF_DAY>
- <hurz_BOOL>
- <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
- </hurz_BOOL>
+ <hurz_INT>
+ <PlcINT dataType="int" bitLength="16">7</PlcINT>
+ </hurz_INT>
+ <hurz_UINT>
+ <PlcUINT dataType="uint" bitLength="16">8</PlcUINT>
+ </hurz_UINT>
<hurz_DINT>
<PlcDINT dataType="int" bitLength="32">9</PlcDINT>
</hurz_DINT>
<hurz_UDINT>
- <PlcUDINT dataType="int" bitLength="32">10</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">10</PlcUDINT>
</hurz_UDINT>
- <hurz_BYTE>
- <PlcBYTE dataType="int" bitLength="8">1</PlcBYTE>
- </hurz_BYTE>
+ <hurz_LINT>
+ <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
+ </hurz_LINT>
+ <hurz_ULINT>
+ <PlcULINT dataType="uint" bitLength="64">12</PlcULINT>
+ </hurz_ULINT>
+ <hurz_REAL>
+ <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
+ </hurz_REAL>
+ <hurz_LREAL>
+ <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
+ </hurz_LREAL>
<hurz_TIME>
<PlcTIME dataType="string" bitLength="64" encoding="UTF-8">PT1.234S</PlcTIME>
</hurz_TIME>
- <hurz_DWORD>
- <PlcDWORD dataType="int" bitLength="32">3</PlcDWORD>
- </hurz_DWORD>
- <hurz_INT>
- <PlcINT dataType="int" bitLength="16">7</PlcINT>
- </hurz_INT>
- <hurz_WORD>
- <PlcWORD dataType="int" bitLength="16">2</PlcWORD>
- </hurz_WORD>
+ <hurz_LTIME>
+ <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
+ </hurz_LTIME>
<hurz_DATE>
<PlcDATE dataType="string" bitLength="80" encoding="UTF-8">1978-03-28</PlcDATE>
</hurz_DATE>
+ <hurz_TIME_OF_DAY>
+ <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
+ </hurz_TIME_OF_DAY>
+ <hurz_DATE_AND_TIME>
+ <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
+ </hurz_DATE_AND_TIME>
+ <hurz_STRING>
+ <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
+ </hurz_STRING>
+ <hurz_WSTRING>
+ <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
+ </hurz_WSTRING>
</PlcStruct>
</value>
</tag>
diff --git a/plc4go/assets/testing/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml b/plc4go/assets/testing/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml
index 58ab5c62e0..1a1ce25989 100644
--- a/plc4go/assets/testing/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml
+++ b/plc4go/assets/testing/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml
@@ -129,7 +129,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="uint" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</holding-register_2_BYTE>
@@ -1064,7 +1064,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="uint" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</holding-register_2_BYTE>
diff --git a/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml b/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml
index 7804ad4fc3..a875b5f4d5 100644
--- a/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml
+++ b/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml
@@ -313,7 +313,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</hurz>
@@ -401,7 +401,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcTagValueItem>
</hurz>
@@ -506,7 +506,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -594,7 +594,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -699,7 +699,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -789,7 +789,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -1087,7 +1087,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1175,7 +1175,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1473,7 +1473,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1561,7 +1561,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1861,7 +1861,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1951,7 +1951,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -2251,7 +2251,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcResponseItem>
</hurz>
@@ -2341,7 +2341,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcTagValueItem>
</hurz>
diff --git a/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes.xml b/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes.xml
index 1c5b7da057..31b5d343cd 100644
--- a/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes.xml
+++ b/plc4go/assets/testing/protocols/modbus/tcp/Modbus-all-datatypes.xml
@@ -307,7 +307,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</hurz>
@@ -395,7 +395,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcTagValueItem>
</hurz>
@@ -500,7 +500,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -588,7 +588,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -693,7 +693,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -783,7 +783,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -1081,7 +1081,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1169,7 +1169,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1467,7 +1467,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1555,7 +1555,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1855,7 +1855,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1945,7 +1945,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -2245,7 +2245,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcResponseItem>
</hurz>
@@ -2335,7 +2335,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcTagValueItem>
</hurz>
diff --git a/plc4go/internal/ads/Connection.go b/plc4go/internal/ads/Connection.go
index 28c46db1f1..e1eff2a8fd 100644
--- a/plc4go/internal/ads/Connection.go
+++ b/plc4go/internal/ads/Connection.go
@@ -27,6 +27,7 @@ import (
"strconv"
"strings"
"sync"
+ "time"
"github.com/rs/zerolog"
@@ -143,6 +144,18 @@ func (m *Connection) Connect(ctx context.Context) error {
return errors.Wrap(err, "error connecting to message codec")
}
+ // For testing purposes we can skip the waiting for a complete connection
+ if !m.driverContext.awaitSetupComplete {
+ m.wg.Go(func() {
+ if err := m.setupConnection(ctx); err != nil {
+ m.log.Error().Err(err).Msg("Error during connection setup")
+ }
+ })
+ m.log.Warn().Msg("Connection used in an unsafe way. !!!DON'T USE IN PRODUCTION!!!")
+ m.SetConnected(true)
+ return nil
+ }
+
if err := m.setupConnection(ctx); err != nil {
return errors.Wrap(err, "error setting up connection")
}
@@ -158,6 +171,18 @@ func (m *Connection) setupConnection(ctx context.Context) error {
m.driverContext.adsVersion = fmt.Sprintf("%d.%d.%d", deviceInfoResponse.GetMajorVersion(), deviceInfoResponse.GetMinorVersion(), deviceInfoResponse.GetVersion())
m.driverContext.deviceName = GetZeroTerminatedString(deviceInfoResponse.GetDevice())
+ // Read the online-version
+ // (The order online- before symbol-version matches the Java driver and the shared driver testsuite.)
+ onlineVersionResponse, err := m.ExecuteAdsReadWriteRequest(ctx, uint32(readWriteModel.ReservedIndexGroups_ADSIGRP_SYM_VALBYNAME), 0, 4, nil, []byte("TwinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt"))
+ if err != nil {
+ return errors.Wrap(err, "error reading online version")
+ }
+ rb := utils.NewReadBufferByteBased(onlineVersionResponse.GetData(), utils.WithByteOrderForReadBufferByteBased(binary.LittleEndian))
+ m.driverContext.onlineVersion, err = rb.ReadUint32("", 32)
+ if err != nil {
+ return errors.Wrap(err, "error reading online version")
+ }
+
// Read the symbol-version (offline changes)
symbolVersionResponse, err := m.ExecuteAdsReadRequest(ctx, uint32(readWriteModel.ReservedIndexGroups_ADSIGRP_SYM_VERSION), 0, 1)
if err != nil {
@@ -170,17 +195,6 @@ func (m *Connection) setupConnection(ctx context.Context) error {
}
m.driverContext.symbolVersion = symbolVersionResponse.GetData()[0]
- // Read the online-version
- onlineVersionResponse, err := m.ExecuteAdsReadWriteRequest(ctx, uint32(readWriteModel.ReservedIndexGroups_ADSIGRP_SYM_VALBYNAME), 0, 4, nil, []byte("TwinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt"))
- if err != nil {
- return errors.Wrap(err, "error reading online version")
- }
- rb := utils.NewReadBufferByteBased(onlineVersionResponse.GetData(), utils.WithByteOrderForReadBufferByteBased(binary.LittleEndian))
- m.driverContext.onlineVersion, err = rb.ReadUint32("", 32)
- if err != nil {
- return errors.Wrap(err, "error reading online version")
- }
-
// Read the data type and symbol table
err = m.readSymbolTableAndDatatypeTable(ctx)
if err != nil {
@@ -219,13 +233,16 @@ func (m *Connection) setupConnection(ctx context.Context) error {
})
// Subscribe for changes to the symbol or the offline-versions
+ // (Cyclic with a 1s check interval: the wire request is an ON_CHANGE device notification
+ // whose cycle time is the interval, matching the Java driver's Duration.ofMillis(1000).
+ // The online- before offline-version order matches the Java driver and the driver testsuite.)
versionChangeRequest, err := m.SubscriptionRequestBuilder().
- AddChangeOfStateTagAddress("offlineVersion", "0xF008/0x0000:USINT").
- AddPreRegisteredConsumer("offlineVersion", func(event apiModel.PlcSubscriptionEvent) {
- if event.GetResponseCode("offlineVersion") == apiModel.PlcResponseCode_OK {
- newVersion := event.GetValue("offlineVersion").GetUint8()
- if newVersion != m.driverContext.symbolVersion {
- m.log.Info().Msg("detected offline version change: reloading symbol- and data-type-table.")
+ AddCyclicTagAddress("onlineVersion", "TwinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt", time.Second).
+ AddPreRegisteredConsumer("onlineVersion", func(event apiModel.PlcSubscriptionEvent) {
+ if event.GetResponseCode("onlineVersion") == apiModel.PlcResponseCode_OK {
+ newVersion := event.GetValue("onlineVersion").GetUint32()
+ if newVersion != m.driverContext.onlineVersion {
+ m.log.Info().Msg("detected online version change: reloading symbol- and data-type-table.")
err := m.readSymbolTableAndDatatypeTable(ctx)
if err != nil {
m.log.Error().Err(err).Msg("error updating data-type and symbol tables")
@@ -233,12 +250,12 @@ func (m *Connection) setupConnection(ctx context.Context) error {
}
}
}).
- AddChangeOfStateTagAddress("onlineVersion", "TwinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt").
- AddPreRegisteredConsumer("onlineVersion", func(event apiModel.PlcSubscriptionEvent) {
- if event.GetResponseCode("onlineVersion") == apiModel.PlcResponseCode_OK {
- newVersion := event.GetValue("onlineVersion").GetUint32()
- if newVersion != m.driverContext.onlineVersion {
- m.log.Info().Msg("detected online version change: reloading symbol- and data-type-table.")
+ AddCyclicTagAddress("offlineVersion", "0xF008/0x0000:USINT", time.Second).
+ AddPreRegisteredConsumer("offlineVersion", func(event apiModel.PlcSubscriptionEvent) {
+ if event.GetResponseCode("offlineVersion") == apiModel.PlcResponseCode_OK {
+ newVersion := event.GetValue("offlineVersion").GetUint8()
+ if newVersion != m.driverContext.symbolVersion {
+ m.log.Info().Msg("detected offline version change: reloading symbol- and data-type-table.")
err := m.readSymbolTableAndDatatypeTable(ctx)
if err != nil {
m.log.Error().Err(err).Msg("error updating data-type and symbol tables")
@@ -309,7 +326,9 @@ func (m *Connection) readDataTypeTable(ctx context.Context, dataTableSize uint32
if err != nil {
return nil, fmt.Errorf("error parsing table: %v", err)
}
- dataTypes[dataType.GetSecondaryName()] = dataType
+ // Key by the main name: that's the name symbols reference via their dataTypeName
+ // (and what the Java driver keys by); the secondary name is the aliased/simple type.
+ dataTypes[dataType.GetMainName()] = dataType
}
return dataTypes, nil
}
@@ -401,7 +420,9 @@ func (m *Connection) resolveSymbolicAddress(ctx context.Context, addressParts []
func (m *Connection) getPlcValueForAdsDataTypeTableEntry(entry readWriteModel.AdsDataTypeTableEntry) (apiValues.PlcValueType, int32) {
stringLength := -1
- dataTypeName := entry.GetSecondaryName()
+ // The main name carries the type's name ("BYTE", "STRING(80)", ...); the secondary
+ // name is only set for aliased types (matching the Java driver's use of getMainName()).
+ dataTypeName := entry.GetMainName()
if strings.HasPrefix(dataTypeName, "STRING(") {
var err error
stringLength, err = strconv.Atoi(dataTypeName[7 : len(dataTypeName)-1])
diff --git a/plc4go/internal/ads/Driver.go b/plc4go/internal/ads/Driver.go
index 07577d02e5..d0743cee23 100644
--- a/plc4go/internal/ads/Driver.go
+++ b/plc4go/internal/ads/Driver.go
@@ -42,6 +42,9 @@ type Driver struct {
discoverer *Discoverer
+ awaitSetupComplete bool
+ awaitDisconnectComplete bool
+
log zerolog.Logger
_options []options.WithOption // Used to pass them downstream
}
@@ -49,9 +52,11 @@ type Driver struct {
func NewDriver(_options ...options.WithOption) plc4go.PlcDriver {
customLogger := options.ExtractCustomLoggerOrDefaultToGlobal(_options...)
driver := &Driver{
- discoverer: NewDiscoverer(_options...),
- log: customLogger,
- _options: _options,
+ discoverer: NewDiscoverer(_options...),
+ awaitSetupComplete: true,
+ awaitDisconnectComplete: true,
+ log: customLogger,
+ _options: _options,
}
driver.DefaultDriver = _default.NewDefaultDriver(driver, "ads", "Beckhoff TwinCat ADS", "tcp", NewTagHandler())
return driver
@@ -108,6 +113,8 @@ func (d *Driver) GetConnection(ctx context.Context, transportUrl url.URL, transp
if err != nil {
return nil, errors.Wrap(err, "couldn't create connection")
}
+ connection.driverContext.awaitSetupComplete = d.awaitSetupComplete
+ connection.driverContext.awaitDisconnectComplete = d.awaitDisconnectComplete
connectionLog.Debug().Interface("connection", connection).Msg("created connection, connecting now")
if err := connection.Connect(ctx); err != nil {
return nil, errors.Wrap(err, "Error connecting connection")
@@ -115,6 +122,17 @@ func (d *Driver) GetConnection(ctx context.Context, transportUrl url.URL, transp
return connection, nil
}
+// SetAwaitSetupComplete lets the driver testsuite runner return from Connect before the
+// connection handshake ran, so the testsuite can feed the handshake's responses itself.
+func (d *Driver) SetAwaitSetupComplete(awaitComplete bool) {
+ d.awaitSetupComplete = awaitComplete
+}
+
+// SetAwaitDisconnectComplete is the disconnect-side counterpart of SetAwaitSetupComplete.
+func (d *Driver) SetAwaitDisconnectComplete(awaitComplete bool) {
+ d.awaitDisconnectComplete = awaitComplete
+}
+
func (d *Driver) SupportsDiscovery() bool {
return true
}
diff --git a/plc4go/internal/ads/DriverContext.go b/plc4go/internal/ads/DriverContext.go
index b3fe369d78..4b81eea754 100644
--- a/plc4go/internal/ads/DriverContext.go
+++ b/plc4go/internal/ads/DriverContext.go
@@ -61,8 +61,8 @@ func (m *DriverContext) clear() {
m.onlineVersion = 0
m.dataTypeTable = map[string]driverModel.AdsDataTypeTableEntry{}
m.symbolTable = map[string]driverModel.AdsSymbolTableEntry{}
- m.awaitSetupComplete = false
- m.awaitDisconnectComplete = false
+ // awaitSetupComplete/awaitDisconnectComplete are configuration handed down from the
+ // driver, not session state - clearing must not reset them.
}
func (m *DriverContext) getDirectTagForSymbolTag(symbolicPlcTag model.SymbolicPlcTag) (*model.DirectPlcTag, error) {
@@ -124,7 +124,8 @@ func (m *DriverContext) getDataTypeForDataTypeTableEntry(entry driverModel.AdsDa
if entry.GetNumChildren() > 0 {
return apiValues.Struct
}
- dataTypeName := entry.GetSecondaryName()
+ // The main name carries the type's name (matching the Java driver's getMainName()).
+ dataTypeName := entry.GetMainName()
if strings.HasPrefix(dataTypeName, "STRING(") {
dataTypeName = "STRING"
} else if strings.HasPrefix(dataTypeName, "WSTRING(") {
@@ -135,7 +136,7 @@ func (m *DriverContext) getDataTypeForDataTypeTableEntry(entry driverModel.AdsDa
}
func (m *DriverContext) getStringLengthForDataTypeTableEntry(entry driverModel.AdsDataTypeTableEntry) int32 {
- dataTypeName := entry.GetSecondaryName()
+ dataTypeName := entry.GetMainName()
if strings.HasPrefix(dataTypeName, "STRING(") {
lenStr := dataTypeName[7 : len(dataTypeName)-1]
lenVal, err := strconv.Atoi(lenStr)
diff --git a/plc4go/internal/ads/MessageTemplates.go b/plc4go/internal/ads/MessageTemplates.go
index eccf0cb147..ab396d5b7e 100644
--- a/plc4go/internal/ads/MessageTemplates.go
+++ b/plc4go/internal/ads/MessageTemplates.go
@@ -23,9 +23,10 @@ import adsModel "github.com/apache/plc4x/plc4go/protocols/ads/readwrite/model"
func (m *Connection) NewAdsReadDeviceInfoRequest() adsModel.AmsTCPPacket {
return adsModel.NewAmsTCPPacket(
- adsModel.NewAdsReadDeviceInfoRequest(m.configuration.TargetAmsNetId, uint16(adsModel.DefaultAmsPorts_RUNTIME_SYSTEM_01),
- // TODO: Replace 800 with constant.
- m.configuration.SourceAmsNetId, 800, 0, m.driverContext.getInvokeId()))
+ adsModel.NewAdsReadDeviceInfoRequest(
+ m.configuration.TargetAmsNetId, m.configuration.TargetAmsPort,
+ m.configuration.SourceAmsNetId, m.configuration.SourceAmsPort,
+ 0, m.driverContext.getInvokeId()))
}
func (m *Connection) NewAdsReadRequest(indexGroup uint32, indexOffset uint32, length uint32) adsModel.AmsTCPPacket {
diff --git a/plc4go/internal/ads/Reader.go b/plc4go/internal/ads/Reader.go
index 75d69174bb..872c64c6bd 100644
--- a/plc4go/internal/ads/Reader.go
+++ b/plc4go/internal/ads/Reader.go
@@ -279,6 +279,7 @@ func (m *Connection) parsePlcValue(dataType driverModel.AdsDataTypeTableEntry, a
} else if len(dataType.GetChildren()) > 0 {
// This is a Struct type.
plcValues := map[string]apiValues.PlcValue{}
+ var memberOrder []string
startPos := uint32(rb.GetPos())
curPos := uint32(0)
for _, child := range dataType.GetChildren() {
@@ -298,9 +299,12 @@ func (m *Connection) parsePlcValue(dataType driverModel.AdsDataTypeTableEntry, a
return nil, errors.Wrap(err, fmt.Sprintf("error parsing propery %s of type %s", childName, dataType.GetSecondaryName()))
}
plcValues[childName] = childValue
+ memberOrder = append(memberOrder, childName)
curPos = uint32(rb.GetPos()) - startPos
}
- return spiValues.NewPlcStruct(plcValues), nil
+ // Keep the struct's declaration order (the order of the children in the data type
+ // table), like the Java driver does with its LinkedHashMap-based PlcStruct.
+ return spiValues.NewPlcStructOrdered(plcValues, memberOrder), nil
} else {
// This is a primitive type.
valueType, stringLength := m.getPlcValueForAdsDataTypeTableEntry(dataType)
diff --git a/plc4go/internal/ads/Subscriber.go b/plc4go/internal/ads/Subscriber.go
index 0d7d9dc807..8cb1457221 100644
--- a/plc4go/internal/ads/Subscriber.go
+++ b/plc4go/internal/ads/Subscriber.go
@@ -62,6 +62,7 @@ func (m *Connection) Subscribe(ctx context.Context, subscriptionRequest apiModel
symbolicTag := tag.(dirverModel.SymbolicPlcTag)
directTagPtr, err := m.driverContext.getDirectTagForSymbolTag(symbolicTag)
if err != nil {
+ m.log.Warn().Err(err).Str("tagName", tagName).Msg("error resolving symbolic tag for subscription")
subResults[tagName] = spiModel.NewDefaultPlcSubscriptionRequestResult(subscriptionRequest, nil, errors.Wrap(err, "error resolving symbolic tag"))
continue
}
@@ -84,20 +85,18 @@ func (m *Connection) Subscribe(ctx context.Context, subscriptionRequest apiModel
)
}
- // If this is a single item request, we can take a shortcut.
- if len(subSubscriptionRequests) == 1 {
+ // If this is a single item request with a successfully resolved tag, we can take a shortcut.
+ // (Indexing by the request's first tag name would be wrong here: with multiple tags where
+ // all but one failed resolution, the surviving entry is not necessarily the first tag.)
+ if len(subSubscriptionRequests) == 1 && len(subscriptionRequest.GetTagNames()) == 1 {
tagName := subscriptionRequest.GetTagNames()[0]
subSubscriptionRequest := subSubscriptionRequests[tagName]
return m.subscribe(ctx, subSubscriptionRequest)
}
- // Create a sub-result-channel map and prepare execute the subscriptions.
- subResultChannels := map[string]<-chan apiModel.PlcSubscriptionRequestResult{}
- for tagName, subSubscriptionRequest := range subSubscriptionRequests {
- subResultChannels[tagName] = m.subscribe(ctx, subSubscriptionRequest)
- }
-
- // Create a new result-channel, which completes as soon as all sub-result-channels have returned
+ // Create a new result-channel, which completes as soon as all sub-requests are done.
+ // The sub-requests are executed sequentially in request order: each is a single
+ // round-trip anyway, and it keeps the order of the requests on the wire deterministic.
globalResultChannel := make(chan apiModel.PlcSubscriptionRequestResult, 1)
m.wg.Go(func() {
defer func() {
@@ -108,15 +107,17 @@ func (m *Connection) Subscribe(ctx context.Context, subscriptionRequest apiModel
Msg("panic-ed")
}
}()
- // Iterate over all sub-results
- for _, subResultChannel := range subResultChannels {
+ for _, tagName := range subscriptionRequest.GetTagNames() {
+ subSubscriptionRequest, ok := subSubscriptionRequests[tagName]
+ if !ok {
+ // Tag resolution failed; its error result is already recorded in subResults.
+ continue
+ }
select {
case <-ctx.Done():
utils.DeliverResult(m.log, globalResultChannel, spiModel.NewDefaultPlcSubscriptionRequestResult(subscriptionRequest, nil, ctx.Err()))
return
- case subResult := <-subResultChannel:
- // These are all single value requests ... so it's safe to assume this shortcut.
- tagName := subResult.GetRequest().GetTagNames()[0]
+ case subResult := <-m.subscribe(ctx, subSubscriptionRequest):
subResults[tagName] = subResult
}
}
@@ -141,16 +142,29 @@ func (m *Connection) subscribe(ctx context.Context, subscriptionRequest apiModel
tagName := subscriptionRequest.GetTagNames()[0]
directTag := subscriptionRequest.GetTag(tagName).(dirverModel.DirectPlcTag)
if directTag.DataType == nil {
- directTag.DataType = m.driverContext.dataTypeTable[directTag.ValueType.String()]
+ dataType, ok := m.driverContext.dataTypeTable[directTag.ValueType.String()]
+ if !ok {
+ utils.DeliverResult(m.log, responseChan, spiModel.NewDefaultPlcSubscriptionRequestResult(
+ subscriptionRequest,
+ nil,
+ errors.Errorf("no entry for data type %s of tag %s in the data type table", directTag.ValueType, tagName),
+ ))
+ return
+ }
+ directTag.DataType = dataType
}
- response, err := m.ExecuteAdsAddDeviceNotificationRequest(ctx, directTag.IndexGroup, directTag.IndexOffset, directTag.DataType.GetSize(), model.AdsTransMode_ON_CHANGE, 0, 0)
+ // The requested interval is the device's cycle time for checking the value (in ms on
+ // the wire, matching the Java driver); 0 leaves the check cycle to the device.
+ cycleTime := uint32(subscriptionRequest.(*spiModel.DefaultPlcSubscriptionRequest).GetInterval(tagName).Milliseconds())
+ response, err := m.ExecuteAdsAddDeviceNotificationRequest(ctx, directTag.IndexGroup, directTag.IndexOffset, directTag.DataType.GetSize(), model.AdsTransMode_ON_CHANGE, 0, cycleTime)
if err != nil {
utils.DeliverResult(m.log, responseChan, spiModel.NewDefaultPlcSubscriptionRequestResult(
subscriptionRequest,
nil,
err,
))
+ return
}
// Create a new subscription handle.
subscriptionHandle := dirverModel.NewAdsSubscriptionHandle(
diff --git a/plc4go/internal/ads/model/Configuration.go b/plc4go/internal/ads/model/Configuration.go
index fd10219354..a76dbdc385 100644
--- a/plc4go/internal/ads/model/Configuration.go
+++ b/plc4go/internal/ads/model/Configuration.go
@@ -39,7 +39,7 @@ type Configuration struct {
func ParseFromOptions(localLogger zerolog.Logger, options map[string][]string) (Configuration, error) {
configuration := Configuration{}
- sourceAmsNetId := getFromOptions(localLogger, options, "sourceAmsNetId")
+ sourceAmsNetId := getFromOptions(localLogger, options, "sourceAmsNetId", "source-ams-net-id")
if sourceAmsNetId == "" {
return Configuration{}, errors.New("Required parameter sourceAmsNetId missing")
}
@@ -76,7 +76,7 @@ func ParseFromOptions(localLogger zerolog.Logger, options map[string][]string) (
uint8(octet5),
uint8(octet6),
)
- sourceAmsPort := getFromOptions(localLogger, options, "sourceAmsPort")
+ sourceAmsPort := getFromOptions(localLogger, options, "sourceAmsPort", "source-ams-port")
if sourceAmsPort == "" {
return Configuration{}, errors.New("Required parameter sourceAmsPort missing")
}
@@ -85,8 +85,8 @@ func ParseFromOptions(localLogger zerolog.Logger, options map[string][]string) (
return Configuration{}, errors.Wrap(err, "error parsing sourceAmsPort")
}
configuration.SourceAmsPort = uint16(parsedUint)
- targetAmsNetId := getFromOptions(localLogger, options, "targetAmsNetId")
- if sourceAmsNetId == "" {
+ targetAmsNetId := getFromOptions(localLogger, options, "targetAmsNetId", "target-ams-net-id")
+ if targetAmsNetId == "" {
return Configuration{}, errors.New("Required parameter targetAmsNetId missing")
}
split = strings.Split(targetAmsNetId, ".")
@@ -122,7 +122,7 @@ func ParseFromOptions(localLogger zerolog.Logger, options map[string][]string) (
uint8(octet5),
uint8(octet6),
)
- targetAmsPort := getFromOptions(localLogger, options, "targetAmsPort")
+ targetAmsPort := getFromOptions(localLogger, options, "targetAmsPort", "target-ams-port")
if targetAmsPort == "" {
return Configuration{}, errors.New("Required parameter targetAmsPort missing")
}
@@ -135,15 +135,20 @@ func ParseFromOptions(localLogger zerolog.Logger, options map[string][]string) (
return configuration, nil
}
-func getFromOptions(localLogger zerolog.Logger, options map[string][]string, key string) string {
- if optionValues, ok := options[key]; ok {
- if len(optionValues) <= 0 {
- return ""
- }
- if len(optionValues) > 1 {
- localLogger.Warn().Str("key", key).Msg("Options key must be unique")
+// getFromOptions returns the value of the first key present in the options. The additional
+// keys are aliases: the kebab-case names match the plc4j driver's connection parameters, so
+// the same connection string works with both implementations.
+func getFromOptions(localLogger zerolog.Logger, options map[string][]string, keys ...string) string {
+ for _, key := range keys {
+ if optionValues, ok := options[key]; ok {
+ if len(optionValues) <= 0 {
+ continue
+ }
+ if len(optionValues) > 1 {
+ localLogger.Warn().Str("key", key).Msg("Options key must be unique")
+ }
+ return optionValues[0]
}
- return optionValues[0]
}
return ""
}
diff --git a/plc4go/protocols/ads/readwrite/model/DataItem.go b/plc4go/protocols/ads/readwrite/model/DataItem.go
index 5ff0cacf37..61c16a4666 100644
--- a/plc4go/protocols/ads/readwrite/model/DataItem.go
+++ b/plc4go/protocols/ads/readwrite/model/DataItem.go
@@ -193,7 +193,7 @@ func DataItemParseWithBuffer(ctx context.Context, readBuffer utils.ReadBuffer, p
return values.NewPlcCHAR(value), nil
case plcValueType == api.WCHAR: // WCHAR
// Simple Field (value)
- value, _valueErr := /*TODO: migrate me*/ /*TODO: migrate me*/ readBuffer.ReadString("value", uint32(16), utils.WithEncoding("UTF8"))
+ value, _valueErr := /*TODO: migrate me*/ /*TODO: migrate me*/ readBuffer.ReadString("value", uint32(16), utils.WithEncoding("UTF16LE"))
if _valueErr != nil {
return nil, errors.Wrap(_valueErr, "Error parsing 'value' field")
}
@@ -216,7 +216,7 @@ func DataItemParseWithBuffer(ctx context.Context, readBuffer utils.ReadBuffer, p
return values.NewPlcSTRING(value), nil
case plcValueType == api.WSTRING: // WSTRING
// Simple Field (value)
- value, _valueErr := /*TODO: migrate me*/ /*TODO: migrate me*/ readBuffer.ReadString("value", uint32(((stringLength)*(8))*(2)), utils.WithEncoding("UTF8"))
+ value, _valueErr := /*TODO: migrate me*/ /*TODO: migrate me*/ readBuffer.ReadString("value", uint32(((stringLength)*(8))*(2)), utils.WithEncoding("UTF16LE"))
if _valueErr != nil {
return nil, errors.Wrap(_valueErr, "Error parsing 'value' field")
}
@@ -418,17 +418,17 @@ func DataItemSerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.Wri
}
case plcValueType == api.CHAR: // CHAR
// Simple Field (value)
- if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32(8), value.GetString(), utils.WithEncoding("UTF8)")); _err != nil {
+ if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32(8), value.GetString(), utils.WithEncoding("UTF8")); _err != nil {
return errors.Wrap(_err, "Error serializing 'value' field")
}
case plcValueType == api.WCHAR: // WCHAR
// Simple Field (value)
- if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32(16), value.GetString(), utils.WithEncoding("UTF8)")); _err != nil {
+ if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32(16), value.GetString(), utils.WithEncoding("UTF16LE")); _err != nil {
return errors.Wrap(_err, "Error serializing 'value' field")
}
case plcValueType == api.STRING: // STRING
// Simple Field (value)
- if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32((stringLength)*(8)), value.GetString(), utils.WithEncoding("UTF8)")); _err != nil {
+ if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32((stringLength)*(8)), value.GetString(), utils.WithEncoding("UTF8")); _err != nil {
return errors.Wrap(_err, "Error serializing 'value' field")
}
@@ -438,7 +438,7 @@ func DataItemSerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.Wri
}
case plcValueType == api.WSTRING: // WSTRING
// Simple Field (value)
- if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32(((stringLength)*(8))*(2)), value.GetString(), utils.WithEncoding("UTF8)")); _err != nil {
+ if _err := /*TODO: migrate me*/ writeBuffer.WriteString("value", uint32(((stringLength)*(8))*(2)), value.GetString(), utils.WithEncoding("UTF16LE")); _err != nil {
return errors.Wrap(_err, "Error serializing 'value' field")
}
diff --git a/plc4go/spi/testutils/DriverTestRunner.go b/plc4go/spi/testutils/DriverTestRunner.go
index 19af308859..ad76c7382e 100644
--- a/plc4go/spi/testutils/DriverTestRunner.go
+++ b/plc4go/spi/testutils/DriverTestRunner.go
@@ -26,6 +26,7 @@ import (
"fmt"
"os"
"runtime/debug"
+ "sort"
"strconv"
"strings"
"testing"
@@ -223,6 +224,8 @@ func (m DriverTestsuite) ExecuteStep(t *testing.T, connection plc4go.PlcConnecti
tagValue = append(tagValue, valueChild.Text)
}
wrb.AddTagAddress(tagName, tagAddress, tagValue)
+ } else if structValue := convertApiRequestStructValue(tagNode.GetChild("value")); structValue != nil {
+ wrb.AddTagAddress(tagName, tagAddress, structValue)
} else {
tagValue := tagNode.GetChild("value").Text
wrb.AddTagAddress(tagName, tagAddress, tagValue)
@@ -259,10 +262,11 @@ func (m DriverTestsuite) ExecuteStep(t *testing.T, connection plc4go.PlcConnecti
return errors.Wrap(err, "error serializing response")
}
actualResponse := xmlWriteBuffer.GetXmlString()
- // Get the reference XML
- referenceSerialized := step.payload.XMLPretty()
+ // Compare only the values sections: like on the Java side, the expected XML may
+ // contain a request element whose serialization is implementation-specific.
+ actualSection, referenceSerialized := extractResponseSection(actualResponse, step.payload, "values")
// Compare the results
- err = CompareResults(t, []byte(actualResponse), []byte(referenceSerialized))
+ err = CompareResults(t, []byte(actualSection), []byte(referenceSerialized))
if err != nil {
return errors.Wrap(err, "Error comparing the results")
}
@@ -286,10 +290,11 @@ func (m DriverTestsuite) ExecuteStep(t *testing.T, connection plc4go.PlcConnecti
return errors.Wrap(err, "error serializing response")
}
actualResponse := xmlWriteBuffer.GetXmlString()
- // Get the reference XML
- referenceSerialized := step.payload.XMLPretty()
+ // Compare only the response-code sections: like on the Java side, the expected XML
+ // may contain a request element whose serialization is implementation-specific.
+ actualSection, referenceSerialized := extractResponseSection(actualResponse, step.payload, "responseCodes")
// Compare the results
- err = CompareResults(t, []byte(actualResponse), []byte(referenceSerialized))
+ err = CompareResults(t, []byte(actualSection), []byte(referenceSerialized))
if err != nil {
return errors.Wrap(err, "Error comparing the results")
}
@@ -565,6 +570,68 @@ type ConnectionConnectAwaiter interface {
SetAwaitDisconnectComplete(awaitComplete bool)
}
+// convertApiRequestStructValue converts a structured api-request value (a PlcStruct element
+// with one member element per property) into the member-name-to-value map form the driver
+// value handlers accept, recursing for nested structs. Plain text values return nil.
+func convertApiRequestStructValue(valueNode *xmldom.Node) any {
+ if valueNode == nil || len(valueNode.Children) != 1 || valueNode.Children[0].Name != "PlcStruct" {
+ return nil
+ }
+ return structValueNodeToMap(valueNode.Children[0])
+}
+
+func structValueNodeToMap(structNode *xmldom.Node) map[string]any {
+ members := map[string]any{}
+ for _, member := range structNode.Children {
+ switch {
+ case len(member.Children) == 1 && member.Children[0].Name == "PlcStruct":
+ members[member.Name] = structValueNodeToMap(member.Children[0])
+ case len(member.Children) == 1:
+ members[member.Name] = member.Children[0].Text
+ default:
+ members[member.Name] = member.Text
+ }
+ }
+ return members
+}
+
+// extractResponseSection narrows an api-response comparison to the named child element
+// (e.g. "values" of a PlcReadResponse), mirroring the Java driver testsuite runner: the
+// reference XML may contain sections (like the request) whose serialization is
+// implementation-specific. When either side lacks the section, both are returned unchanged.
+//
+// Within the section, sibling elements are sorted by name on both sides before comparing:
+// response values are keyed by tag/member name (maps in the API), so their order is not
+// part of the contract and differs between implementations.
+func extractResponseSection(actualResponse string, reference xmldom.Node, sectionName string) (string, string) {
+ referenceSection := reference.GetChild(sectionName)
+ if referenceSection == nil {
+ return actualResponse, reference.XMLPretty()
+ }
+ actualDocument, err := xmldom.Parse(strings.NewReader(actualResponse))
+ if err != nil {
+ return actualResponse, reference.XMLPretty()
+ }
+ actualSection := actualDocument.Root.GetChild(sectionName)
+ if actualSection == nil {
+ return actualResponse, reference.XMLPretty()
+ }
+ sortChildElementsByName(actualSection)
+ sortChildElementsByName(referenceSection)
+ return actualSection.XMLPretty(), referenceSection.XMLPretty()
+}
+
+// sortChildElementsByName recursively brings sibling elements into a canonical (name-sorted)
+// order. Only safe where sibling names are unique keys, as they are in api-response sections.
+func sortChildElementsByName(node *xmldom.Node) {
+ sort.SliceStable(node.Children, func(i, j int) bool {
+ return node.Children[i].Name < node.Children[j].Name
+ })
+ for _, child := range node.Children {
+ sortChildElementsByName(child)
+ }
+}
+
func ParseDriverTestsuiteXml(t *testing.T, testPath string) *xmldom.Node {
// Get the current working directory
path, err := os.Getwd()
diff --git a/plc4go/spi/utils/ReadBufferByteBased.go b/plc4go/spi/utils/ReadBufferByteBased.go
index 6f6ea829ce..9cc00d9720 100644
--- a/plc4go/spi/utils/ReadBufferByteBased.go
+++ b/plc4go/spi/utils/ReadBufferByteBased.go
@@ -27,6 +27,7 @@ import (
"os"
"strconv"
"strings"
+ "unicode/utf16"
"github.com/rs/zerolog/log"
@@ -469,19 +470,38 @@ func (rb *byteReadBuffer) ReadBigFloat(logicalName string, bitLength uint8, _ ..
return big.NewFloat(readFloat64), nil
}
-func (rb *byteReadBuffer) ReadString(logicalName string, bitLength uint32, _ ...WithReaderArgs) (string, error) {
+func (rb *byteReadBuffer) ReadString(logicalName string, bitLength uint32, readerArgs ...WithReaderArgs) (string, error) {
stringBytes, err := rb.ReadByteArray(logicalName, int(bitLength/8))
if err != nil {
- return "", errors.Wrap(err, "Error reading big int")
- }
- // TODO: make the null-termination a reader arg
- // End the string at the 0-character.
- for i, value := range stringBytes {
- if value == 0x00 {
- return string(stringBytes[0:i]), nil
+ return "", errors.Wrap(err, "Error reading string bytes")
+ }
+ encoding := strings.ToUpper(strings.ReplaceAll(BufferCommons{}.ExtractEncoding(UpcastReaderArgs(readerArgs...)...), "-", ""))
+ switch encoding {
+ case "UTF16", "UTF16LE", "UTF16BE":
+ byteOrder := binary.ByteOrder(binary.LittleEndian)
+ if encoding == "UTF16BE" {
+ byteOrder = binary.BigEndian
+ }
+ codeUnits := make([]uint16, 0, len(stringBytes)/2)
+ for i := 0; i+1 < len(stringBytes); i += 2 {
+ codeUnit := byteOrder.Uint16(stringBytes[i : i+2])
+ // End the string at the 0-character.
+ if codeUnit == 0x0000 {
+ break
+ }
+ codeUnits = append(codeUnits, codeUnit)
+ }
+ return string(utf16.Decode(codeUnits)), nil
+ default:
+ // TODO: make the null-termination a reader arg
+ // End the string at the 0-character.
+ for i, value := range stringBytes {
+ if value == 0x00 {
+ return string(stringBytes[0:i]), nil
+ }
}
+ return string(stringBytes), nil
}
- return string(stringBytes), nil
}
func (rb *byteReadBuffer) CloseContext(_ string, _ ...WithReaderArgs) error {
diff --git a/plc4go/spi/utils/WriteBufferXmlBased.go b/plc4go/spi/utils/WriteBufferXmlBased.go
index 8c370f8784..d3ae02aa27 100644
--- a/plc4go/spi/utils/WriteBufferXmlBased.go
+++ b/plc4go/spi/utils/WriteBufferXmlBased.go
@@ -196,16 +196,17 @@ func (x *xmlWriteBuffer) WriteBigInt(logicalName string, bitLength uint8, value
func (x *xmlWriteBuffer) WriteFloat32(logicalName string, bitLength uint8, value float32, writerArgs ...WithWriterArgs) error {
x.move(uint(bitLength))
- // Render the shortest decimal that round-trips back to the same float32, the
- // way Java's Float.toString does. Printing a fixed 16 fraction digits instead
- // spells out the full binary expansion (9.87 -> 9.8699998855590820), which no
- // testsuite reference - all authored against the Java output - can match.
+ // Render the shortest decimal that round-trips back to the same float32. Printing a
+ // fixed number of fraction digits instead spells out the full binary expansion
+ // (9.87 -> 9.8699998855590820), which no testsuite reference can match. Fraction-digit
+ // differences to other implementations (13 vs 13.0) are absorbed by the comparison.
return x.encodeElement(logicalName, strconv.FormatFloat(float64(value), 'f', -1, 32), x.generateAttr(rwFloatKey, uint(bitLength), writerArgs...), writerArgs...)
}
func (x *xmlWriteBuffer) WriteFloat64(logicalName string, bitLength uint8, value float64, writerArgs ...WithWriterArgs) error {
x.move(uint(bitLength))
- return x.encodeElement(logicalName, fmt.Sprintf("%32.32f", value), x.generateAttr(rwFloatKey, uint(bitLength), writerArgs...), writerArgs...)
+ // Shortest round-trip form, for the same reason as WriteFloat32.
+ return x.encodeElement(logicalName, strconv.FormatFloat(value, 'f', -1, 64), x.generateAttr(rwFloatKey, uint(bitLength), writerArgs...), writerArgs...)
}
func (x *xmlWriteBuffer) WriteBigFloat(logicalName string, bitLength uint8, value *big.Float, writerArgs ...WithWriterArgs) error {
diff --git a/plc4go/spi/values/PlcCHAR.go b/plc4go/spi/values/PlcCHAR.go
index 1129ae4cf5..94eba4bcbf 100644
--- a/plc4go/spi/values/PlcCHAR.go
+++ b/plc4go/spi/values/PlcCHAR.go
@@ -69,7 +69,7 @@ func (m PlcCHAR) Serialize() ([]byte, error) {
}
func (m PlcCHAR) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcCHAR", 8, m.value)
+ return writeBuffer.WriteString("PlcCHAR", 8, m.value, utils.WithEncoding("UTF-8"))
}
func (m PlcCHAR) String() string {
diff --git a/plc4go/spi/values/PlcDATE.go b/plc4go/spi/values/PlcDATE.go
index 6e4da89c3b..b419a17eb1 100644
--- a/plc4go/spi/values/PlcDATE.go
+++ b/plc4go/spi/values/PlcDATE.go
@@ -127,7 +127,7 @@ func (m PlcDATE) Serialize() ([]byte, error) {
}
func (m PlcDATE) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcDATE", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcDATE", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcDATE) String() string {
diff --git a/plc4go/spi/values/PlcDATE_AND_LTIME.go b/plc4go/spi/values/PlcDATE_AND_LTIME.go
index 539cda1eb5..d1c813ba60 100644
--- a/plc4go/spi/values/PlcDATE_AND_LTIME.go
+++ b/plc4go/spi/values/PlcDATE_AND_LTIME.go
@@ -141,7 +141,7 @@ func (m PlcDATE_AND_LTIME) Serialize() ([]byte, error) {
}
func (m PlcDATE_AND_LTIME) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcDATE_AND_LTIME", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcDATE_AND_LTIME", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcDATE_AND_LTIME) String() string {
diff --git a/plc4go/spi/values/PlcDATE_AND_TIME.go b/plc4go/spi/values/PlcDATE_AND_TIME.go
index 0701990cf0..ca1218d27a 100644
--- a/plc4go/spi/values/PlcDATE_AND_TIME.go
+++ b/plc4go/spi/values/PlcDATE_AND_TIME.go
@@ -127,7 +127,9 @@ func (m PlcDATE_AND_TIME) GetDateTime() time.Time {
}
func (m PlcDATE_AND_TIME) GetString() string {
- return fmt.Sprintf("%v", m.GetDateTime())
+ // DATE_AND_TIME is a wall-clock value without a time zone: render the instant's UTC
+ // wall time in ISO-8601, with the fraction only when there is one.
+ return m.GetDateTime().UTC().Format("2006-01-02T15:04:05.999999999")
}
func (m PlcDATE_AND_TIME) GetPlcValueType() apiValues.PlcValueType {
@@ -143,7 +145,7 @@ func (m PlcDATE_AND_TIME) Serialize() ([]byte, error) {
}
func (m PlcDATE_AND_TIME) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcDATE_AND_TIME", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcDATE_AND_TIME", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcDATE_AND_TIME) String() string {
diff --git a/plc4go/spi/values/PlcDWORD.go b/plc4go/spi/values/PlcDWORD.go
index 378b189230..5c2cf73a68 100644
--- a/plc4go/spi/values/PlcDWORD.go
+++ b/plc4go/spi/values/PlcDWORD.go
@@ -218,7 +218,7 @@ func (m PlcDWORD) Serialize() ([]byte, error) {
}
func (m PlcDWORD) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteUint32("PlcDINT", 32, m.value)
+ return writeBuffer.WriteUint32("PlcDWORD", 32, m.value)
}
func (m PlcDWORD) String() string {
diff --git a/plc4go/spi/values/PlcLDATE.go b/plc4go/spi/values/PlcLDATE.go
index d9aea97f53..9fae901e2f 100644
--- a/plc4go/spi/values/PlcLDATE.go
+++ b/plc4go/spi/values/PlcLDATE.go
@@ -94,7 +94,7 @@ func (m PlcLDATE) Serialize() ([]byte, error) {
}
func (m PlcLDATE) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcLDATE", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcLDATE", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcLDATE) String() string {
diff --git a/plc4go/spi/values/PlcLDATE_AND_TIME.go b/plc4go/spi/values/PlcLDATE_AND_TIME.go
index c2e80a9eae..11ad576844 100644
--- a/plc4go/spi/values/PlcLDATE_AND_TIME.go
+++ b/plc4go/spi/values/PlcLDATE_AND_TIME.go
@@ -93,7 +93,7 @@ func (m PlcLDATE_AND_TIME) Serialize() ([]byte, error) {
}
func (m PlcLDATE_AND_TIME) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcLDATE_AND_TIME", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcLDATE_AND_TIME", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcLDATE_AND_TIME) String() string {
diff --git a/plc4go/spi/values/PlcLTIME.go b/plc4go/spi/values/PlcLTIME.go
index 27eefad7e1..f49b985cba 100644
--- a/plc4go/spi/values/PlcLTIME.go
+++ b/plc4go/spi/values/PlcLTIME.go
@@ -70,7 +70,7 @@ func (m PlcLTIME) IsString() bool {
}
func (m PlcLTIME) GetString() string {
- return fmt.Sprintf("PT%0.fS", m.GetDuration().Seconds())
+ return formatDurationIso8601(m.GetDuration())
}
func (m PlcLTIME) GetPlcValueType() apiValues.PlcValueType {
@@ -86,7 +86,7 @@ func (m PlcLTIME) Serialize() ([]byte, error) {
}
func (m PlcLTIME) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcLTIME", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcLTIME", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcLTIME) String() string {
diff --git a/plc4go/spi/values/PlcLTIME_OF_DAY.go b/plc4go/spi/values/PlcLTIME_OF_DAY.go
index ec72f1a784..c65ebc982f 100644
--- a/plc4go/spi/values/PlcLTIME_OF_DAY.go
+++ b/plc4go/spi/values/PlcLTIME_OF_DAY.go
@@ -99,7 +99,7 @@ func (m PlcLTIME_OF_DAY) Serialize() ([]byte, error) {
}
func (m PlcLTIME_OF_DAY) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcLTIME_OF_DAY", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcLTIME_OF_DAY", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcLTIME_OF_DAY) String() string {
diff --git a/plc4go/spi/values/PlcSINT.go b/plc4go/spi/values/PlcSINT.go
index 32e2ebea4e..0605f5034a 100644
--- a/plc4go/spi/values/PlcSINT.go
+++ b/plc4go/spi/values/PlcSINT.go
@@ -151,7 +151,7 @@ func (m PlcSINT) Serialize() ([]byte, error) {
}
func (m PlcSINT) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteInt8("PlcINT", 8, m.value)
+ return writeBuffer.WriteInt8("PlcSINT", 8, m.value)
}
func (m PlcSINT) String() string {
diff --git a/plc4go/spi/values/PlcSTRING.go b/plc4go/spi/values/PlcSTRING.go
index 003b00e778..5ca4eab578 100644
--- a/plc4go/spi/values/PlcSTRING.go
+++ b/plc4go/spi/values/PlcSTRING.go
@@ -69,7 +69,7 @@ func (m PlcSTRING) Serialize() ([]byte, error) {
}
func (m PlcSTRING) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcSTRING", uint32(len([]rune(m.value))*8), m.value)
+ return writeBuffer.WriteString("PlcSTRING", uint32(len([]rune(m.value))*8), m.value, utils.WithEncoding("UTF-8"))
}
func (m PlcSTRING) String() string {
diff --git a/plc4go/spi/values/PlcStruct.go b/plc4go/spi/values/PlcStruct.go
index fa2aa95d10..e2e63d96dd 100644
--- a/plc4go/spi/values/PlcStruct.go
+++ b/plc4go/spi/values/PlcStruct.go
@@ -23,6 +23,7 @@ import (
"context"
"encoding/binary"
"fmt"
+ "sort"
"strings"
"github.com/rs/zerolog/log"
@@ -34,12 +35,47 @@ import (
type PlcStruct struct {
values map[string]apiValues.PlcValue
+ // order fixes the member order for GetKeys and serialization: insertion order when
+ // constructed with NewPlcStructOrdered (e.g. the declaration order of a decoded PLC
+ // struct), sorted key order otherwise - never Go's random map iteration order.
+ order []string
PlcValueAdapter
}
func NewPlcStruct(value map[string]apiValues.PlcValue) PlcStruct {
+ order := make([]string, 0, len(value))
+ for key := range value {
+ order = append(order, key)
+ }
+ sort.Strings(order)
return PlcStruct{
values: value,
+ order: order,
+ }
+}
+
+// NewPlcStructOrdered creates a PlcStruct whose members keep the given order (usually the
+// struct's declaration order on the PLC). Keys missing from values are ignored; values not
+// listed in order are appended in sorted order.
+func NewPlcStructOrdered(value map[string]apiValues.PlcValue, order []string) PlcStruct {
+ seen := make(map[string]struct{}, len(order))
+ orderedKeys := make([]string, 0, len(value))
+ for _, key := range order {
+ if _, ok := value[key]; ok {
+ orderedKeys = append(orderedKeys, key)
+ seen[key] = struct{}{}
+ }
+ }
+ var rest []string
+ for key := range value {
+ if _, ok := seen[key]; !ok {
+ rest = append(rest, key)
+ }
+ }
+ sort.Strings(rest)
+ return PlcStruct{
+ values: value,
+ order: append(orderedKeys, rest...),
}
}
@@ -67,11 +103,7 @@ func (m PlcStruct) IsStruct() bool {
}
func (m PlcStruct) GetKeys() []string {
- var keys []string
- for k := range m.values {
- keys = append(keys, k)
- }
- return keys
+ return append([]string(nil), m.order...)
}
func (m PlcStruct) HasKey(key string) bool {
@@ -99,7 +131,8 @@ func (m PlcStruct) IsString() bool {
func (m PlcStruct) GetString() string {
var sb strings.Builder
sb.WriteString("PlcStruct{\n")
- for tagName, tagValue := range m.values {
+ for _, tagName := range m.order {
+ tagValue := m.values[tagName]
sb.WriteString(" ")
sb.WriteString(tagName)
sb.WriteString(": \"")
@@ -130,7 +163,8 @@ func (m PlcStruct) SerializeWithWriteBuffer(ctx context.Context, writeBuffer uti
if err := writeBuffer.PushContext("PlcStruct"); err != nil {
return err
}
- for tagName, tagValue := range m.values {
+ for _, tagName := range m.order {
+ tagValue := m.values[tagName]
if err := writeBuffer.PushContext(tagName); err != nil {
return err
}
diff --git a/plc4go/spi/values/PlcTIME.go b/plc4go/spi/values/PlcTIME.go
index 67ab66bad0..cf13b1cf33 100644
--- a/plc4go/spi/values/PlcTIME.go
+++ b/plc4go/spi/values/PlcTIME.go
@@ -23,6 +23,8 @@ import (
"context"
"encoding/binary"
"fmt"
+ "strconv"
+ "strings"
"time"
apiValues "github.com/apache/plc4x/plc4go/pkg/api/values"
@@ -70,7 +72,76 @@ func (m PlcTIME) IsString() bool {
}
func (m PlcTIME) GetString() string {
- return fmt.Sprintf("PT%0.fS", m.GetDuration().Seconds())
+ return formatDurationIso8601(m.GetDuration())
+}
+
+// parseDurationIso8601 parses the time-only ISO-8601 duration form produced by
+// formatDurationIso8601 ("PT24015H23M12.034002044S").
+func parseDurationIso8601(value string) (time.Duration, error) {
+ input := value
+ negative := strings.HasPrefix(input, "-")
+ input = strings.TrimPrefix(input, "-")
+ rest, ok := strings.CutPrefix(input, "PT")
+ if !ok || rest == "" {
+ return 0, fmt.Errorf("invalid ISO-8601 duration %q", value)
+ }
+ var duration time.Duration
+ for _, component := range []struct {
+ designator string
+ unit time.Duration
+ }{{"H", time.Hour}, {"M", time.Minute}, {"S", time.Second}} {
+ number, remainder, found := strings.Cut(rest, component.designator)
+ if !found {
+ continue
+ }
+ amount, err := strconv.ParseFloat(number, 64)
+ if err != nil {
+ return 0, fmt.Errorf("invalid ISO-8601 duration %q: %v", value, err)
+ }
+ duration += time.Duration(amount * float64(component.unit))
+ rest = remainder
+ }
+ if rest != "" {
+ return 0, fmt.Errorf("invalid ISO-8601 duration %q", value)
+ }
+ if negative {
+ duration = -duration
+ }
+ return duration, nil
+}
+
+// formatDurationIso8601 renders a duration in ISO-8601 form with an hours/minutes/seconds
+// decomposition and the sub-second fraction ("PT24015H23M12.034002044S"), the shared
+// cross-implementation rendering of duration-typed PLC values.
+func formatDurationIso8601(duration time.Duration) string {
+ if duration == 0 {
+ return "PT0S"
+ }
+ var sb strings.Builder
+ if duration < 0 {
+ sb.WriteString("-")
+ duration = -duration
+ }
+ sb.WriteString("PT")
+ totalSeconds := int64(duration / time.Second)
+ nanos := int64(duration % time.Second)
+ hours := totalSeconds / 3600
+ minutes := (totalSeconds % 3600) / 60
+ seconds := totalSeconds % 60
+ if hours != 0 {
+ fmt.Fprintf(&sb, "%dH", hours)
+ }
+ if minutes != 0 {
+ fmt.Fprintf(&sb, "%dM", minutes)
+ }
+ if seconds != 0 || nanos != 0 || (hours == 0 && minutes == 0) {
+ if nanos == 0 {
+ fmt.Fprintf(&sb, "%dS", seconds)
+ } else {
+ fmt.Fprintf(&sb, "%d.%sS", seconds, strings.TrimRight(fmt.Sprintf("%09d", nanos), "0"))
+ }
+ }
+ return sb.String()
}
func (m PlcTIME) GetPlcValueType() apiValues.PlcValueType {
@@ -86,7 +157,7 @@ func (m PlcTIME) Serialize() ([]byte, error) {
}
func (m PlcTIME) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcTIME", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcTIME", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcTIME) String() string {
diff --git a/plc4go/spi/values/PlcTIME_OF_DAY.go b/plc4go/spi/values/PlcTIME_OF_DAY.go
index b9e6f47084..d25891f8f8 100644
--- a/plc4go/spi/values/PlcTIME_OF_DAY.go
+++ b/plc4go/spi/values/PlcTIME_OF_DAY.go
@@ -126,7 +126,7 @@ func (m PlcTIME_OF_DAY) Serialize() ([]byte, error) {
}
func (m PlcTIME_OF_DAY) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcTIME_OF_DAY", uint32(len([]rune(m.GetString()))*8), m.GetString())
+ return writeBuffer.WriteString("PlcTIME_OF_DAY", uint32(len([]rune(m.GetString()))*8), m.GetString(), utils.WithEncoding("UTF-8"))
}
func (m PlcTIME_OF_DAY) String() string {
diff --git a/plc4go/spi/values/PlcULINT.go b/plc4go/spi/values/PlcULINT.go
index 97a1cee62e..826b86f381 100644
--- a/plc4go/spi/values/PlcULINT.go
+++ b/plc4go/spi/values/PlcULINT.go
@@ -173,7 +173,7 @@ func (m PlcULINT) Serialize() ([]byte, error) {
}
func (m PlcULINT) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteUint64("PlcUINT", 64, m.value)
+ return writeBuffer.WriteUint64("PlcULINT", 64, m.value)
}
func (m PlcULINT) String() string {
diff --git a/plc4go/spi/values/PlcValueHandler.go b/plc4go/spi/values/PlcValueHandler.go
index dc4c396689..24dd00a746 100644
--- a/plc4go/spi/values/PlcValueHandler.go
+++ b/plc4go/spi/values/PlcValueHandler.go
@@ -349,20 +349,33 @@ func (m DefaultValueHandler) NewPlcValueFromType(valueType apiValues.PlcValueTyp
// Temporal Values
// - Duration
- case apiValues.TIME:
+ case apiValues.TIME, apiValues.LTIME:
+ var duration time.Duration
if isString {
- return nil, errors.New("string to IEC61131_TIME conversion not implemented")
+ parsed, err := parseDurationIso8601(stringValue)
+ if err != nil {
+ return nil, errors.Wrap(err, "couldn't parse string value '"+stringValue+"' to a duration")
+ }
+ duration = parsed
} else {
casted, ok := value.(time.Duration)
if !ok {
return nil, errors.New("couldn't cast value of type " + reflect.TypeOf(value).Name() + " to time.Duration")
}
- return NewPlcTIME(casted), nil
+ duration = casted
+ }
+ if valueType == apiValues.LTIME {
+ return NewPlcLTIME(duration), nil
}
+ return NewPlcTIME(duration), nil
// - Date
case apiValues.DATE:
if isString {
- return nil, errors.New("string to IEC61131_DATE conversion not implemented")
+ casted, err := time.Parse("2006-01-02", stringValue)
+ if err != nil {
+ return nil, errors.Wrap(err, "couldn't parse string value '"+stringValue+"' to a date")
+ }
+ return NewPlcDATE(casted), nil
} else {
casted, ok := value.(time.Time)
if !ok {
@@ -373,7 +386,11 @@ func (m DefaultValueHandler) NewPlcValueFromType(valueType apiValues.PlcValueTyp
// - Time
case apiValues.TIME_OF_DAY:
if isString {
- return nil, errors.New("string to IEC61131_TIME_OF_DAY conversion not implemented")
+ casted, err := time.Parse("15:04:05.999999999", stringValue)
+ if err != nil {
+ return nil, errors.Wrap(err, "couldn't parse string value '"+stringValue+"' to a time of day")
+ }
+ return NewPlcTIME_OF_DAY(casted), nil
} else {
casted, ok := value.(time.Time)
if !ok {
@@ -384,7 +401,11 @@ func (m DefaultValueHandler) NewPlcValueFromType(valueType apiValues.PlcValueTyp
// - Date and Time
case apiValues.DATE_AND_TIME:
if isString {
- return nil, errors.New("string to IEC61131_DATE_AND_TIME conversion not implemented")
+ casted, err := time.Parse("2006-01-02T15:04:05.999999999", stringValue)
+ if err != nil {
+ return nil, errors.Wrap(err, "couldn't parse string value '"+stringValue+"' to a date and time")
+ }
+ return NewPlcDATE_AND_TIME(casted), nil
} else {
casted, ok := value.(time.Time)
if !ok {
diff --git a/plc4go/spi/values/PlcWSTRING.go b/plc4go/spi/values/PlcWSTRING.go
index 5ea495de6d..7e39c608c6 100644
--- a/plc4go/spi/values/PlcWSTRING.go
+++ b/plc4go/spi/values/PlcWSTRING.go
@@ -23,6 +23,7 @@ import (
"context"
"encoding/binary"
"fmt"
+ "unicode/utf16"
apiValues "github.com/apache/plc4x/plc4go/pkg/api/values"
"github.com/apache/plc4x/plc4go/spi/utils"
@@ -69,7 +70,10 @@ func (m PlcWSTRING) Serialize() ([]byte, error) {
}
func (m PlcWSTRING) SerializeWithWriteBuffer(ctx context.Context, writeBuffer utils.WriteBuffer) error {
- return writeBuffer.WriteString("PlcSTRING", uint32(len(m.value)*8), m.value)
+ // The bit length is the UTF-16 size of the value including the byte order mark
+ // (2 bytes per code unit plus 2 for the BOM), matching the Java implementation.
+ bitLength := uint32((1 + len(utf16.Encode([]rune(m.value)))) * 16)
+ return writeBuffer.WriteString("PlcWSTRING", bitLength, m.value, utils.WithEncoding("UTF-8"))
}
func (m PlcWSTRING) String() string {
diff --git a/plc4go/tests/drivers/tests/ads_driver_test.go b/plc4go/tests/drivers/tests/ads_driver_test.go
index 9cee641f37..204de1357b 100644
--- a/plc4go/tests/drivers/tests/ads_driver_test.go
+++ b/plc4go/tests/drivers/tests/ads_driver_test.go
@@ -30,7 +30,6 @@ import (
)
func TestAdsDriver(t *testing.T) {
- t.Skip("I have to port the commands for reading the symbol-table first")
parser := func(readBufferByteBased utils.ReadBufferByteBased) (any, error) {
return readWriteModel.AmsTCPPacketParseWithBuffer(t.Context(), readBufferByteBased)
}
diff --git a/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/AdsDriverIT.java b/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/AdsDriverIT.java
index f0e567d83b..c984d16442 100644
--- a/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/AdsDriverIT.java
+++ b/plc4j/drivers/ads/src/test/java/org/apache/plc4x/java/ads/AdsDriverIT.java
@@ -23,7 +23,7 @@ import org.apache.plc4x.java.utils.testutils.driver.DriverTestsuiteRunner;
public class AdsDriverIT extends DriverTestsuiteRunner {
public AdsDriverIT() {
- super("/protocols/ads/DriverTestsuite.xml", "org.apache.plc4x.java.ads.readwrite", true);
+ super("/protocols/ads/DriverTestsuite.xml", "org.apache.plc4x.java.ads.readwrite", false);
}
}
diff --git a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcBYTE.java b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcBYTE.java
index 2348e70f3c..6b828d3aad 100644
--- a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcBYTE.java
+++ b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcBYTE.java
@@ -291,7 +291,8 @@ public class PlcBYTE extends PlcIECValue<Short> {
@Override
public void serialize(WriteBuffer writeBuffer) throws BufferException {
- writeBuffer.writeSignedShort(8, value, WithOption.WithName(getClass().getSimpleName()));
+ // A BYTE is a bit string, not a number: render it the way byte fields render (0x..).
+ writeBuffer.writeBits(8, new byte[]{(byte) (value & 0xFF)}, WithOption.WithName(getClass().getSimpleName()));
}
}
diff --git a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcDWORD.java b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcDWORD.java
index cd456d6abf..b78398c811 100644
--- a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcDWORD.java
+++ b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcDWORD.java
@@ -294,7 +294,7 @@ public class PlcDWORD extends PlcIECValue<Long> {
@Override
public void serialize(WriteBuffer writeBuffer) throws BufferException {
- writeBuffer.writeSignedLong(32, value, WithOption.WithName(getClass().getSimpleName()));
+ writeBuffer.writeUnsignedLong(32, value, WithOption.WithName(getClass().getSimpleName()));
}
}
diff --git a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcLWORD.java b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcLWORD.java
index 4ea7931670..06bacfb7b3 100644
--- a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcLWORD.java
+++ b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcLWORD.java
@@ -308,7 +308,7 @@ public class PlcLWORD extends PlcIECValue<BigInteger> {
@Override
public void serialize(WriteBuffer writeBuffer) throws BufferException {
- writeBuffer.writeSignedBigInteger(64, value, WithOption.WithName(getClass().getSimpleName()));
+ writeBuffer.writeUnsignedBigInteger(64, value, WithOption.WithName(getClass().getSimpleName()));
}
}
diff --git a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java
index 98acb6cb03..31fdd0c7d8 100644
--- a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java
+++ b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java
@@ -168,7 +168,10 @@ public class PlcTIME extends PlcIECValue<Duration> {
@Override
public void serialize(WriteBuffer writeBuffer) throws BufferException {
- writeBuffer.writeSignedLong(32, value.toMillis(), WithOption.WithName(getClass().getSimpleName()));
+ // Durations render as ISO-8601 (like PlcLTIME), not as a raw millisecond count.
+ String valueString = value.toString();
+ writeBuffer.writeString(valueString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length * 8,
+ valueString, WithOption.WithName(getClass().getSimpleName()), WithOption.WithEncoding("UTF8"));
}
}
diff --git a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcWORD.java b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcWORD.java
index 7909e13a4e..1325d99c60 100644
--- a/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcWORD.java
+++ b/plc4j/spi/values/src/main/java/org/apache/plc4x/java/spi/values/PlcWORD.java
@@ -288,7 +288,7 @@ public class PlcWORD extends PlcIECValue<Integer> {
@Override
public void serialize(WriteBuffer writeBuffer) throws BufferException {
- writeBuffer.writeSignedInt(16, value, WithOption.WithName(getClass().getSimpleName()));
+ writeBuffer.writeUnsignedInt(16, value, WithOption.WithName(getClass().getSimpleName()));
}
}
diff --git a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/ApiResponseHandler.java b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/ApiResponseHandler.java
index 89fc7c09ad..afd14f7c2e 100644
--- a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/ApiResponseHandler.java
+++ b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/java/utils/testutils/driver/internal/handlers/ApiResponseHandler.java
@@ -237,7 +237,7 @@ public class ApiResponseHandler {
// Handle RAW_BYTE_ARRAY first since it reports isList()=true but should be serialized as a hex string
if (plcValueType == PlcValueType.RAW_BYTE_ARRAY) {
Element valueElement = parentElement.addElement("PlcRAW_BYTE_ARRAY");
- addTypeAttributes(valueElement, plcValueType);
+ addTypeAttributes(valueElement, plcValueType, value.toString());
valueElement.setText(value.toString());
} else if (value.isStruct()) {
Element structElement = parentElement.addElement("PlcStruct");
@@ -255,25 +255,35 @@ public class ApiResponseHandler {
String typeName = (plcValueType != null) ? "Plc" + plcValueType.name() : "PlcValue";
Element valueElement = parentElement.addElement(typeName);
+ // A BYTE is a bit string, not a number: render it the way byte fields render (0x..).
+ String text = (plcValueType == PlcValueType.BYTE)
+ ? String.format("0x%02x", value.getShort() & 0xFF)
+ : value.toString();
+
// Add dataType and bitLength attributes based on the PlcValueType
if (plcValueType != null) {
- addTypeAttributes(valueElement, plcValueType);
+ addTypeAttributes(valueElement, plcValueType, text);
}
- valueElement.setText(value.toString());
+ valueElement.setText(text);
}
}
/**
* Adds dataType and bitLength attributes to an element based on the PlcValueType.
+ * The rendered text is passed in for the types whose bit length derives from it.
*/
- private void addTypeAttributes(Element element, PlcValueType plcValueType) {
+ private void addTypeAttributes(Element element, PlcValueType plcValueType, String text) {
switch (plcValueType) {
case BOOL -> {
element.addAttribute("dataType", "bit");
element.addAttribute("bitLength", "1");
}
- case BYTE, USINT -> {
+ case BYTE -> {
+ element.addAttribute("dataType", "byte");
+ element.addAttribute("bitLength", "8");
+ }
+ case USINT -> {
element.addAttribute("dataType", "uint");
element.addAttribute("bitLength", "8");
}
@@ -313,12 +323,18 @@ public class ApiResponseHandler {
element.addAttribute("dataType", "float");
element.addAttribute("bitLength", "64");
}
- case STRING, WSTRING, CHAR, WCHAR -> {
+ case WSTRING -> {
element.addAttribute("dataType", "string");
+ // UTF-16 size of the value including the byte order mark.
+ element.addAttribute("bitLength", String.valueOf((text.codePointCount(0, text.length()) + 1) * 16));
element.addAttribute("encoding", "UTF-8");
}
- case TIME, LTIME, DATE, LDATE, TIME_OF_DAY, LTIME_OF_DAY, DATE_AND_TIME, LDATE_AND_TIME, DATE_AND_LTIME -> {
- element.addAttribute("dataType", "time");
+ case STRING, CHAR, WCHAR,
+ // Temporal values render as their ISO-8601 string form.
+ TIME, LTIME, DATE, LDATE, TIME_OF_DAY, LTIME_OF_DAY, DATE_AND_TIME, LDATE_AND_TIME, DATE_AND_LTIME -> {
+ element.addAttribute("dataType", "string");
+ element.addAttribute("bitLength", String.valueOf(text.getBytes(java.nio.charset.StandardCharsets.UTF_8).length * 8));
+ element.addAttribute("encoding", "UTF-8");
}
case RAW_BYTE_ARRAY -> {
element.addAttribute("dataType", "byte");
diff --git a/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml b/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
index 33d0248a5e..021ce08bfc 100644
--- a/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
+++ b/protocols/ads/src/test/resources/protocols/ads/DriverTestsuite.xml
@@ -1206,72 +1206,72 @@
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
<PlcStruct>
- <hurz_STRING>
- <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
- </hurz_STRING>
- <hurz_LTIME>
- <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
- </hurz_LTIME>
+ <hurz_BOOL>
+ <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
+ </hurz_BOOL>
+ <hurz_BYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x01</PlcBYTE>
+ </hurz_BYTE>
+ <hurz_WORD>
+ <PlcWORD dataType="uint" bitLength="16">2</PlcWORD>
+ </hurz_WORD>
+ <hurz_DWORD>
+ <PlcDWORD dataType="uint" bitLength="32">3</PlcDWORD>
+ </hurz_DWORD>
<hurz_LWORD>
- <PlcLWORD dataType="int" bitLength="64">4</PlcLWORD>
+ <PlcLWORD dataType="uint" bitLength="64">4</PlcLWORD>
</hurz_LWORD>
- <hurz_REAL>
- <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
- </hurz_REAL>
- <hurz_ULINT>
- <PlcULINT dataType="int" bitLength="64">12</PlcULINT>
- </hurz_ULINT>
- <hurz_WSTRING>
- <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
- </hurz_WSTRING>
- <hurz_LINT>
- <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
- </hurz_LINT>
- <hurz_DATE_AND_TIME>
- <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
- </hurz_DATE_AND_TIME>
- <hurz_UINT>
- <PlcUINT dataType="int" bitLength="16">8</PlcUINT>
- </hurz_UINT>
<hurz_SINT>
<PlcSINT dataType="int" bitLength="8">5</PlcSINT>
</hurz_SINT>
<hurz_USINT>
- <PlcUSINT dataType="int" bitLength="8">6</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">6</PlcUSINT>
</hurz_USINT>
- <hurz_LREAL>
- <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
- </hurz_LREAL>
- <hurz_TIME_OF_DAY>
- <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
- </hurz_TIME_OF_DAY>
- <hurz_BOOL>
- <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
- </hurz_BOOL>
+ <hurz_INT>
+ <PlcINT dataType="int" bitLength="16">7</PlcINT>
+ </hurz_INT>
+ <hurz_UINT>
+ <PlcUINT dataType="uint" bitLength="16">8</PlcUINT>
+ </hurz_UINT>
<hurz_DINT>
<PlcDINT dataType="int" bitLength="32">9</PlcDINT>
</hurz_DINT>
<hurz_UDINT>
- <PlcUDINT dataType="int" bitLength="32">10</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">10</PlcUDINT>
</hurz_UDINT>
- <hurz_BYTE>
- <PlcBYTE dataType="int" bitLength="8">1</PlcBYTE>
- </hurz_BYTE>
+ <hurz_LINT>
+ <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
+ </hurz_LINT>
+ <hurz_ULINT>
+ <PlcULINT dataType="uint" bitLength="64">12</PlcULINT>
+ </hurz_ULINT>
+ <hurz_REAL>
+ <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
+ </hurz_REAL>
+ <hurz_LREAL>
+ <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
+ </hurz_LREAL>
<hurz_TIME>
- <PlcTIME dataType="int" bitLength="32">1234</PlcTIME>
+ <PlcTIME dataType="string" bitLength="64" encoding="UTF-8">PT1.234S</PlcTIME>
</hurz_TIME>
- <hurz_DWORD>
- <PlcDWORD dataType="int" bitLength="32">3</PlcDWORD>
- </hurz_DWORD>
- <hurz_INT>
- <PlcINT dataType="int" bitLength="16">7</PlcINT>
- </hurz_INT>
- <hurz_WORD>
- <PlcWORD dataType="int" bitLength="16">2</PlcWORD>
- </hurz_WORD>
+ <hurz_LTIME>
+ <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
+ </hurz_LTIME>
<hurz_DATE>
<PlcDATE dataType="string" bitLength="80" encoding="UTF-8">1978-03-28</PlcDATE>
</hurz_DATE>
+ <hurz_TIME_OF_DAY>
+ <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
+ </hurz_TIME_OF_DAY>
+ <hurz_DATE_AND_TIME>
+ <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
+ </hurz_DATE_AND_TIME>
+ <hurz_STRING>
+ <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
+ </hurz_STRING>
+ <hurz_WSTRING>
+ <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
+ </hurz_WSTRING>
</PlcStruct>
</value>
</PlcResponseItem>
@@ -1305,72 +1305,72 @@
<address>MAIN.hurz_Struct</address>
<value>
<PlcStruct>
- <hurz_STRING>
- <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
- </hurz_STRING>
- <hurz_LTIME>
- <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
- </hurz_LTIME>
+ <hurz_BOOL>
+ <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
+ </hurz_BOOL>
+ <hurz_BYTE>
+ <PlcBYTE dataType="int" bitLength="8">1</PlcBYTE>
+ </hurz_BYTE>
+ <hurz_WORD>
+ <PlcWORD dataType="uint" bitLength="16">2</PlcWORD>
+ </hurz_WORD>
+ <hurz_DWORD>
+ <PlcDWORD dataType="uint" bitLength="32">3</PlcDWORD>
+ </hurz_DWORD>
<hurz_LWORD>
- <PlcLWORD dataType="int" bitLength="64">4</PlcLWORD>
+ <PlcLWORD dataType="uint" bitLength="64">4</PlcLWORD>
</hurz_LWORD>
- <hurz_REAL>
- <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
- </hurz_REAL>
- <hurz_ULINT>
- <PlcULINT dataType="int" bitLength="64">12</PlcULINT>
- </hurz_ULINT>
- <hurz_WSTRING>
- <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
- </hurz_WSTRING>
- <hurz_LINT>
- <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
- </hurz_LINT>
- <hurz_DATE_AND_TIME>
- <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
- </hurz_DATE_AND_TIME>
- <hurz_UINT>
- <PlcUINT dataType="int" bitLength="16">8</PlcUINT>
- </hurz_UINT>
<hurz_SINT>
<PlcSINT dataType="int" bitLength="8">5</PlcSINT>
</hurz_SINT>
<hurz_USINT>
- <PlcUSINT dataType="int" bitLength="8">6</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">6</PlcUSINT>
</hurz_USINT>
- <hurz_LREAL>
- <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
- </hurz_LREAL>
- <hurz_TIME_OF_DAY>
- <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
- </hurz_TIME_OF_DAY>
- <hurz_BOOL>
- <PlcBOOL dataType="bit" bitLength="1">true</PlcBOOL>
- </hurz_BOOL>
+ <hurz_INT>
+ <PlcINT dataType="int" bitLength="16">7</PlcINT>
+ </hurz_INT>
+ <hurz_UINT>
+ <PlcUINT dataType="uint" bitLength="16">8</PlcUINT>
+ </hurz_UINT>
<hurz_DINT>
<PlcDINT dataType="int" bitLength="32">9</PlcDINT>
</hurz_DINT>
<hurz_UDINT>
- <PlcUDINT dataType="int" bitLength="32">10</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">10</PlcUDINT>
</hurz_UDINT>
- <hurz_BYTE>
- <PlcBYTE dataType="int" bitLength="8">1</PlcBYTE>
- </hurz_BYTE>
+ <hurz_LINT>
+ <PlcLINT dataType="int" bitLength="64">11</PlcLINT>
+ </hurz_LINT>
+ <hurz_ULINT>
+ <PlcULINT dataType="uint" bitLength="64">12</PlcULINT>
+ </hurz_ULINT>
+ <hurz_REAL>
+ <PlcREAL dataType="float" bitLength="32">13.0</PlcREAL>
+ </hurz_REAL>
+ <hurz_LREAL>
+ <PlcLREAL dataType="float" bitLength="64">14.0</PlcLREAL>
+ </hurz_LREAL>
<hurz_TIME>
<PlcTIME dataType="string" bitLength="64" encoding="UTF-8">PT1.234S</PlcTIME>
</hurz_TIME>
- <hurz_DWORD>
- <PlcDWORD dataType="int" bitLength="32">3</PlcDWORD>
- </hurz_DWORD>
- <hurz_INT>
- <PlcINT dataType="int" bitLength="16">7</PlcINT>
- </hurz_INT>
- <hurz_WORD>
- <PlcWORD dataType="int" bitLength="16">2</PlcWORD>
- </hurz_WORD>
+ <hurz_LTIME>
+ <PlcLTIME dataType="string" bitLength="192" encoding="UTF-8">PT24015H23M12.034002044S</PlcLTIME>
+ </hurz_LTIME>
<hurz_DATE>
<PlcDATE dataType="string" bitLength="80" encoding="UTF-8">1978-03-28</PlcDATE>
</hurz_DATE>
+ <hurz_TIME_OF_DAY>
+ <PlcTIME_OF_DAY dataType="string" bitLength="96" encoding="UTF-8">15:36:30.123</PlcTIME_OF_DAY>
+ </hurz_TIME_OF_DAY>
+ <hurz_DATE_AND_TIME>
+ <PlcDATE_AND_TIME dataType="string" bitLength="152" encoding="UTF-8">1996-05-06T15:36:30</PlcDATE_AND_TIME>
+ </hurz_DATE_AND_TIME>
+ <hurz_STRING>
+ <PlcSTRING dataType="string" bitLength="32" encoding="UTF-8">hurz</PlcSTRING>
+ </hurz_STRING>
+ <hurz_WSTRING>
+ <PlcWSTRING dataType="string" bitLength="80" encoding="UTF-8">wolf</PlcWSTRING>
+ </hurz_WSTRING>
</PlcStruct>
</value>
</tag>
diff --git a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml
index 9855becb85..716dbc1771 100644
--- a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml
+++ b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml
@@ -100,7 +100,7 @@
</CANOpenNMTTag>
</tag>
<value>
- <PlcUSINT dataType="int" bitLength="8">128</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">128</PlcUSINT>
</value>
</PlcTagValueItem>
</boot_up_request>
diff --git a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml
index e3d7300fb2..89a19be36b 100644
--- a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml
+++ b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml
@@ -208,7 +208,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUDINT dataType="int" bitLength="32">1717859169</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">1717859169</PlcUDINT>
</value>
</PlcResponseItem>
</expeditedSdo>
@@ -343,7 +343,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUSINT dataType="int" bitLength="8">97</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">97</PlcUSINT>
</value>
</PlcResponseItem>
</expeditedShort>
@@ -525,7 +525,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUDINT dataType="int" bitLength="32">1717859169</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">1717859169</PlcUDINT>
</value>
</PlcResponseItem>
</expeditedSdoCustomAnswer>
diff --git a/protocols/modbus/src/test/resources/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml b/protocols/modbus/src/test/resources/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml
index 58ab5c62e0..1a1ce25989 100644
--- a/protocols/modbus/src/test/resources/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml
+++ b/protocols/modbus/src/test/resources/protocols/modbus/tcp/ManualFactoryModbusTCPDriverTest-testsuite.xml
@@ -129,7 +129,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="uint" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</holding-register_2_BYTE>
@@ -1064,7 +1064,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="uint" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</holding-register_2_BYTE>
diff --git a/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml b/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml
index 7804ad4fc3..a875b5f4d5 100644
--- a/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml
+++ b/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes-little-endian.xml
@@ -313,7 +313,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</hurz>
@@ -401,7 +401,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcTagValueItem>
</hurz>
@@ -506,7 +506,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -594,7 +594,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -699,7 +699,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -789,7 +789,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -1087,7 +1087,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1175,7 +1175,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1473,7 +1473,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1561,7 +1561,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1861,7 +1861,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1951,7 +1951,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -2251,7 +2251,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcResponseItem>
</hurz>
@@ -2341,7 +2341,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcTagValueItem>
</hurz>
diff --git a/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes.xml b/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes.xml
index 1c5b7da057..31b5d343cd 100644
--- a/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes.xml
+++ b/protocols/modbus/src/test/resources/protocols/modbus/tcp/Modbus-all-datatypes.xml
@@ -307,7 +307,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcResponseItem>
</hurz>
@@ -395,7 +395,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcBYTE dataType="int" bitLength="8">42</PlcBYTE>
+ <PlcBYTE dataType="byte" bitLength="8">0x2a</PlcBYTE>
</value>
</PlcTagValueItem>
</hurz>
@@ -500,7 +500,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -588,7 +588,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcWORD dataType="int" bitLength="16">42424</PlcWORD>
+ <PlcWORD dataType="uint" bitLength="16">42424</PlcWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -693,7 +693,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcResponseItem>
</hurz>
@@ -783,7 +783,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcDWORD dataType="int" bitLength="32">4242442424</PlcDWORD>
+ <PlcDWORD dataType="uint" bitLength="32">4242442424</PlcDWORD>
</value>
</PlcTagValueItem>
</hurz>
@@ -1081,7 +1081,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1169,7 +1169,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUSINT dataType="int" bitLength="8">42</PlcUSINT>
+ <PlcUSINT dataType="uint" bitLength="8">42</PlcUSINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1467,7 +1467,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1555,7 +1555,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUINT dataType="int" bitLength="16">42424</PlcUINT>
+ <PlcUINT dataType="uint" bitLength="16">42424</PlcUINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -1855,7 +1855,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcResponseItem>
</hurz>
@@ -1945,7 +1945,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcUDINT dataType="int" bitLength="32">4242442424</PlcUDINT>
+ <PlcUDINT dataType="uint" bitLength="32">4242442424</PlcUDINT>
</value>
</PlcTagValueItem>
</hurz>
@@ -2245,7 +2245,7 @@
<PlcResponseItem>
<code dataType="string" bitLength="16" encoding="UTF-8">OK</code>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcResponseItem>
</hurz>
@@ -2335,7 +2335,7 @@
</ModbusTagHoldingRegister>
</tag>
<value>
- <PlcULINT dataType="int" bitLength="64">4242442424242424242</PlcULINT>
+ <PlcULINT dataType="uint" bitLength="64">4242442424242424242</PlcULINT>
</value>
</PlcTagValueItem>
</hurz>