aioads 0.1.0.dev8__tar.gz → 0.1.0.dev9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/PKG-INFO +1 -1
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ads_symbol_parser.py +39 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/pyproject.toml +2 -1
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ads_symbol_parser.py +155 -11
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/.github/dependabot.yml +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/.github/workflows/ci.yml +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/.github/workflows/publish.yml +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/.gitignore +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/.vscode/settings.json +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/LICENSE +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/README.md +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ads_client.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ads_error_codes.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ads_notifications.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ads_symbol_cache.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ams_address.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ams_header.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ams_service_port.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/ams_tcp_header.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_add_notification.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_command.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_delete_notification.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_read.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_read_device_info.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_read_state.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_read_write.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_write.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/ads_write_state.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/commands/errors.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/errors.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_enable_route.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_function.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_sum_read.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_sum_read_write.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_symbol_datatype_by_name.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_symbol_datatype_upload.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_symbol_info_by_name_ex.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_symbol_table_version.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_symbol_upload.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/functions/ads_symbol_upload_info.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/py.typed +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/stream.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/transport.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/aioads/utils/local_ip.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/docs/transmission_mode.md +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/docs/unittest_style_guide.html +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/examples/read_cmd_reuse_mqtt.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/examples/read_cycles.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/examples/read_cycles_mqtt.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/examples/read_multiple.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/examples/read_multiple_mqtt.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/examples/read_single.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/pdm.lock +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/__init__.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/builders.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/README.md +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/__init__.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/base.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/config.example.toml +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/config.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/test_connection.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/test_memory.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/test_performance.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/integration/test_read_symbols.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/__init__.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/__init__.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_add_notification.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_command.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_delete_notification.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_read.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_read_device_info.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_read_state.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_read_write.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_write.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_ads_write_state.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/commands/test_errors.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/__init__.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_enable_route.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_function.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_sum_read.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_sum_read_write.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_datatype_by_name.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_datatype_upload.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_info_by_name_ex.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_table_version.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_upload.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_upload_info.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ads_client.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ads_error_codes.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ads_notifications.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ads_symbol_cache.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ams_address.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ams_header.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_ams_tcp_header.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_errors.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_stream.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/test_transport.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/utils/__init__.py +0 -0
- {aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/utils/test_local_ip.py +0 -0
|
@@ -134,6 +134,37 @@ class AdsSymbolParser(ISymbolParser):
|
|
|
134
134
|
|
|
135
135
|
def __init__(self, data_types: list[SymbolDataTypeResponse]):
|
|
136
136
|
self._type_lookup = {dt.name: dt for dt in data_types}
|
|
137
|
+
# Resolution of IEC 61131-3 elementary type names to their wire encoding.
|
|
138
|
+
self._elementary_type_names: dict[str, AdsSymbolDataType] = {
|
|
139
|
+
"BOOL": AdsSymbolDataType.BIT,
|
|
140
|
+
"BIT": AdsSymbolDataType.BIT,
|
|
141
|
+
"SINT": AdsSymbolDataType.INT8,
|
|
142
|
+
"USINT": AdsSymbolDataType.UINT8,
|
|
143
|
+
"BYTE": AdsSymbolDataType.UINT8,
|
|
144
|
+
"INT": AdsSymbolDataType.INT16,
|
|
145
|
+
"UINT": AdsSymbolDataType.UINT16,
|
|
146
|
+
"WORD": AdsSymbolDataType.UINT16,
|
|
147
|
+
"DINT": AdsSymbolDataType.INT32,
|
|
148
|
+
"UDINT": AdsSymbolDataType.UINT32,
|
|
149
|
+
"DWORD": AdsSymbolDataType.UINT32,
|
|
150
|
+
"LINT": AdsSymbolDataType.INT64,
|
|
151
|
+
"ULINT": AdsSymbolDataType.UINT64,
|
|
152
|
+
"LWORD": AdsSymbolDataType.UINT64,
|
|
153
|
+
"REAL": AdsSymbolDataType.REAL32,
|
|
154
|
+
"LREAL": AdsSymbolDataType.REAL64,
|
|
155
|
+
"TIME": AdsSymbolDataType.UINT32,
|
|
156
|
+
"TOD": AdsSymbolDataType.UINT32,
|
|
157
|
+
"TIME_OF_DAY": AdsSymbolDataType.UINT32,
|
|
158
|
+
"DATE": AdsSymbolDataType.UINT32,
|
|
159
|
+
"DT": AdsSymbolDataType.UINT32,
|
|
160
|
+
"DATE_AND_TIME": AdsSymbolDataType.UINT32,
|
|
161
|
+
"LTIME": AdsSymbolDataType.UINT64,
|
|
162
|
+
"LTOD": AdsSymbolDataType.UINT64,
|
|
163
|
+
"LTIME_OF_DAY": AdsSymbolDataType.UINT64,
|
|
164
|
+
"LDATE": AdsSymbolDataType.UINT64,
|
|
165
|
+
"LDT": AdsSymbolDataType.UINT64,
|
|
166
|
+
"LDATE_AND_TIME": AdsSymbolDataType.UINT64,
|
|
167
|
+
}
|
|
137
168
|
self._const_type_names: dict[str, Callable[[str, AdsStream], Any]] = {
|
|
138
169
|
"Tc2_System.T_MaxString": lambda type_name, raw_data: raw_data.read(255)
|
|
139
170
|
.decode("latin-1")
|
|
@@ -142,6 +173,7 @@ class AdsSymbolParser(ISymbolParser):
|
|
|
142
173
|
.decode("latin-1")
|
|
143
174
|
.rstrip("\x00"),
|
|
144
175
|
}
|
|
176
|
+
|
|
145
177
|
self._primitive_parser = PrimitiveTypeParser()
|
|
146
178
|
|
|
147
179
|
def update_datatypes(self, data_types: list[SymbolDataTypeResponse]) -> None:
|
|
@@ -265,6 +297,13 @@ class AdsSymbolParser(ISymbolParser):
|
|
|
265
297
|
if data_type != AdsSymbolDataType.BIGTYPE:
|
|
266
298
|
return self._primitive_parser.parse(data_type, type_name, raw_data)
|
|
267
299
|
|
|
300
|
+
# TwinCAT 2 reports BIGTYPE for some elementary types
|
|
301
|
+
# like INT, UINT, REAL, etc. So we need to resolve them by name.
|
|
302
|
+
if type_name in self._elementary_type_names:
|
|
303
|
+
return self._primitive_parser.parse(
|
|
304
|
+
self._elementary_type_names[type_name], type_name, raw_data
|
|
305
|
+
)
|
|
306
|
+
|
|
268
307
|
# Parsing of structured types
|
|
269
308
|
if data_type == AdsSymbolDataType.BIGTYPE:
|
|
270
309
|
return self.parse_struct(type_name, raw_data)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "aioads"
|
|
3
|
-
version = "0.1.0.
|
|
3
|
+
version = "0.1.0.dev9"
|
|
4
4
|
description = "An asynchronous Python library for communicating with Beckhoff TwinCAT PLCs"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "MkKiefer", email = "102972583+MkKiefer@users.noreply.github.com" },
|
|
@@ -78,4 +78,5 @@ disable = [
|
|
|
78
78
|
"too-many-locals",
|
|
79
79
|
"too-few-public-methods",
|
|
80
80
|
"broad-exception-caught",
|
|
81
|
+
"too-many-return-statements",
|
|
81
82
|
]
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import struct
|
|
4
4
|
import unittest
|
|
5
5
|
|
|
6
|
+
from parameterized import parameterized
|
|
7
|
+
|
|
6
8
|
from aioads.ads_symbol_parser import AdsSymbolParser, PrimitiveTypeParser
|
|
7
9
|
from aioads.functions.ads_symbol_datatype_by_name import SymbolDataTypeResponse
|
|
8
10
|
from aioads.functions.ads_symbol_info_by_name_ex import (
|
|
@@ -89,7 +91,8 @@ class TestPrimitiveTypeParser(unittest.TestCase):
|
|
|
89
91
|
stream = make_stream(b"Hi\x00\x00\x00\x00")
|
|
90
92
|
|
|
91
93
|
# Act
|
|
92
|
-
result = self.parser.parse(
|
|
94
|
+
result = self.parser.parse(
|
|
95
|
+
AdsSymbolDataType.STRING, "STRING(5)", stream)
|
|
93
96
|
|
|
94
97
|
# Assert
|
|
95
98
|
self.assertEqual(result, "Hi")
|
|
@@ -110,7 +113,8 @@ class TestPrimitiveTypeParser(unittest.TestCase):
|
|
|
110
113
|
stream = make_stream(raw)
|
|
111
114
|
|
|
112
115
|
# Act
|
|
113
|
-
result = self.parser.parse(
|
|
116
|
+
result = self.parser.parse(
|
|
117
|
+
AdsSymbolDataType.WSTRING, "WSTRING(3)", stream)
|
|
114
118
|
|
|
115
119
|
# Assert
|
|
116
120
|
self.assertEqual(result, "Hi")
|
|
@@ -136,7 +140,8 @@ class TestAdsSymbolParser(unittest.TestCase):
|
|
|
136
140
|
stream = make_stream(bytes(range(8)))
|
|
137
141
|
|
|
138
142
|
# Act
|
|
139
|
-
result = self.parser.parse(
|
|
143
|
+
result = self.parser.parse(
|
|
144
|
+
AdsSymbolDataType.BIGTYPE, "REFERENCE TO ST_Foo", stream)
|
|
140
145
|
|
|
141
146
|
# Assert
|
|
142
147
|
self.assertEqual(result, "0001020304050607^")
|
|
@@ -146,7 +151,8 @@ class TestAdsSymbolParser(unittest.TestCase):
|
|
|
146
151
|
stream = make_stream(bytes(range(8)))
|
|
147
152
|
|
|
148
153
|
# Act
|
|
149
|
-
result = self.parser.parse(
|
|
154
|
+
result = self.parser.parse(
|
|
155
|
+
AdsSymbolDataType.BIGTYPE, "POINTER TO ST_Foo", stream)
|
|
150
156
|
|
|
151
157
|
# Assert
|
|
152
158
|
self.assertEqual(result, "0001020304050607*")
|
|
@@ -181,7 +187,8 @@ class TestAdsSymbolParser(unittest.TestCase):
|
|
|
181
187
|
|
|
182
188
|
# Act / Assert
|
|
183
189
|
with self.assertRaises(ValueError) as ctx:
|
|
184
|
-
self.parser.parse(AdsSymbolDataType.INT16,
|
|
190
|
+
self.parser.parse(AdsSymbolDataType.INT16,
|
|
191
|
+
"ARRAY OF nonsense", stream)
|
|
185
192
|
|
|
186
193
|
self.assertIn("Cannot parse array type", str(ctx.exception))
|
|
187
194
|
|
|
@@ -200,14 +207,18 @@ class TestAdsSymbolParser(unittest.TestCase):
|
|
|
200
207
|
|
|
201
208
|
def test_parse_struct_with_sub_items_returns_dict(self) -> None:
|
|
202
209
|
# Arrange
|
|
203
|
-
sub_a = make_datatype(
|
|
204
|
-
|
|
205
|
-
|
|
210
|
+
sub_a = make_datatype(
|
|
211
|
+
"a", size=2, data_type=AdsSymbolDataType.INT16, type_name="INT16", offs=0)
|
|
212
|
+
sub_b = make_datatype(
|
|
213
|
+
"b", size=2, data_type=AdsSymbolDataType.INT16, type_name="INT16", offs=2)
|
|
214
|
+
struct_type = make_datatype(
|
|
215
|
+
"ST_Pair", size=4, sub_items=[sub_a, sub_b])
|
|
206
216
|
self.parser.update_datatypes([struct_type])
|
|
207
217
|
stream = make_stream(struct.pack("<hh", 10, 20))
|
|
208
218
|
|
|
209
219
|
# Act
|
|
210
|
-
result = self.parser.parse(
|
|
220
|
+
result = self.parser.parse(
|
|
221
|
+
AdsSymbolDataType.BIGTYPE, "ST_Pair", stream)
|
|
211
222
|
|
|
212
223
|
# Assert
|
|
213
224
|
self.assertEqual(result, {"a": 10, "b": 20})
|
|
@@ -222,18 +233,151 @@ class TestAdsSymbolParser(unittest.TestCase):
|
|
|
222
233
|
|
|
223
234
|
self.assertIn("unknown type name", str(ctx.exception))
|
|
224
235
|
|
|
236
|
+
@parameterized.expand([
|
|
237
|
+
("BOOL", b"\x01", True),
|
|
238
|
+
("INT", struct.pack("<h", -7), -7),
|
|
239
|
+
("WORD", struct.pack("<H", 512), 512),
|
|
240
|
+
("DINT", struct.pack("<i", -100_000), -100_000),
|
|
241
|
+
("LWORD", struct.pack("<Q", 2**40), 2**40),
|
|
242
|
+
("LREAL", struct.pack("<d", 2.5), 2.5),
|
|
243
|
+
])
|
|
244
|
+
def test_parse_bigtype_with_elementary_name_resolves_by_name(
|
|
245
|
+
self, type_name: str, raw: bytes, expected: object
|
|
246
|
+
) -> None:
|
|
247
|
+
"""TwinCAT 2 reports BIGTYPE even for elementary types like INT."""
|
|
248
|
+
# Arrange
|
|
249
|
+
stream = make_stream(raw)
|
|
250
|
+
|
|
251
|
+
# Act
|
|
252
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, type_name, stream)
|
|
253
|
+
|
|
254
|
+
# Assert
|
|
255
|
+
self.assertEqual(result, expected)
|
|
256
|
+
|
|
257
|
+
def test_parse_time_returns_milliseconds(self) -> None:
|
|
258
|
+
# Arrange
|
|
259
|
+
stream = make_stream(struct.pack("<I", 90_500))
|
|
260
|
+
|
|
261
|
+
# Act
|
|
262
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, "TIME", stream)
|
|
263
|
+
|
|
264
|
+
# Assert
|
|
265
|
+
self.assertEqual(result, 90_500)
|
|
266
|
+
|
|
267
|
+
def test_parse_ltime_returns_nanoseconds(self) -> None:
|
|
268
|
+
# Arrange
|
|
269
|
+
stream = make_stream(struct.pack("<Q", 1_500_000_000))
|
|
270
|
+
|
|
271
|
+
# Act
|
|
272
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, "LTIME", stream)
|
|
273
|
+
|
|
274
|
+
# Assert
|
|
275
|
+
self.assertEqual(result, 1_500_000_000)
|
|
276
|
+
|
|
277
|
+
def test_parse_time_of_day_returns_milliseconds_since_midnight(self) -> None:
|
|
278
|
+
# Arrange
|
|
279
|
+
milliseconds_since_midnight = (13 * 3600 + 37 * 60 + 5) * 1000 + 250
|
|
280
|
+
stream = make_stream(struct.pack("<I", milliseconds_since_midnight))
|
|
281
|
+
|
|
282
|
+
# Act
|
|
283
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, "TOD", stream)
|
|
284
|
+
|
|
285
|
+
# Assert
|
|
286
|
+
self.assertEqual(result, milliseconds_since_midnight)
|
|
287
|
+
|
|
288
|
+
def test_parse_date_returns_epoch_seconds(self) -> None:
|
|
289
|
+
# Arrange
|
|
290
|
+
stream = make_stream(struct.pack("<I", 1_752_364_800)) # 2025-07-13
|
|
291
|
+
|
|
292
|
+
# Act
|
|
293
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, "DATE", stream)
|
|
294
|
+
|
|
295
|
+
# Assert
|
|
296
|
+
self.assertEqual(result, 1_752_364_800)
|
|
297
|
+
|
|
298
|
+
def test_parse_date_and_time_returns_epoch_seconds(self) -> None:
|
|
299
|
+
# Arrange
|
|
300
|
+
# 2025-07-13T11:45:06Z
|
|
301
|
+
stream = make_stream(struct.pack("<I", 1_752_407_106))
|
|
302
|
+
|
|
303
|
+
# Act
|
|
304
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, "DT", stream)
|
|
305
|
+
|
|
306
|
+
# Assert
|
|
307
|
+
self.assertEqual(result, 1_752_407_106)
|
|
308
|
+
|
|
309
|
+
@parameterized.expand([
|
|
310
|
+
("LTIME",),
|
|
311
|
+
("LTOD",),
|
|
312
|
+
("LTIME_OF_DAY",),
|
|
313
|
+
("LDATE",),
|
|
314
|
+
("LDT",),
|
|
315
|
+
("LDATE_AND_TIME",),
|
|
316
|
+
])
|
|
317
|
+
def test_parse_long_datetime_types_return_nanoseconds(self, type_name: str) -> None:
|
|
318
|
+
# Arrange
|
|
319
|
+
stream = make_stream(struct.pack("<Q", 1_752_407_106_000_000_000))
|
|
320
|
+
|
|
321
|
+
# Act
|
|
322
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, type_name, stream)
|
|
323
|
+
|
|
324
|
+
# Assert
|
|
325
|
+
self.assertEqual(result, 1_752_407_106_000_000_000)
|
|
326
|
+
|
|
327
|
+
@parameterized.expand([
|
|
328
|
+
("TIME_OF_DAY",),
|
|
329
|
+
("DATE_AND_TIME",),
|
|
330
|
+
])
|
|
331
|
+
def test_parse_long_form_datetime_aliases_return_integer(self, type_name: str) -> None:
|
|
332
|
+
# Arrange
|
|
333
|
+
stream = make_stream(struct.pack("<I", 42_000))
|
|
334
|
+
|
|
335
|
+
# Act
|
|
336
|
+
result = self.parser.parse(AdsSymbolDataType.BIGTYPE, type_name, stream)
|
|
337
|
+
|
|
338
|
+
# Assert
|
|
339
|
+
self.assertEqual(result, 42_000)
|
|
340
|
+
|
|
341
|
+
def test_parse_array_of_time_returns_millisecond_list(self) -> None:
|
|
342
|
+
# Arrange
|
|
343
|
+
stream = make_stream(struct.pack("<II", 1000, 2000))
|
|
344
|
+
|
|
345
|
+
# Act
|
|
346
|
+
result = self.parser.parse(
|
|
347
|
+
AdsSymbolDataType.BIGTYPE, "ARRAY [0..1] OF TIME", stream
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Assert
|
|
351
|
+
self.assertEqual(result, [1000, 2000])
|
|
352
|
+
|
|
353
|
+
def test_parse_struct_with_time_sub_item_returns_millisecond_field(self) -> None:
|
|
354
|
+
# Arrange
|
|
355
|
+
sub_time = make_datatype("duration", size=4, type_name="TIME", offs=0)
|
|
356
|
+
struct_type = make_datatype("ST_Timed", size=4, sub_items=[sub_time])
|
|
357
|
+
self.parser.update_datatypes([struct_type])
|
|
358
|
+
stream = make_stream(struct.pack("<I", 250))
|
|
359
|
+
|
|
360
|
+
# Act
|
|
361
|
+
result = self.parser.parse(
|
|
362
|
+
AdsSymbolDataType.BIGTYPE, "ST_Timed", stream)
|
|
363
|
+
|
|
364
|
+
# Assert
|
|
365
|
+
self.assertEqual(result, {"duration": 250})
|
|
366
|
+
|
|
225
367
|
def test_update_datatypes_registers_new_type_for_lookup(self) -> None:
|
|
226
368
|
# Arrange
|
|
227
369
|
struct_type = make_datatype(
|
|
228
370
|
"ST_Single",
|
|
229
371
|
size=2,
|
|
230
|
-
sub_items=[make_datatype(
|
|
372
|
+
sub_items=[make_datatype(
|
|
373
|
+
"v", size=2, data_type=AdsSymbolDataType.INT16, type_name="INT16")],
|
|
231
374
|
)
|
|
232
375
|
|
|
233
376
|
# Act
|
|
234
377
|
self.parser.update_datatypes([struct_type])
|
|
235
378
|
result = self.parser.parse(
|
|
236
|
-
AdsSymbolDataType.BIGTYPE, "ST_Single", make_stream(
|
|
379
|
+
AdsSymbolDataType.BIGTYPE, "ST_Single", make_stream(
|
|
380
|
+
struct.pack("<h", 42))
|
|
237
381
|
)
|
|
238
382
|
|
|
239
383
|
# Assert
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_datatype_by_name.py
RENAMED
|
File without changes
|
{aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_datatype_upload.py
RENAMED
|
File without changes
|
{aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_info_by_name_ex.py
RENAMED
|
File without changes
|
{aioads-0.1.0.dev8 → aioads-0.1.0.dev9}/tests/unit/functions/test_ads_symbol_table_version.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|