pystuderxcom 3.4.1__tar.gz → 3.4.2__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.
- {pystuderxcom-3.4.1/src/pystuderxcom.egg-info → pystuderxcom-3.4.2}/PKG-INFO +1 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/pyproject.toml +1 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/__init__.py +1 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/api_base_async.py +74 -22
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/api_base_sync.py +75 -23
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/const.py +8 -2
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/datapoints.py +13 -3
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/datapoints_xcom.json +3 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/discover_async.py +5 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/discover_sync.py +5 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/families.py +2 -2
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2/src/pystuderxcom.egg-info}/PKG-INFO +1 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_base_async.py +112 -18
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_base_sync.py +112 -18
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_datapoints_async.py +8 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_datapoints_sync.py +8 -1
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/LICENSE +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/README.md +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/setup.cfg +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/api_serial.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/api_tcp.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/api_udp.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/data.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/datapoints_120v.json +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/datapoints_240v.json +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/factory_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/factory_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/messages.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/messages_en.json +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/protocol.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom/values.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom.egg-info/SOURCES.txt +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom.egg-info/dependency_links.txt +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom.egg-info/requires.txt +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/src/pystuderxcom.egg-info/top_level.txt +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_serial_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_serial_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_tcp_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_tcp_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_udp_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_api_udp_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_const.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_data.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_discover_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_discover_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_families.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_messageset_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_messageset_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_package_async.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_package_sync.py +0 -0
- {pystuderxcom-3.4.1 → pystuderxcom-3.4.2}/tests/test_values.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystuderxcom
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Summary: Python library to communicate with Studer-Innotec devices on the local network via Xcom protocol
|
|
5
5
|
Author-email: Anko Hanse <anko_hanse@hotmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ankohanse/pystuderxcom
|
|
@@ -10,7 +10,7 @@ from .api_base_sync import XcomApiBase
|
|
|
10
10
|
from .discover_sync import XcomDiscover
|
|
11
11
|
from .factory_sync import XcomFactory
|
|
12
12
|
|
|
13
|
-
from .const import XcomApiTcpMode, XcomVoltage, XcomLevel, XcomFormat, XcomCategory, XcomAggregationType
|
|
13
|
+
from .const import XcomApiTcpMode, XcomVoltage, XcomLevel, XcomFormat, XcomTarget, XcomCategory, XcomAggregationType
|
|
14
14
|
from .const import XcomApiWriteException, XcomApiReadException, XcomApiTimeoutException, XcomApiUnpackException, XcomApiResponseIsError, XcomDiscoverNotConnected, XcomParamException
|
|
15
15
|
from .data import XcomDiscoveredClient, XcomDiscoveredDevice
|
|
16
16
|
from .datapoints import XcomDataset, XcomDatapoint, XcomDatapointUnknownException
|
|
@@ -14,6 +14,7 @@ from .const import (
|
|
|
14
14
|
REQ_BURST_PERIOD,
|
|
15
15
|
ScomAddress,
|
|
16
16
|
XcomFormat,
|
|
17
|
+
XcomTarget,
|
|
17
18
|
XcomCategory,
|
|
18
19
|
XcomAggregationType,
|
|
19
20
|
ScomFrameFlag,
|
|
@@ -196,14 +197,14 @@ class AsyncXcomApiBase:
|
|
|
196
197
|
XcomApiResponseIsError
|
|
197
198
|
"""
|
|
198
199
|
|
|
200
|
+
# Sanity check
|
|
201
|
+
if parameter.target != XcomTarget.STANDARD:
|
|
202
|
+
raise XcomParamException(f"Invalid datapoint passed to request_value; must have target STANDARD. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
203
|
+
|
|
199
204
|
# Check/convert input parameters
|
|
200
205
|
if type(dstAddr) is str:
|
|
201
206
|
dstAddr = XcomDeviceFamilies.get_addr_by_code(dstAddr)
|
|
202
207
|
|
|
203
|
-
# Check for virtual value request
|
|
204
|
-
if parameter.category in [XcomCategory.VIRTUAL]:
|
|
205
|
-
return await self.request_virtual(parameter, dstAddr=dstAddr, retries=retries, timeout=timeout, verbose=verbose)
|
|
206
|
-
|
|
207
208
|
# Compose the request and send it
|
|
208
209
|
request: XcomPackage = XcomPackage.gen_package(
|
|
209
210
|
service_id = ScomServiceId.READ,
|
|
@@ -235,9 +236,12 @@ class AsyncXcomApiBase:
|
|
|
235
236
|
XcomApiTimeoutException
|
|
236
237
|
XcomApiResponseIsError
|
|
237
238
|
"""
|
|
238
|
-
|
|
239
|
+
# Sanity check
|
|
239
240
|
if not self.connected:
|
|
240
241
|
return None
|
|
242
|
+
|
|
243
|
+
if parameter.target != XcomTarget.VIRTUAL:
|
|
244
|
+
raise XcomParamException(f"Invalid datapoint passed to request_value; must have target VIRTUAL. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
241
245
|
|
|
242
246
|
# Virtual request
|
|
243
247
|
match parameter.nr:
|
|
@@ -255,6 +259,9 @@ class AsyncXcomApiBase:
|
|
|
255
259
|
case 99021: return datetime.fromtimestamp(msg.timestamp)
|
|
256
260
|
case 99022: return msg.source_address
|
|
257
261
|
|
|
262
|
+
case _:
|
|
263
|
+
raise XcomParamException(f"Invalid datapoint passed to request_virtual; nr not recognized. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
264
|
+
|
|
258
265
|
return None
|
|
259
266
|
|
|
260
267
|
|
|
@@ -276,6 +283,9 @@ class AsyncXcomApiBase:
|
|
|
276
283
|
|
|
277
284
|
# Sanity check
|
|
278
285
|
for item in request_data.items:
|
|
286
|
+
if item.datapoint.target != XcomTarget.STANDARD:
|
|
287
|
+
raise XcomParamException(f"Invalid datapoint passed to request_infos; must have target STANDARD. Violated by datapoint '{item.datapoint.name}' ({item.datapoint.nr})")
|
|
288
|
+
|
|
279
289
|
if item.datapoint.category != XcomCategory.INFO:
|
|
280
290
|
raise XcomParamException(f"Invalid datapoint passed to request_infos; must have type INFO. Violated by datapoint '{item.datapoint.name}' ({item.datapoint.nr})")
|
|
281
291
|
|
|
@@ -305,12 +315,12 @@ class AsyncXcomApiBase:
|
|
|
305
315
|
|
|
306
316
|
async def request_values(self, request_data: XcomValues, retries = None, timeout = None, verbose=False) -> XcomValues:
|
|
307
317
|
"""
|
|
308
|
-
Request multiple infos
|
|
318
|
+
Request multiple infos, params or virtuals in one call.
|
|
309
319
|
Can only retrieve actual device values, NOT the average or sum over multiple devices.
|
|
310
320
|
|
|
311
321
|
The function will try to be as efficient as possible and combine retrieval of multiple infos in one call.
|
|
312
322
|
When the xcom-client does not support multiple-infos in one call, they are retried one by one.
|
|
313
|
-
Requested params are always retrieved one by one, so the function can take a while to finish.
|
|
323
|
+
Requested params and virtuals are always retrieved one by one, so the function can take a while to finish.
|
|
314
324
|
|
|
315
325
|
Returns None if not connected, otherwise returns the list of requested values
|
|
316
326
|
Throws
|
|
@@ -321,6 +331,7 @@ class AsyncXcomApiBase:
|
|
|
321
331
|
"""
|
|
322
332
|
|
|
323
333
|
# Sort out which XcomValues can be done via multi request_values and which must be done via single request_value
|
|
334
|
+
req_virtuals: list[XcomValuesItem] = []
|
|
324
335
|
req_singles: list[XcomValuesItem] = []
|
|
325
336
|
req_multi_items: list[XcomValuesItem] = []
|
|
326
337
|
req_multis: list[XcomValues] = []
|
|
@@ -328,26 +339,37 @@ class AsyncXcomApiBase:
|
|
|
328
339
|
|
|
329
340
|
for idx,item in enumerate(request_data.items):
|
|
330
341
|
|
|
331
|
-
match item.datapoint.
|
|
332
|
-
case
|
|
333
|
-
if item.
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
elif item.address is not None:
|
|
338
|
-
# Any others need to be done via an individual request_value cal
|
|
339
|
-
req_singles.append(item)
|
|
342
|
+
match item.datapoint.target:
|
|
343
|
+
case XcomTarget.VIRTUAL:
|
|
344
|
+
if item.address is not None:
|
|
345
|
+
# Needs to be done via an individual request_virtual call
|
|
346
|
+
req_virtuals.append(item)
|
|
340
347
|
|
|
341
348
|
else:
|
|
342
349
|
raise XcomParamException(f"Invalid XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
343
350
|
|
|
344
|
-
case
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
351
|
+
case XcomTarget.STANDARD:
|
|
352
|
+
# Standard datapoints are handled depending on category and aggregation type
|
|
353
|
+
match item.datapoint.category:
|
|
354
|
+
case XcomCategory.INFO:
|
|
355
|
+
if item.aggregation_type is not None and item.aggregation_type in range(XcomAggregationType.DEVICE1, XcomAggregationType.DEVICE15+1):
|
|
356
|
+
# Can be combined with other infos in a request_values call
|
|
357
|
+
req_multi_items.append(item)
|
|
348
358
|
|
|
349
|
-
|
|
350
|
-
|
|
359
|
+
elif item.address is not None:
|
|
360
|
+
# Any others need to be done via an individual request_value cal
|
|
361
|
+
req_singles.append(item)
|
|
362
|
+
|
|
363
|
+
else:
|
|
364
|
+
raise XcomParamException(f"Invalid XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
365
|
+
|
|
366
|
+
case XcomCategory.PARAMETER:
|
|
367
|
+
if item.address is not None:
|
|
368
|
+
# Needs to be done via an individual request_value call
|
|
369
|
+
req_singles.append(item)
|
|
370
|
+
|
|
371
|
+
else:
|
|
372
|
+
raise XcomParamException(f"Invalid XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
351
373
|
|
|
352
374
|
if (len(req_multi_items) == MULTI_INFO_REQ_MAX) or \
|
|
353
375
|
(len(req_multi_items) > 0 and idx == idx_last):
|
|
@@ -417,6 +439,36 @@ class AsyncXcomApiBase:
|
|
|
417
439
|
await asyncio.sleep(1)
|
|
418
440
|
burst_start = datetime.now()
|
|
419
441
|
|
|
442
|
+
# Finally perform all the virtual request_value requests
|
|
443
|
+
for req_virtual in req_virtuals:
|
|
444
|
+
try:
|
|
445
|
+
error = None
|
|
446
|
+
value = await self.request_virtual(req_virtual.datapoint, req_virtual.address, retries=retries, timeout=timeout, verbose=verbose)
|
|
447
|
+
|
|
448
|
+
except Exception as ex:
|
|
449
|
+
value = None
|
|
450
|
+
error = str(ex)
|
|
451
|
+
|
|
452
|
+
if error is not None:
|
|
453
|
+
_LOGGER.debug(f"Failed to retrieve virtual info or param {req_virtual.datapoint.nr}:{req_virtual.address}; {error}")
|
|
454
|
+
|
|
455
|
+
# Add to results
|
|
456
|
+
rsp_virtual = XcomValuesItem(
|
|
457
|
+
datapoint = req_virtual.datapoint,
|
|
458
|
+
code = req_virtual.code,
|
|
459
|
+
address = req_virtual.address,
|
|
460
|
+
aggregation_type=req_virtual.aggregation_type,
|
|
461
|
+
value = value,
|
|
462
|
+
error = error,
|
|
463
|
+
)
|
|
464
|
+
result_items.append(rsp_virtual)
|
|
465
|
+
|
|
466
|
+
# Periodically wait for a second. This will make sure we do not block Xcom-LAN with
|
|
467
|
+
# too many requests at once and prevent it from uploading data to the Studer portal.
|
|
468
|
+
if (datetime.now() - burst_start).total_seconds() > REQ_BURST_PERIOD:
|
|
469
|
+
await asyncio.sleep(1)
|
|
470
|
+
burst_start = datetime.now()
|
|
471
|
+
|
|
420
472
|
# Return all reponse items as one XcomValues object
|
|
421
473
|
return XcomValues(result_items)
|
|
422
474
|
|
|
@@ -16,6 +16,7 @@ from .const import (
|
|
|
16
16
|
REQ_BURST_PERIOD,
|
|
17
17
|
ScomAddress,
|
|
18
18
|
XcomFormat,
|
|
19
|
+
XcomTarget,
|
|
19
20
|
XcomCategory,
|
|
20
21
|
XcomAggregationType,
|
|
21
22
|
ScomFrameFlag,
|
|
@@ -58,8 +59,8 @@ from .values import (
|
|
|
58
59
|
XcomValues,
|
|
59
60
|
XcomValuesItem,
|
|
60
61
|
)
|
|
61
|
-
import time
|
|
62
62
|
import threading
|
|
63
|
+
import time
|
|
63
64
|
|
|
64
65
|
|
|
65
66
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -200,14 +201,14 @@ class XcomApiBase:
|
|
|
200
201
|
XcomApiResponseIsError
|
|
201
202
|
"""
|
|
202
203
|
|
|
204
|
+
# Sanity check
|
|
205
|
+
if parameter.target != XcomTarget.STANDARD:
|
|
206
|
+
raise XcomParamException(f"Invalid datapoint passed to request_value; must have target STANDARD. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
207
|
+
|
|
203
208
|
# Check/convert input parameters
|
|
204
209
|
if type(dstAddr) is str:
|
|
205
210
|
dstAddr = XcomDeviceFamilies.get_addr_by_code(dstAddr)
|
|
206
211
|
|
|
207
|
-
# Check for virtual value request
|
|
208
|
-
if parameter.category in [XcomCategory.VIRTUAL]:
|
|
209
|
-
return self.request_virtual(parameter, dstAddr=dstAddr, retries=retries, timeout=timeout, verbose=verbose)
|
|
210
|
-
|
|
211
212
|
# Compose the request and send it
|
|
212
213
|
request: XcomPackage = XcomPackage.gen_package(
|
|
213
214
|
service_id = ScomServiceId.READ,
|
|
@@ -239,9 +240,12 @@ class XcomApiBase:
|
|
|
239
240
|
XcomApiTimeoutException
|
|
240
241
|
XcomApiResponseIsError
|
|
241
242
|
"""
|
|
242
|
-
|
|
243
|
+
# Sanity check
|
|
243
244
|
if not self.connected:
|
|
244
245
|
return None
|
|
246
|
+
|
|
247
|
+
if parameter.target != XcomTarget.VIRTUAL:
|
|
248
|
+
raise XcomParamException(f"Invalid datapoint passed to request_value; must have target VIRTUAL. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
245
249
|
|
|
246
250
|
# Virtual request
|
|
247
251
|
match parameter.nr:
|
|
@@ -259,6 +263,9 @@ class XcomApiBase:
|
|
|
259
263
|
case 99021: return datetime.fromtimestamp(msg.timestamp)
|
|
260
264
|
case 99022: return msg.source_address
|
|
261
265
|
|
|
266
|
+
case _:
|
|
267
|
+
raise XcomParamException(f"Invalid datapoint passed to request_virtual; nr not recognized. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
268
|
+
|
|
262
269
|
return None
|
|
263
270
|
|
|
264
271
|
|
|
@@ -280,6 +287,9 @@ class XcomApiBase:
|
|
|
280
287
|
|
|
281
288
|
# Sanity check
|
|
282
289
|
for item in request_data.items:
|
|
290
|
+
if item.datapoint.target != XcomTarget.STANDARD:
|
|
291
|
+
raise XcomParamException(f"Invalid datapoint passed to request_infos; must have target STANDARD. Violated by datapoint '{item.datapoint.name}' ({item.datapoint.nr})")
|
|
292
|
+
|
|
283
293
|
if item.datapoint.category != XcomCategory.INFO:
|
|
284
294
|
raise XcomParamException(f"Invalid datapoint passed to request_infos; must have type INFO. Violated by datapoint '{item.datapoint.name}' ({item.datapoint.nr})")
|
|
285
295
|
|
|
@@ -309,12 +319,12 @@ class XcomApiBase:
|
|
|
309
319
|
|
|
310
320
|
def request_values(self, request_data: XcomValues, retries = None, timeout = None, verbose=False) -> XcomValues:
|
|
311
321
|
"""
|
|
312
|
-
Request multiple infos
|
|
322
|
+
Request multiple infos, params or virtuals in one call.
|
|
313
323
|
Can only retrieve actual device values, NOT the average or sum over multiple devices.
|
|
314
324
|
|
|
315
325
|
The function will try to be as efficient as possible and combine retrieval of multiple infos in one call.
|
|
316
326
|
When the xcom-client does not support multiple-infos in one call, they are retried one by one.
|
|
317
|
-
Requested params are always retrieved one by one, so the function can take a while to finish.
|
|
327
|
+
Requested params and virtuals are always retrieved one by one, so the function can take a while to finish.
|
|
318
328
|
|
|
319
329
|
Returns None if not connected, otherwise returns the list of requested values
|
|
320
330
|
Throws
|
|
@@ -325,6 +335,7 @@ class XcomApiBase:
|
|
|
325
335
|
"""
|
|
326
336
|
|
|
327
337
|
# Sort out which XcomValues can be done via multi request_values and which must be done via single request_value
|
|
338
|
+
req_virtuals: list[XcomValuesItem] = []
|
|
328
339
|
req_singles: list[XcomValuesItem] = []
|
|
329
340
|
req_multi_items: list[XcomValuesItem] = []
|
|
330
341
|
req_multis: list[XcomValues] = []
|
|
@@ -332,26 +343,37 @@ class XcomApiBase:
|
|
|
332
343
|
|
|
333
344
|
for idx,item in enumerate(request_data.items):
|
|
334
345
|
|
|
335
|
-
match item.datapoint.
|
|
336
|
-
case
|
|
337
|
-
if item.
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
elif item.address is not None:
|
|
342
|
-
# Any others need to be done via an individual request_value cal
|
|
343
|
-
req_singles.append(item)
|
|
346
|
+
match item.datapoint.target:
|
|
347
|
+
case XcomTarget.VIRTUAL:
|
|
348
|
+
if item.address is not None:
|
|
349
|
+
# Needs to be done via an individual request_virtual call
|
|
350
|
+
req_virtuals.append(item)
|
|
344
351
|
|
|
345
352
|
else:
|
|
346
353
|
raise XcomParamException(f"Invalid XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
347
354
|
|
|
348
|
-
case
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
355
|
+
case XcomTarget.STANDARD:
|
|
356
|
+
# Standard datapoints are handled depending on category and aggregation type
|
|
357
|
+
match item.datapoint.category:
|
|
358
|
+
case XcomCategory.INFO:
|
|
359
|
+
if item.aggregation_type is not None and item.aggregation_type in range(XcomAggregationType.DEVICE1, XcomAggregationType.DEVICE15+1):
|
|
360
|
+
# Can be combined with other infos in a request_values call
|
|
361
|
+
req_multi_items.append(item)
|
|
352
362
|
|
|
353
|
-
|
|
354
|
-
|
|
363
|
+
elif item.address is not None:
|
|
364
|
+
# Any others need to be done via an individual request_value cal
|
|
365
|
+
req_singles.append(item)
|
|
366
|
+
|
|
367
|
+
else:
|
|
368
|
+
raise XcomParamException(f"Invalid XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
369
|
+
|
|
370
|
+
case XcomCategory.PARAMETER:
|
|
371
|
+
if item.address is not None:
|
|
372
|
+
# Needs to be done via an individual request_value call
|
|
373
|
+
req_singles.append(item)
|
|
374
|
+
|
|
375
|
+
else:
|
|
376
|
+
raise XcomParamException(f"Invalid XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
355
377
|
|
|
356
378
|
if (len(req_multi_items) == MULTI_INFO_REQ_MAX) or \
|
|
357
379
|
(len(req_multi_items) > 0 and idx == idx_last):
|
|
@@ -421,6 +443,36 @@ class XcomApiBase:
|
|
|
421
443
|
time.sleep(1)
|
|
422
444
|
burst_start = datetime.now()
|
|
423
445
|
|
|
446
|
+
# Finally perform all the virtual request_value requests
|
|
447
|
+
for req_virtual in req_virtuals:
|
|
448
|
+
try:
|
|
449
|
+
error = None
|
|
450
|
+
value = self.request_virtual(req_virtual.datapoint, req_virtual.address, retries=retries, timeout=timeout, verbose=verbose)
|
|
451
|
+
|
|
452
|
+
except Exception as ex:
|
|
453
|
+
value = None
|
|
454
|
+
error = str(ex)
|
|
455
|
+
|
|
456
|
+
if error is not None:
|
|
457
|
+
_LOGGER.debug(f"Failed to retrieve virtual info or param {req_virtual.datapoint.nr}:{req_virtual.address}; {error}")
|
|
458
|
+
|
|
459
|
+
# Add to results
|
|
460
|
+
rsp_virtual = XcomValuesItem(
|
|
461
|
+
datapoint = req_virtual.datapoint,
|
|
462
|
+
code = req_virtual.code,
|
|
463
|
+
address = req_virtual.address,
|
|
464
|
+
aggregation_type=req_virtual.aggregation_type,
|
|
465
|
+
value = value,
|
|
466
|
+
error = error,
|
|
467
|
+
)
|
|
468
|
+
result_items.append(rsp_virtual)
|
|
469
|
+
|
|
470
|
+
# Periodically wait for a second. This will make sure we do not block Xcom-LAN with
|
|
471
|
+
# too many requests at once and prevent it from uploading data to the Studer portal.
|
|
472
|
+
if (datetime.now() - burst_start).total_seconds() > REQ_BURST_PERIOD:
|
|
473
|
+
time.sleep(1)
|
|
474
|
+
burst_start = datetime.now()
|
|
475
|
+
|
|
424
476
|
# Return all reponse items as one XcomValues object
|
|
425
477
|
return XcomValues(result_items)
|
|
426
478
|
|
|
@@ -37,6 +37,9 @@ REQ_TIMEOUT = 3
|
|
|
37
37
|
REQ_RETRIES = 3
|
|
38
38
|
REQ_BURST_PERIOD = 5 # do burst of requests for 5 seconds, then wait a second, then the next burst
|
|
39
39
|
|
|
40
|
+
NR_VIRTUAL_START = 98000
|
|
41
|
+
NR_VIRTUAL_END = 99999
|
|
42
|
+
|
|
40
43
|
|
|
41
44
|
class XcomApiTcpMode(StrEnum):
|
|
42
45
|
SERVER = "server"
|
|
@@ -187,9 +190,13 @@ class XcomMultiInfoFlags(IntEnum):
|
|
|
187
190
|
VT_PRESENT = 0x00000080,
|
|
188
191
|
VS_PRESENT = 0x00000100,
|
|
189
192
|
|
|
193
|
+
### values for target
|
|
194
|
+
class XcomTarget(StrEnum):
|
|
195
|
+
STANDARD = "standard"
|
|
196
|
+
VIRTUAL = "virtual"
|
|
197
|
+
|
|
190
198
|
### values for category
|
|
191
199
|
class XcomCategory:
|
|
192
|
-
VIRTUAL = 0x0000
|
|
193
200
|
INFO = 0x0001
|
|
194
201
|
PARAMETER = 0x0002
|
|
195
202
|
|
|
@@ -208,7 +215,6 @@ class ScomObjType:
|
|
|
208
215
|
def __repr__(self):
|
|
209
216
|
return self.name
|
|
210
217
|
|
|
211
|
-
@staticmethod
|
|
212
218
|
@staticmethod
|
|
213
219
|
def from_str(s: str, default: int|None = None):
|
|
214
220
|
for e in XcomAggregationType:
|
|
@@ -9,9 +9,15 @@ import logging
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
|
|
11
11
|
from .const import (
|
|
12
|
+
NR_VIRTUAL_END,
|
|
13
|
+
NR_VIRTUAL_START,
|
|
12
14
|
XcomLevel,
|
|
13
15
|
XcomFormat,
|
|
14
16
|
XcomCategory,
|
|
17
|
+
XcomTarget,
|
|
18
|
+
)
|
|
19
|
+
from .families import (
|
|
20
|
+
XcomDeviceFamilies,
|
|
15
21
|
)
|
|
16
22
|
|
|
17
23
|
|
|
@@ -82,9 +88,6 @@ class XcomDatapoint:
|
|
|
82
88
|
|
|
83
89
|
@property
|
|
84
90
|
def category(self) -> XcomCategory:
|
|
85
|
-
if self.nr >= 99000:
|
|
86
|
-
return XcomCategory.VIRTUAL
|
|
87
|
-
|
|
88
91
|
if self.level in [XcomLevel.INFO]:
|
|
89
92
|
return XcomCategory.INFO
|
|
90
93
|
|
|
@@ -94,6 +97,13 @@ class XcomDatapoint:
|
|
|
94
97
|
_LOGGER.debug(f"Unknown category for datapoint {self.nr} with level {self.level} and format {self.format}")
|
|
95
98
|
return XcomCategory.INFO
|
|
96
99
|
|
|
100
|
+
@property
|
|
101
|
+
def target(self) -> XcomTarget:
|
|
102
|
+
if NR_VIRTUAL_START <= self.nr <= NR_VIRTUAL_END:
|
|
103
|
+
return XcomTarget.VIRTUAL
|
|
104
|
+
else:
|
|
105
|
+
return XcomTarget.STANDARD
|
|
106
|
+
|
|
97
107
|
def enum_value(self, key):
|
|
98
108
|
if self.format not in [XcomFormat.LONG_ENUM, XcomFormat.SHORT_ENUM]:
|
|
99
109
|
return None
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
[
|
|
2
2
|
{ "_rem": "Extra 'virtual' Xcom datapoints"},
|
|
3
3
|
|
|
4
|
+
{ "_rem": "These are not included in 'Studer Technical Specification - Xtender serial protocol appendix'"},
|
|
5
|
+
{ "_rem": "But are defined by us based on other parameters exposed by the Xcom protocol"},
|
|
6
|
+
|
|
4
7
|
{ "_rem": "Column descriptions:"},
|
|
5
8
|
{ "_rem": " fam = Device Family"},
|
|
6
9
|
{ "_rem": " lvl = User level required to access the param or info"},
|
|
@@ -17,6 +17,7 @@ from .api_base_sync import (
|
|
|
17
17
|
)
|
|
18
18
|
from .const import (
|
|
19
19
|
XcomDiscoverNotConnected,
|
|
20
|
+
XcomTarget,
|
|
20
21
|
)
|
|
21
22
|
from .data import (
|
|
22
23
|
XcomDiscoveredDevice,
|
|
@@ -86,7 +87,10 @@ class AsyncXcomDiscover:
|
|
|
86
87
|
param = self._dataset.get_by_nr(nr, family.id_for_nr)
|
|
87
88
|
|
|
88
89
|
_LOGGER.info(f"Trying device {device_code} on {device_addr} for nr {nr}")
|
|
89
|
-
|
|
90
|
+
match param.target:
|
|
91
|
+
case XcomTarget.STANDARD: value = await self._api.request_value(param, device_addr, verbose=verbose)
|
|
92
|
+
case XcomTarget.VIRTUAL : value = await self._api.request_virtual(param, device_addr, verbose=verbose)
|
|
93
|
+
|
|
90
94
|
if value is not None:
|
|
91
95
|
_LOGGER.info(f" Found device {device_code} via {nr}:{device_addr}")
|
|
92
96
|
|
|
@@ -19,6 +19,7 @@ from .api_base_sync import (
|
|
|
19
19
|
)
|
|
20
20
|
from .const import (
|
|
21
21
|
XcomDiscoverNotConnected,
|
|
22
|
+
XcomTarget,
|
|
22
23
|
)
|
|
23
24
|
from .data import (
|
|
24
25
|
XcomDiscoveredDevice,
|
|
@@ -89,7 +90,10 @@ class XcomDiscover:
|
|
|
89
90
|
param = self._dataset.get_by_nr(nr, family.id_for_nr)
|
|
90
91
|
|
|
91
92
|
_LOGGER.info(f"Trying device {device_code} on {device_addr} for nr {nr}")
|
|
92
|
-
|
|
93
|
+
match param.target:
|
|
94
|
+
case XcomTarget.STANDARD: value = self._api.request_value(param, device_addr, verbose=verbose)
|
|
95
|
+
case XcomTarget.VIRTUAL : value = self._api.request_virtual(param, device_addr, verbose=verbose)
|
|
96
|
+
|
|
93
97
|
if value is not None:
|
|
94
98
|
_LOGGER.info(f" Found device {device_code} via {nr}:{device_addr}")
|
|
95
99
|
|
|
@@ -139,10 +139,10 @@ class XcomDeviceFamilies:
|
|
|
139
139
|
)
|
|
140
140
|
XCOM = XcomDeviceFamily(
|
|
141
141
|
"xcom", "xcom", # virtual device to expose additional values in a uniform way
|
|
142
|
-
"Xcom",
|
|
142
|
+
"Xcom-LAN/232i",
|
|
143
143
|
990, # addr multicast to all devices (write only)
|
|
144
144
|
990, 990, # addr devices, start to end
|
|
145
|
-
|
|
145
|
+
98000, 98999, # nr for params, start to end
|
|
146
146
|
99000, 99999, # nr for infos, start to end
|
|
147
147
|
99000, # nr for discovery
|
|
148
148
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystuderxcom
|
|
3
|
-
Version: 3.4.
|
|
3
|
+
Version: 3.4.2
|
|
4
4
|
Summary: Python library to communicate with Studer-Innotec devices on the local network via Xcom protocol
|
|
5
5
|
Author-email: Anko Hanse <anko_hanse@hotmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ankohanse/pystuderxcom
|
|
@@ -9,8 +9,9 @@ from pystuderxcom import AsyncXcomFactory, XcomFactory
|
|
|
9
9
|
from pystuderxcom import XcomApiTimeoutException, XcomApiResponseIsError, XcomParamException
|
|
10
10
|
from pystuderxcom import XcomDataset, XcomData, XcomPackage
|
|
11
11
|
from pystuderxcom import XcomValues, XcomValuesItem
|
|
12
|
-
from pystuderxcom import XcomVoltage, XcomFormat, XcomAggregationType, ScomServiceId, ScomServiceFlag, ScomFrameFlag, ScomObjType, ScomObjId, ScomQspId, ScomAddress, ScomErrorCode
|
|
12
|
+
from pystuderxcom import XcomVoltage, XcomFormat, XcomAggregationType, XcomTarget, ScomServiceId, ScomServiceFlag, ScomFrameFlag, ScomObjType, ScomObjId, ScomQspId, ScomAddress, ScomErrorCode
|
|
13
13
|
from pystuderxcom import XcomDataMessageRsp
|
|
14
|
+
|
|
14
15
|
from . import AsyncTestApi, TestApi
|
|
15
16
|
from . import AsyncTaskHelper, TaskHelper
|
|
16
17
|
|
|
@@ -63,6 +64,7 @@ async def data_message():
|
|
|
63
64
|
@pytest.mark.parametrize(
|
|
64
65
|
"name, test_nr, test_dest, rsp_frm_flags, rsp_data, exp_value, exp_except",
|
|
65
66
|
[
|
|
67
|
+
("request virtual err", 3000, 100, 0x00, None, None, XcomParamException),
|
|
66
68
|
("request virtual false", 99000, 990, 0x00, None, False, None),
|
|
67
69
|
("request virtual is_message_pending", 99000, 990, ScomFrameFlag.IS_MESSAGE_PENDING, None, True, None),
|
|
68
70
|
("request virtual was_rcc_reseted", 99001, 990, ScomFrameFlag.WAS_RCC_RESETED, None, True, None),
|
|
@@ -99,7 +101,6 @@ async def test_request_virtual(name, test_nr, test_dest, rsp_frm_flags, rsp_data
|
|
|
99
101
|
|
|
100
102
|
if exp_except == None:
|
|
101
103
|
value = await api.request_virtual(param, test_dest, retries=1, timeout=5)
|
|
102
|
-
|
|
103
104
|
assert value == exp_value
|
|
104
105
|
else:
|
|
105
106
|
with pytest.raises(exp_except):
|
|
@@ -162,6 +163,7 @@ async def test_request_guid(name, exp_dst_addr, exp_svc_id, exp_obj_type, exp_ob
|
|
|
162
163
|
("request info timeout", 3000, 100, 100, ScomServiceId.READ, ScomObjType.INFO, 3000, ScomQspId.VALUE, 0x00, XcomData.pack(1234.0, XcomFormat.FLOAT), None, XcomApiTimeoutException),
|
|
163
164
|
("request param ok", 1107, 100, 100, ScomServiceId.READ, ScomObjType.PARAMETER, 1107, ScomQspId.UNSAVED_VALUE, 0x02, XcomData.pack(1234.0, XcomFormat.FLOAT), 1234.0, None),
|
|
164
165
|
("request param vo", 5012, 501, 501, ScomServiceId.READ, ScomObjType.PARAMETER, 5012, ScomQspId.UNSAVED_VALUE, 0x02, XcomData.pack(32, XcomFormat.INT32), 32, None),
|
|
166
|
+
("request virtual err", 99003, 990, 990, ScomServiceId.READ, ScomObjType.INFO, 3000, ScomQspId.VALUE, 0x02, XcomData.pack(1234.0, XcomFormat.FLOAT), None, XcomParamException),
|
|
165
167
|
]
|
|
166
168
|
)
|
|
167
169
|
async def test_request_value(name, test_nr, test_dest, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id, exp_prop_id, rsp_flags, rsp_data, exp_value, exp_except, request):
|
|
@@ -173,6 +175,7 @@ async def test_request_value(name, test_nr, test_dest, exp_dst_addr, exp_svc_id,
|
|
|
173
175
|
"""Helper to turn a request into a response"""
|
|
174
176
|
api.response_package = copy.deepcopy(api.request_package)
|
|
175
177
|
|
|
178
|
+
api.response_package.header.frame_flags = ScomFrameFlag.IS_MESSAGE_PENDING
|
|
176
179
|
api.response_package.frame_data.service_flags = rsp_flags
|
|
177
180
|
api.response_package.frame_data.service_data.property_data = rsp_data
|
|
178
181
|
api.response_package.header.data_length = len(api.response_package.frame_data)
|
|
@@ -281,9 +284,12 @@ async def data_infos_dev(dataset):
|
|
|
281
284
|
XcomValuesItem(info_3023, aggregation_type=XcomAggregationType.DEVICE1, value=78.9),
|
|
282
285
|
]
|
|
283
286
|
)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
rsp_single = XcomValues(
|
|
288
|
+
flags = 0x00,
|
|
289
|
+
datetime = 0,
|
|
290
|
+
items=[]
|
|
291
|
+
)
|
|
292
|
+
yield req_data, rsp_multi, rsp_single
|
|
287
293
|
|
|
288
294
|
@pytest_asyncio.fixture
|
|
289
295
|
async def data_infos_aggr(dataset):
|
|
@@ -305,9 +311,12 @@ async def data_infos_aggr(dataset):
|
|
|
305
311
|
XcomValuesItem(info_3023, aggregation_type=XcomAggregationType.SUM, value=78.9),
|
|
306
312
|
]
|
|
307
313
|
)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
314
|
+
rsp_single = XcomValues(
|
|
315
|
+
flags = 0x00,
|
|
316
|
+
datetime = 0,
|
|
317
|
+
items=[]
|
|
318
|
+
)
|
|
319
|
+
yield req_data, rsp_multi, rsp_single
|
|
311
320
|
|
|
312
321
|
@pytest_asyncio.fixture
|
|
313
322
|
async def data_infos_params_dev(dataset):
|
|
@@ -328,9 +337,14 @@ async def data_infos_params_dev(dataset):
|
|
|
328
337
|
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.DEVICE1, value=45.6),
|
|
329
338
|
]
|
|
330
339
|
)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
340
|
+
rsp_single = XcomValues(
|
|
341
|
+
flags = 0x00,
|
|
342
|
+
datetime = 0,
|
|
343
|
+
items=[
|
|
344
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
345
|
+
]
|
|
346
|
+
)
|
|
347
|
+
yield req_data, rsp_multi, rsp_single
|
|
334
348
|
|
|
335
349
|
|
|
336
350
|
@pytest_asyncio.fixture
|
|
@@ -352,13 +366,82 @@ async def data_infos_params_aggr(dataset):
|
|
|
352
366
|
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.AVERAGE, value=45.6),
|
|
353
367
|
]
|
|
354
368
|
)
|
|
355
|
-
|
|
369
|
+
rsp_single = XcomValues(
|
|
370
|
+
flags = 0x00,
|
|
371
|
+
datetime = 0,
|
|
372
|
+
items=[
|
|
373
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
374
|
+
]
|
|
375
|
+
)
|
|
376
|
+
yield req_data, rsp_multi, rsp_single
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@pytest_asyncio.fixture
|
|
380
|
+
async def data_infos_virt_dev(dataset):
|
|
381
|
+
info_3021 = dataset.get_by_nr(3021)
|
|
382
|
+
info_3022 = dataset.get_by_nr(3022)
|
|
383
|
+
param_1107 = dataset.get_by_nr(1107)
|
|
384
|
+
info_99003 = dataset.get_by_nr(99003)
|
|
385
|
+
|
|
386
|
+
req_data = XcomValues([
|
|
387
|
+
XcomValuesItem(info_3021, code="XT1"),
|
|
388
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.DEVICE1),
|
|
389
|
+
XcomValuesItem(param_1107, address=101),
|
|
390
|
+
XcomValuesItem(info_99003, code="XCOM"), # Make sure to have another single request prior to virtual
|
|
391
|
+
])
|
|
392
|
+
rsp_multi = XcomValues(
|
|
393
|
+
flags = 0x00,
|
|
394
|
+
datetime = 0,
|
|
395
|
+
items=[
|
|
396
|
+
XcomValuesItem(info_3021, aggregation_type=XcomAggregationType.MASTER, value=12.3),
|
|
397
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.DEVICE1, value=45.6),
|
|
398
|
+
]
|
|
399
|
+
)
|
|
400
|
+
rsp_single = XcomValues(
|
|
401
|
+
flags = 0x00,
|
|
402
|
+
datetime = 0,
|
|
403
|
+
items=[
|
|
404
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
405
|
+
XcomValuesItem(info_99003, aggregation_type=XcomAggregationType.DEVICE1, value=True),
|
|
406
|
+
]
|
|
407
|
+
)
|
|
408
|
+
yield req_data, rsp_multi, rsp_single
|
|
409
|
+
|
|
356
410
|
|
|
357
|
-
|
|
411
|
+
@pytest_asyncio.fixture
|
|
412
|
+
async def data_infos_virt_aggr(dataset):
|
|
413
|
+
info_3021 = dataset.get_by_nr(3021)
|
|
414
|
+
info_3022 = dataset.get_by_nr(3022)
|
|
415
|
+
param_1107 = dataset.get_by_nr(1107)
|
|
416
|
+
info_99003 = dataset.get_by_nr(99003)
|
|
417
|
+
|
|
418
|
+
req_data = XcomValues([
|
|
419
|
+
XcomValuesItem(info_3021, aggregation_type=XcomAggregationType.MASTER),
|
|
420
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.AVERAGE),
|
|
421
|
+
XcomValuesItem(param_1107, address=101),
|
|
422
|
+
XcomValuesItem(info_99003, code="XCOM"), # Make sure to have another single request prior to virtual
|
|
423
|
+
])
|
|
424
|
+
rsp_multi = XcomValues(
|
|
425
|
+
flags = 0x00,
|
|
426
|
+
datetime = 0,
|
|
427
|
+
items=[
|
|
428
|
+
XcomValuesItem(info_3021, aggregation_type=XcomAggregationType.MASTER, value=12.3),
|
|
429
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.AVERAGE, value=45.6),
|
|
430
|
+
]
|
|
431
|
+
)
|
|
432
|
+
rsp_single = XcomValues(
|
|
433
|
+
flags = 0x00,
|
|
434
|
+
datetime = 0,
|
|
435
|
+
items=[
|
|
436
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
437
|
+
XcomValuesItem(info_99003, aggregation_type=XcomAggregationType.DEVICE1, value=True),
|
|
438
|
+
]
|
|
439
|
+
)
|
|
440
|
+
yield req_data, rsp_multi, rsp_single
|
|
358
441
|
|
|
359
442
|
|
|
360
443
|
@pytest.mark.asyncio
|
|
361
|
-
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr")
|
|
444
|
+
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr", "data_infos_virt_dev", "data_infos_virt_aggr")
|
|
362
445
|
@pytest.mark.parametrize(
|
|
363
446
|
"name, values_fixture, run_receive, exp_src_addr, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id, exp_prop_id, rsp_flags, exp_except",
|
|
364
447
|
[
|
|
@@ -370,6 +453,8 @@ async def data_infos_params_aggr(dataset):
|
|
|
370
453
|
("request infos aggr timeout", "data_infos_aggr", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x02, XcomApiTimeoutException),
|
|
371
454
|
("request infos params err", "data_infos_params_dev", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x03, XcomParamException),
|
|
372
455
|
("request infos params aggr", "data_infos_params_aggr", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x03, XcomParamException),
|
|
456
|
+
("request infos virt err", "data_infos_virt_dev", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x03, XcomParamException),
|
|
457
|
+
("request infos virt aggr", "data_infos_virt_aggr", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x02, XcomParamException),
|
|
373
458
|
]
|
|
374
459
|
)
|
|
375
460
|
async def test_request_infos(name, values_fixture, run_receive, exp_src_addr, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id, exp_prop_id, rsp_flags, exp_except, request):
|
|
@@ -383,6 +468,7 @@ async def test_request_infos(name, values_fixture, run_receive, exp_src_addr, ex
|
|
|
383
468
|
else:
|
|
384
469
|
api.response_package = copy.deepcopy(api.request_package)
|
|
385
470
|
|
|
471
|
+
api.response_package.header.frame_flags = 0x1F
|
|
386
472
|
api.response_package.frame_data.service_flags = rsp_flags
|
|
387
473
|
if rsp_flags & 0x01:
|
|
388
474
|
api.response_package.frame_data.service_data.property_data = XcomData.pack(ScomErrorCode.READ_PROPERTY_FAILED, XcomFormat.ERROR)
|
|
@@ -435,7 +521,7 @@ async def test_request_infos(name, values_fixture, run_receive, exp_src_addr, ex
|
|
|
435
521
|
|
|
436
522
|
|
|
437
523
|
@pytest.mark.asyncio
|
|
438
|
-
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr")
|
|
524
|
+
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr", "data_infos_virt_dev", "data_infos_virt_aggr")
|
|
439
525
|
@pytest.mark.parametrize(
|
|
440
526
|
"name, values_fixture, run_receive, rsp_flags, exp_value, exp_error, exp_except",
|
|
441
527
|
[
|
|
@@ -445,11 +531,13 @@ async def test_request_infos(name, values_fixture, run_receive, exp_src_addr, ex
|
|
|
445
531
|
("request values infos aggr", "data_infos_aggr", False, 0x02, False, False, XcomParamException),
|
|
446
532
|
("request values params ok", "data_infos_params_dev", True, 0x02, True, False, None),
|
|
447
533
|
("request values params aggr", "data_infos_params_aggr", False, 0x02, False, False, XcomParamException),
|
|
534
|
+
("request values virt ok", "data_infos_virt_dev", True, 0x02, True, False, None),
|
|
535
|
+
("request values virt aggr", "data_infos_virt_aggr", False, 0x02, False, False, XcomParamException),
|
|
448
536
|
]
|
|
449
537
|
)
|
|
450
538
|
async def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_value, exp_error, exp_except, request):
|
|
451
539
|
|
|
452
|
-
req_data, exp_rsp_multi,
|
|
540
|
+
req_data, exp_rsp_multi, exp_rsp_single = request.getfixturevalue(values_fixture)
|
|
453
541
|
|
|
454
542
|
async def on_receive(api: AsyncTestApi):
|
|
455
543
|
"""Helper to turn a request into a response"""
|
|
@@ -457,6 +545,7 @@ async def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_
|
|
|
457
545
|
api.response_package = None
|
|
458
546
|
else:
|
|
459
547
|
api.response_package = copy.deepcopy(api.request_package)
|
|
548
|
+
api.response_package.header.frame_flags = 0x1F
|
|
460
549
|
api.response_package.frame_data.service_flags = rsp_flags
|
|
461
550
|
|
|
462
551
|
if rsp_flags & 0x01:
|
|
@@ -466,7 +555,8 @@ async def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_
|
|
|
466
555
|
api.response_package.frame_data.service_data.property_data = exp_rsp_multi.pack_response()
|
|
467
556
|
|
|
468
557
|
else:
|
|
469
|
-
api.
|
|
558
|
+
item = next( (i for i in exp_rsp_single.items if i.datapoint.nr == api.request_package.frame_data.service_data.object_id), None)
|
|
559
|
+
api.response_package.frame_data.service_data.property_data = XcomData.pack(item.value, XcomFormat.FLOAT)
|
|
470
560
|
|
|
471
561
|
api.response_package.header.data_length = len(api.response_package.frame_data)
|
|
472
562
|
|
|
@@ -494,7 +584,11 @@ async def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_
|
|
|
494
584
|
if exp_item is not None:
|
|
495
585
|
exp_val = exp_item.value
|
|
496
586
|
else:
|
|
497
|
-
|
|
587
|
+
exp_item = next((i for i in exp_rsp_single.items if i.datapoint.nr==item.datapoint.nr and i.aggregation_type==item.aggregation_type), None)
|
|
588
|
+
if exp_item is not None:
|
|
589
|
+
exp_val = exp_item.value
|
|
590
|
+
else:
|
|
591
|
+
exp_val = None
|
|
498
592
|
|
|
499
593
|
match item.datapoint.format:
|
|
500
594
|
case XcomFormat.FLOAT:
|
|
@@ -11,8 +11,9 @@ from pystuderxcom import AsyncXcomFactory, XcomFactory
|
|
|
11
11
|
from pystuderxcom import XcomApiTimeoutException, XcomApiResponseIsError, XcomParamException
|
|
12
12
|
from pystuderxcom import XcomDataset, XcomData, XcomPackage
|
|
13
13
|
from pystuderxcom import XcomValues, XcomValuesItem
|
|
14
|
-
from pystuderxcom import XcomVoltage, XcomFormat, XcomAggregationType, ScomServiceId, ScomServiceFlag, ScomFrameFlag, ScomObjType, ScomObjId, ScomQspId, ScomAddress, ScomErrorCode
|
|
14
|
+
from pystuderxcom import XcomVoltage, XcomFormat, XcomAggregationType, XcomTarget, ScomServiceId, ScomServiceFlag, ScomFrameFlag, ScomObjType, ScomObjId, ScomQspId, ScomAddress, ScomErrorCode
|
|
15
15
|
from pystuderxcom import XcomDataMessageRsp
|
|
16
|
+
|
|
16
17
|
from . import AsyncTestApi, TestApi
|
|
17
18
|
from . import AsyncTaskHelper, TaskHelper
|
|
18
19
|
|
|
@@ -65,6 +66,7 @@ def data_message():
|
|
|
65
66
|
@pytest.mark.parametrize(
|
|
66
67
|
"name, test_nr, test_dest, rsp_frm_flags, rsp_data, exp_value, exp_except",
|
|
67
68
|
[
|
|
69
|
+
("request virtual err", 3000, 100, 0x00, None, None, XcomParamException),
|
|
68
70
|
("request virtual false", 99000, 990, 0x00, None, False, None),
|
|
69
71
|
("request virtual is_message_pending", 99000, 990, ScomFrameFlag.IS_MESSAGE_PENDING, None, True, None),
|
|
70
72
|
("request virtual was_rcc_reseted", 99001, 990, ScomFrameFlag.WAS_RCC_RESETED, None, True, None),
|
|
@@ -101,7 +103,6 @@ def test_request_virtual(name, test_nr, test_dest, rsp_frm_flags, rsp_data,exp_v
|
|
|
101
103
|
|
|
102
104
|
if exp_except == None:
|
|
103
105
|
value = api.request_virtual(param, test_dest, retries=1, timeout=5)
|
|
104
|
-
|
|
105
106
|
assert value == exp_value
|
|
106
107
|
else:
|
|
107
108
|
with pytest.raises(exp_except):
|
|
@@ -164,6 +165,7 @@ def test_request_guid(name, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id,
|
|
|
164
165
|
("request info timeout", 3000, 100, 100, ScomServiceId.READ, ScomObjType.INFO, 3000, ScomQspId.VALUE, 0x00, XcomData.pack(1234.0, XcomFormat.FLOAT), None, XcomApiTimeoutException),
|
|
165
166
|
("request param ok", 1107, 100, 100, ScomServiceId.READ, ScomObjType.PARAMETER, 1107, ScomQspId.UNSAVED_VALUE, 0x02, XcomData.pack(1234.0, XcomFormat.FLOAT), 1234.0, None),
|
|
166
167
|
("request param vo", 5012, 501, 501, ScomServiceId.READ, ScomObjType.PARAMETER, 5012, ScomQspId.UNSAVED_VALUE, 0x02, XcomData.pack(32, XcomFormat.INT32), 32, None),
|
|
168
|
+
("request virtual err", 99003, 990, 990, ScomServiceId.READ, ScomObjType.INFO, 3000, ScomQspId.VALUE, 0x02, XcomData.pack(1234.0, XcomFormat.FLOAT), None, XcomParamException),
|
|
167
169
|
]
|
|
168
170
|
)
|
|
169
171
|
def test_request_value(name, test_nr, test_dest, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id, exp_prop_id, rsp_flags, rsp_data, exp_value, exp_except, request):
|
|
@@ -175,6 +177,7 @@ def test_request_value(name, test_nr, test_dest, exp_dst_addr, exp_svc_id, exp_o
|
|
|
175
177
|
"""Helper to turn a request into a response"""
|
|
176
178
|
api.response_package = copy.deepcopy(api.request_package)
|
|
177
179
|
|
|
180
|
+
api.response_package.header.frame_flags = ScomFrameFlag.IS_MESSAGE_PENDING
|
|
178
181
|
api.response_package.frame_data.service_flags = rsp_flags
|
|
179
182
|
api.response_package.frame_data.service_data.property_data = rsp_data
|
|
180
183
|
api.response_package.header.data_length = len(api.response_package.frame_data)
|
|
@@ -283,9 +286,12 @@ def data_infos_dev(dataset):
|
|
|
283
286
|
XcomValuesItem(info_3023, aggregation_type=XcomAggregationType.DEVICE1, value=78.9),
|
|
284
287
|
]
|
|
285
288
|
)
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
rsp_single = XcomValues(
|
|
290
|
+
flags = 0x00,
|
|
291
|
+
datetime = 0,
|
|
292
|
+
items=[]
|
|
293
|
+
)
|
|
294
|
+
yield req_data, rsp_multi, rsp_single
|
|
289
295
|
|
|
290
296
|
@pytest.fixture
|
|
291
297
|
def data_infos_aggr(dataset):
|
|
@@ -307,9 +313,12 @@ def data_infos_aggr(dataset):
|
|
|
307
313
|
XcomValuesItem(info_3023, aggregation_type=XcomAggregationType.SUM, value=78.9),
|
|
308
314
|
]
|
|
309
315
|
)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
316
|
+
rsp_single = XcomValues(
|
|
317
|
+
flags = 0x00,
|
|
318
|
+
datetime = 0,
|
|
319
|
+
items=[]
|
|
320
|
+
)
|
|
321
|
+
yield req_data, rsp_multi, rsp_single
|
|
313
322
|
|
|
314
323
|
@pytest.fixture
|
|
315
324
|
def data_infos_params_dev(dataset):
|
|
@@ -330,9 +339,14 @@ def data_infos_params_dev(dataset):
|
|
|
330
339
|
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.DEVICE1, value=45.6),
|
|
331
340
|
]
|
|
332
341
|
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
342
|
+
rsp_single = XcomValues(
|
|
343
|
+
flags = 0x00,
|
|
344
|
+
datetime = 0,
|
|
345
|
+
items=[
|
|
346
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
347
|
+
]
|
|
348
|
+
)
|
|
349
|
+
yield req_data, rsp_multi, rsp_single
|
|
336
350
|
|
|
337
351
|
|
|
338
352
|
@pytest.fixture
|
|
@@ -354,13 +368,82 @@ def data_infos_params_aggr(dataset):
|
|
|
354
368
|
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.AVERAGE, value=45.6),
|
|
355
369
|
]
|
|
356
370
|
)
|
|
357
|
-
|
|
371
|
+
rsp_single = XcomValues(
|
|
372
|
+
flags = 0x00,
|
|
373
|
+
datetime = 0,
|
|
374
|
+
items=[
|
|
375
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
376
|
+
]
|
|
377
|
+
)
|
|
378
|
+
yield req_data, rsp_multi, rsp_single
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@pytest.fixture
|
|
382
|
+
def data_infos_virt_dev(dataset):
|
|
383
|
+
info_3021 = dataset.get_by_nr(3021)
|
|
384
|
+
info_3022 = dataset.get_by_nr(3022)
|
|
385
|
+
param_1107 = dataset.get_by_nr(1107)
|
|
386
|
+
info_99003 = dataset.get_by_nr(99003)
|
|
387
|
+
|
|
388
|
+
req_data = XcomValues([
|
|
389
|
+
XcomValuesItem(info_3021, code="XT1"),
|
|
390
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.DEVICE1),
|
|
391
|
+
XcomValuesItem(param_1107, address=101),
|
|
392
|
+
XcomValuesItem(info_99003, code="XCOM"), # Make sure to have another single request prior to virtual
|
|
393
|
+
])
|
|
394
|
+
rsp_multi = XcomValues(
|
|
395
|
+
flags = 0x00,
|
|
396
|
+
datetime = 0,
|
|
397
|
+
items=[
|
|
398
|
+
XcomValuesItem(info_3021, aggregation_type=XcomAggregationType.MASTER, value=12.3),
|
|
399
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.DEVICE1, value=45.6),
|
|
400
|
+
]
|
|
401
|
+
)
|
|
402
|
+
rsp_single = XcomValues(
|
|
403
|
+
flags = 0x00,
|
|
404
|
+
datetime = 0,
|
|
405
|
+
items=[
|
|
406
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
407
|
+
XcomValuesItem(info_99003, aggregation_type=XcomAggregationType.DEVICE1, value=True),
|
|
408
|
+
]
|
|
409
|
+
)
|
|
410
|
+
yield req_data, rsp_multi, rsp_single
|
|
411
|
+
|
|
358
412
|
|
|
359
|
-
|
|
413
|
+
@pytest.fixture
|
|
414
|
+
def data_infos_virt_aggr(dataset):
|
|
415
|
+
info_3021 = dataset.get_by_nr(3021)
|
|
416
|
+
info_3022 = dataset.get_by_nr(3022)
|
|
417
|
+
param_1107 = dataset.get_by_nr(1107)
|
|
418
|
+
info_99003 = dataset.get_by_nr(99003)
|
|
419
|
+
|
|
420
|
+
req_data = XcomValues([
|
|
421
|
+
XcomValuesItem(info_3021, aggregation_type=XcomAggregationType.MASTER),
|
|
422
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.AVERAGE),
|
|
423
|
+
XcomValuesItem(param_1107, address=101),
|
|
424
|
+
XcomValuesItem(info_99003, code="XCOM"), # Make sure to have another single request prior to virtual
|
|
425
|
+
])
|
|
426
|
+
rsp_multi = XcomValues(
|
|
427
|
+
flags = 0x00,
|
|
428
|
+
datetime = 0,
|
|
429
|
+
items=[
|
|
430
|
+
XcomValuesItem(info_3021, aggregation_type=XcomAggregationType.MASTER, value=12.3),
|
|
431
|
+
XcomValuesItem(info_3022, aggregation_type=XcomAggregationType.AVERAGE, value=45.6),
|
|
432
|
+
]
|
|
433
|
+
)
|
|
434
|
+
rsp_single = XcomValues(
|
|
435
|
+
flags = 0x00,
|
|
436
|
+
datetime = 0,
|
|
437
|
+
items=[
|
|
438
|
+
XcomValuesItem(param_1107, aggregation_type=XcomAggregationType.DEVICE1, value=1234.0),
|
|
439
|
+
XcomValuesItem(info_99003, aggregation_type=XcomAggregationType.DEVICE1, value=True),
|
|
440
|
+
]
|
|
441
|
+
)
|
|
442
|
+
yield req_data, rsp_multi, rsp_single
|
|
360
443
|
|
|
361
444
|
|
|
362
445
|
@pytest.mark.asyncio
|
|
363
|
-
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr")
|
|
446
|
+
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr", "data_infos_virt_dev", "data_infos_virt_aggr")
|
|
364
447
|
@pytest.mark.parametrize(
|
|
365
448
|
"name, values_fixture, run_receive, exp_src_addr, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id, exp_prop_id, rsp_flags, exp_except",
|
|
366
449
|
[
|
|
@@ -372,6 +455,8 @@ def data_infos_params_aggr(dataset):
|
|
|
372
455
|
("request infos aggr timeout", "data_infos_aggr", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x02, XcomApiTimeoutException),
|
|
373
456
|
("request infos params err", "data_infos_params_dev", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x03, XcomParamException),
|
|
374
457
|
("request infos params aggr", "data_infos_params_aggr", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x03, XcomParamException),
|
|
458
|
+
("request infos virt err", "data_infos_virt_dev", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x03, XcomParamException),
|
|
459
|
+
("request infos virt aggr", "data_infos_virt_aggr", False, ScomAddress.SOURCE, 501, ScomServiceId.READ, ScomObjType.MULTI_INFO, ScomObjId.MULTI_INFO, ScomQspId.MULTI_INFO, 0x02, XcomParamException),
|
|
375
460
|
]
|
|
376
461
|
)
|
|
377
462
|
def test_request_infos(name, values_fixture, run_receive, exp_src_addr, exp_dst_addr, exp_svc_id, exp_obj_type, exp_obj_id, exp_prop_id, rsp_flags, exp_except, request):
|
|
@@ -385,6 +470,7 @@ def test_request_infos(name, values_fixture, run_receive, exp_src_addr, exp_dst_
|
|
|
385
470
|
else:
|
|
386
471
|
api.response_package = copy.deepcopy(api.request_package)
|
|
387
472
|
|
|
473
|
+
api.response_package.header.frame_flags = 0x1F
|
|
388
474
|
api.response_package.frame_data.service_flags = rsp_flags
|
|
389
475
|
if rsp_flags & 0x01:
|
|
390
476
|
api.response_package.frame_data.service_data.property_data = XcomData.pack(ScomErrorCode.READ_PROPERTY_FAILED, XcomFormat.ERROR)
|
|
@@ -437,7 +523,7 @@ def test_request_infos(name, values_fixture, run_receive, exp_src_addr, exp_dst_
|
|
|
437
523
|
|
|
438
524
|
|
|
439
525
|
@pytest.mark.asyncio
|
|
440
|
-
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr")
|
|
526
|
+
@pytest.mark.usefixtures("dataset", "data_infos_dev", "data_infos_aggr", "data_infos_params_dev", "data_infos_params_aggr", "data_infos_virt_dev", "data_infos_virt_aggr")
|
|
441
527
|
@pytest.mark.parametrize(
|
|
442
528
|
"name, values_fixture, run_receive, rsp_flags, exp_value, exp_error, exp_except",
|
|
443
529
|
[
|
|
@@ -447,11 +533,13 @@ def test_request_infos(name, values_fixture, run_receive, exp_src_addr, exp_dst_
|
|
|
447
533
|
("request values infos aggr", "data_infos_aggr", False, 0x02, False, False, XcomParamException),
|
|
448
534
|
("request values params ok", "data_infos_params_dev", True, 0x02, True, False, None),
|
|
449
535
|
("request values params aggr", "data_infos_params_aggr", False, 0x02, False, False, XcomParamException),
|
|
536
|
+
("request values virt ok", "data_infos_virt_dev", True, 0x02, True, False, None),
|
|
537
|
+
("request values virt aggr", "data_infos_virt_aggr", False, 0x02, False, False, XcomParamException),
|
|
450
538
|
]
|
|
451
539
|
)
|
|
452
540
|
def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_value, exp_error, exp_except, request):
|
|
453
541
|
|
|
454
|
-
req_data, exp_rsp_multi,
|
|
542
|
+
req_data, exp_rsp_multi, exp_rsp_single = request.getfixturevalue(values_fixture)
|
|
455
543
|
|
|
456
544
|
def on_receive(api: TestApi):
|
|
457
545
|
"""Helper to turn a request into a response"""
|
|
@@ -459,6 +547,7 @@ def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_value,
|
|
|
459
547
|
api.response_package = None
|
|
460
548
|
else:
|
|
461
549
|
api.response_package = copy.deepcopy(api.request_package)
|
|
550
|
+
api.response_package.header.frame_flags = 0x1F
|
|
462
551
|
api.response_package.frame_data.service_flags = rsp_flags
|
|
463
552
|
|
|
464
553
|
if rsp_flags & 0x01:
|
|
@@ -468,7 +557,8 @@ def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_value,
|
|
|
468
557
|
api.response_package.frame_data.service_data.property_data = exp_rsp_multi.pack_response()
|
|
469
558
|
|
|
470
559
|
else:
|
|
471
|
-
api.
|
|
560
|
+
item = next( (i for i in exp_rsp_single.items if i.datapoint.nr == api.request_package.frame_data.service_data.object_id), None)
|
|
561
|
+
api.response_package.frame_data.service_data.property_data = XcomData.pack(item.value, XcomFormat.FLOAT)
|
|
472
562
|
|
|
473
563
|
api.response_package.header.data_length = len(api.response_package.frame_data)
|
|
474
564
|
|
|
@@ -496,7 +586,11 @@ def test_request_values(name, values_fixture, run_receive, rsp_flags, exp_value,
|
|
|
496
586
|
if exp_item is not None:
|
|
497
587
|
exp_val = exp_item.value
|
|
498
588
|
else:
|
|
499
|
-
|
|
589
|
+
exp_item = next((i for i in exp_rsp_single.items if i.datapoint.nr==item.datapoint.nr and i.aggregation_type==item.aggregation_type), None)
|
|
590
|
+
if exp_item is not None:
|
|
591
|
+
exp_val = exp_item.value
|
|
592
|
+
else:
|
|
593
|
+
exp_val = None
|
|
500
594
|
|
|
501
595
|
match item.datapoint.format:
|
|
502
596
|
case XcomFormat.FLOAT:
|
|
@@ -6,6 +6,7 @@ from pystuderxcom import (
|
|
|
6
6
|
XcomVoltage,
|
|
7
7
|
XcomFormat,
|
|
8
8
|
XcomCategory,
|
|
9
|
+
XcomTarget,
|
|
9
10
|
XcomDatapointUnknownException,
|
|
10
11
|
AsyncXcomFactory,
|
|
11
12
|
XcomFactory,
|
|
@@ -39,6 +40,7 @@ async def test_nr():
|
|
|
39
40
|
assert param.nr == 1107
|
|
40
41
|
assert param.format == XcomFormat.FLOAT
|
|
41
42
|
assert param.category == XcomCategory.PARAMETER
|
|
43
|
+
assert param.target == XcomTarget.STANDARD
|
|
42
44
|
|
|
43
45
|
param = dataset.get_by_nr(1552)
|
|
44
46
|
assert param.family_id == "xt"
|
|
@@ -48,18 +50,21 @@ async def test_nr():
|
|
|
48
50
|
assert param.options != None
|
|
49
51
|
assert type(param.options) is dict
|
|
50
52
|
assert len(param.options) == 3
|
|
53
|
+
assert param.target == XcomTarget.STANDARD
|
|
51
54
|
|
|
52
55
|
param = dataset.get_by_nr(3000)
|
|
53
56
|
assert param.family_id == "xt"
|
|
54
57
|
assert param.nr == 3000
|
|
55
58
|
assert param.format == XcomFormat.FLOAT
|
|
56
59
|
assert param.category == XcomCategory.INFO
|
|
60
|
+
assert param.target == XcomTarget.STANDARD
|
|
57
61
|
|
|
58
62
|
param = dataset.get_by_nr(3000, "xt")
|
|
59
63
|
assert param.family_id == "xt"
|
|
60
64
|
assert param.nr == 3000
|
|
61
65
|
assert param.format == XcomFormat.FLOAT
|
|
62
66
|
assert param.category == XcomCategory.INFO
|
|
67
|
+
assert param.target == XcomTarget.STANDARD
|
|
63
68
|
|
|
64
69
|
param = dataset.get_by_nr(5012, "rcc")
|
|
65
70
|
assert param.family_id == "rcc"
|
|
@@ -68,12 +73,14 @@ async def test_nr():
|
|
|
68
73
|
assert param.category == XcomCategory.PARAMETER
|
|
69
74
|
assert param.options != None
|
|
70
75
|
assert type(param.options) is dict
|
|
76
|
+
assert param.target == XcomTarget.STANDARD
|
|
71
77
|
|
|
72
78
|
param = dataset.get_by_nr(99000)
|
|
73
79
|
assert param.family_id == "xcom"
|
|
74
80
|
assert param.nr == 99000
|
|
75
81
|
assert param.format == XcomFormat.BOOL
|
|
76
|
-
assert param.category == XcomCategory.
|
|
82
|
+
assert param.category == XcomCategory.INFO
|
|
83
|
+
assert param.target == XcomTarget.VIRTUAL
|
|
77
84
|
|
|
78
85
|
with pytest.raises(XcomDatapointUnknownException):
|
|
79
86
|
param = dataset.get_by_nr(9999)
|
|
@@ -8,6 +8,7 @@ from pystuderxcom import (
|
|
|
8
8
|
XcomVoltage,
|
|
9
9
|
XcomFormat,
|
|
10
10
|
XcomCategory,
|
|
11
|
+
XcomTarget,
|
|
11
12
|
XcomDatapointUnknownException,
|
|
12
13
|
AsyncXcomFactory,
|
|
13
14
|
XcomFactory,
|
|
@@ -41,6 +42,7 @@ def test_nr():
|
|
|
41
42
|
assert param.nr == 1107
|
|
42
43
|
assert param.format == XcomFormat.FLOAT
|
|
43
44
|
assert param.category == XcomCategory.PARAMETER
|
|
45
|
+
assert param.target == XcomTarget.STANDARD
|
|
44
46
|
|
|
45
47
|
param = dataset.get_by_nr(1552)
|
|
46
48
|
assert param.family_id == "xt"
|
|
@@ -50,18 +52,21 @@ def test_nr():
|
|
|
50
52
|
assert param.options != None
|
|
51
53
|
assert type(param.options) is dict
|
|
52
54
|
assert len(param.options) == 3
|
|
55
|
+
assert param.target == XcomTarget.STANDARD
|
|
53
56
|
|
|
54
57
|
param = dataset.get_by_nr(3000)
|
|
55
58
|
assert param.family_id == "xt"
|
|
56
59
|
assert param.nr == 3000
|
|
57
60
|
assert param.format == XcomFormat.FLOAT
|
|
58
61
|
assert param.category == XcomCategory.INFO
|
|
62
|
+
assert param.target == XcomTarget.STANDARD
|
|
59
63
|
|
|
60
64
|
param = dataset.get_by_nr(3000, "xt")
|
|
61
65
|
assert param.family_id == "xt"
|
|
62
66
|
assert param.nr == 3000
|
|
63
67
|
assert param.format == XcomFormat.FLOAT
|
|
64
68
|
assert param.category == XcomCategory.INFO
|
|
69
|
+
assert param.target == XcomTarget.STANDARD
|
|
65
70
|
|
|
66
71
|
param = dataset.get_by_nr(5012, "rcc")
|
|
67
72
|
assert param.family_id == "rcc"
|
|
@@ -70,12 +75,14 @@ def test_nr():
|
|
|
70
75
|
assert param.category == XcomCategory.PARAMETER
|
|
71
76
|
assert param.options != None
|
|
72
77
|
assert type(param.options) is dict
|
|
78
|
+
assert param.target == XcomTarget.STANDARD
|
|
73
79
|
|
|
74
80
|
param = dataset.get_by_nr(99000)
|
|
75
81
|
assert param.family_id == "xcom"
|
|
76
82
|
assert param.nr == 99000
|
|
77
83
|
assert param.format == XcomFormat.BOOL
|
|
78
|
-
assert param.category == XcomCategory.
|
|
84
|
+
assert param.category == XcomCategory.INFO
|
|
85
|
+
assert param.target == XcomTarget.VIRTUAL
|
|
79
86
|
|
|
80
87
|
with pytest.raises(XcomDatapointUnknownException):
|
|
81
88
|
param = dataset.get_by_nr(9999)
|
|
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
|