pystuderxcom 3.4.7__tar.gz → 3.6.0__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.7/src/pystuderxcom.egg-info → pystuderxcom-3.6.0}/PKG-INFO +4 -4
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/README.md +3 -3
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/pyproject.toml +1 -1
- pystuderxcom-3.6.0/src/pystuderxcom/__init__.py +31 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_base_async.py +122 -103
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_base_sync.py +122 -103
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_serial.py +4 -10
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_tcp.py +2 -33
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_udp.py +4 -36
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/const.py +15 -65
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/data.py +42 -55
- pystuderxcom-3.6.0/src/pystuderxcom/datapoints.py +301 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/discover_async.py +70 -93
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/discover_sync.py +71 -94
- pystuderxcom-3.6.0/src/pystuderxcom/families.py +356 -0
- pystuderxcom-3.6.0/src/pystuderxcom/messages.py +177 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/protocol.py +4 -2
- pystuderxcom-3.6.0/src/pystuderxcom/shared/helpers.py +106 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_dataset.py +208 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_families.py +68 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_interfaces_async.py +101 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_interfaces_sync.py +103 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_messageset.py +59 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_types.py +123 -0
- pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_valueset.py +40 -0
- pystuderxcom-3.6.0/src/pystuderxcom/values.py +142 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0/src/pystuderxcom.egg-info}/PKG-INFO +4 -4
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/SOURCES.txt +10 -6
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_base_async.py +86 -86
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_base_sync.py +86 -86
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_serial_async.py +1 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_serial_sync.py +1 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_tcp_async.py +1 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_tcp_sync.py +1 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_udp_async.py +1 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_udp_sync.py +1 -5
- pystuderxcom-3.6.0/tests/test_const.py +69 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_data.py +10 -9
- pystuderxcom-3.6.0/tests/test_datapoints.py +189 -0
- pystuderxcom-3.6.0/tests/test_discover_async.py +215 -0
- pystuderxcom-3.6.0/tests/test_discover_sync.py +217 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_families.py +23 -18
- pystuderxcom-3.6.0/tests/test_messages.py +64 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_package_async.py +6 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_package_sync.py +6 -5
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_values.py +17 -18
- pystuderxcom-3.4.7/src/pystuderxcom/__init__.py +0 -26
- pystuderxcom-3.4.7/src/pystuderxcom/datapoints.py +0 -162
- pystuderxcom-3.4.7/src/pystuderxcom/factory_async.py +0 -164
- pystuderxcom-3.4.7/src/pystuderxcom/factory_sync.py +0 -166
- pystuderxcom-3.4.7/src/pystuderxcom/families.py +0 -278
- pystuderxcom-3.4.7/src/pystuderxcom/messages.py +0 -97
- pystuderxcom-3.4.7/src/pystuderxcom/values.py +0 -139
- pystuderxcom-3.4.7/tests/test_const.py +0 -104
- pystuderxcom-3.4.7/tests/test_datapoints_async.py +0 -147
- pystuderxcom-3.4.7/tests/test_datapoints_sync.py +0 -149
- pystuderxcom-3.4.7/tests/test_discover_async.py +0 -208
- pystuderxcom-3.4.7/tests/test_discover_sync.py +0 -210
- pystuderxcom-3.4.7/tests/test_messageset_async.py +0 -51
- pystuderxcom-3.4.7/tests/test_messageset_sync.py +0 -53
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/LICENSE +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/setup.cfg +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/datapoints_120v.json +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/datapoints_240v.json +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/datapoints_xcom.json +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/messages_en.json +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/dependency_links.txt +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/requires.txt +0 -0
- {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystuderxcom
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.6.0
|
|
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
|
|
@@ -22,7 +22,7 @@ Requires-Dist: pytest; extra == "tests"
|
|
|
22
22
|
Requires-Dist: pytest-asyncio; extra == "tests"
|
|
23
23
|
Dynamic: license-file
|
|
24
24
|
|
|
25
|
-
[](LICENSE)
|
|
26
26
|
[](https://www.buymeacoffee.com/ankohanse)
|
|
27
27
|
|
|
28
28
|
|
|
@@ -46,7 +46,7 @@ It should also be able to detect and handle
|
|
|
46
46
|
|
|
47
47
|
Disclaimer: this library is NOT created by Studer-Innotec, but is based on their documentation of the Xcom protocol.
|
|
48
48
|
That documentation can be found on:
|
|
49
|
-
[Studer-Innotec Download Center](https://www.studer-innotec.com/en/downloads/) *->
|
|
49
|
+
[Studer-Innotec Download Center](https://www.studer-innotec.com/en/downloads/) *-> Openstuder -> Communication protocol Xcom-232i*
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
# Prerequisites
|
|
@@ -95,7 +95,7 @@ To read an infos or param or write to a param:
|
|
|
95
95
|
```
|
|
96
96
|
from pystuderxcom import XcomApiTcp, XcomApiTcpMode, XcomDataset, XcomVoltage
|
|
97
97
|
|
|
98
|
-
dataset =
|
|
98
|
+
dataset = XcomDataset.get_instance(XcomVoltage.AC240, XcomVoltage.DC48)
|
|
99
99
|
info_3023 = dataset.get_by_nr(3023, "xt") # the "xt" part is optional but usefull for detecting mistakes
|
|
100
100
|
info_6001 = dataset.get_by_nr(6001, "bsp")
|
|
101
101
|
param_1107 = dataset.get_by_nr(1107, "xt")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](LICENSE)
|
|
2
2
|
[](https://www.buymeacoffee.com/ankohanse)
|
|
3
3
|
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ It should also be able to detect and handle
|
|
|
22
22
|
|
|
23
23
|
Disclaimer: this library is NOT created by Studer-Innotec, but is based on their documentation of the Xcom protocol.
|
|
24
24
|
That documentation can be found on:
|
|
25
|
-
[Studer-Innotec Download Center](https://www.studer-innotec.com/en/downloads/) *->
|
|
25
|
+
[Studer-Innotec Download Center](https://www.studer-innotec.com/en/downloads/) *-> Openstuder -> Communication protocol Xcom-232i*
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
# Prerequisites
|
|
@@ -71,7 +71,7 @@ To read an infos or param or write to a param:
|
|
|
71
71
|
```
|
|
72
72
|
from pystuderxcom import XcomApiTcp, XcomApiTcpMode, XcomDataset, XcomVoltage
|
|
73
73
|
|
|
74
|
-
dataset =
|
|
74
|
+
dataset = XcomDataset.get_instance(XcomVoltage.AC240, XcomVoltage.DC48)
|
|
75
75
|
info_3023 = dataset.get_by_nr(3023, "xt") # the "xt" part is optional but usefull for detecting mistakes
|
|
76
76
|
info_6001 = dataset.get_by_nr(6001, "bsp")
|
|
77
77
|
param_1107 = dataset.get_by_nr(1107, "xt")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from .shared.studer_types import StuderUserLevel, StuderAccess, StuderTarget, StuderDataType
|
|
2
|
+
from .shared.studer_types import StuderDiscoveredGateway, StuderDiscoveredDevice, StuderDiscoverNotConnected
|
|
3
|
+
from .shared.studer_types import StuderParamException
|
|
4
|
+
from .shared.studer_dataset import StuderDataset, StuderDatapoint, StuderDatapointUnknownException, StuderDatapointSyntaxException, StuderDatapointEnumNotFoundException
|
|
5
|
+
from .shared.studer_families import StuderDeviceFamily, StuderDeviceFamilies, StuderDeviceFamilyUnknownException, StuderDeviceCodeUnknownException, StuderDeviceAddressUnknownException, StuderDeviceSlaveUnknownException
|
|
6
|
+
from .shared.studer_messageset import StuderMessageSet, StuderMessageDef, StuderMessageUnknownException, StuderMessageSyntaxException
|
|
7
|
+
from .shared.studer_interfaces_async import AsyncStuderDiscover, StuderDiscoverFlags
|
|
8
|
+
from .shared.studer_interfaces_sync import StuderDiscover
|
|
9
|
+
|
|
10
|
+
from .api_tcp import AsyncXcomApiTcp, XcomApiTcp
|
|
11
|
+
from .api_udp import AsyncXcomApiUdp, XcomApiUdp
|
|
12
|
+
from .api_serial import AsyncXcomApiSerial, XcomApiSerial
|
|
13
|
+
|
|
14
|
+
from .api_base_async import AsyncXcomApiBase
|
|
15
|
+
from .discover_async import AsyncXcomDiscover
|
|
16
|
+
|
|
17
|
+
from .api_base_sync import XcomApiBase
|
|
18
|
+
from .discover_sync import XcomDiscover
|
|
19
|
+
|
|
20
|
+
from .const import XcomApiTcpMode, XcomVoltage, XcomAggregationType
|
|
21
|
+
from .const import XcomApiWriteException, XcomApiReadException, XcomApiTimeoutException, XcomApiUnpackException, XcomApiResponseIsError, XcomDiscoverNotConnected, XcomParamException
|
|
22
|
+
from .datapoints import XcomDataset, XcomDatapoint
|
|
23
|
+
from .families import XcomDeviceFamily, XcomDeviceFamilies
|
|
24
|
+
from .messages import XcomMessageSet, XcomMessageDef, XcomMessage
|
|
25
|
+
from .values import XcomValueItem, XcomValueSet
|
|
26
|
+
|
|
27
|
+
# For unit testing
|
|
28
|
+
from .const import XcomUserLevel, ScomFrameFlag, ScomObjType, ScomObjId, ScomServiceId, ScomServiceFlag, ScomQspId, ScomQspLevel, ScomAddress, ScomErrorCode
|
|
29
|
+
from .data import XcomData, XcomDataMessageRsp, XcomDataMultiInfoReq, XcomDataMultiInfoReqItem, XcomDataMultiInfoRsp, XcomDataMultiInfoRspItem
|
|
30
|
+
from .protocol import XcomHeader, XcomFrame, XcomService, XcomPackage
|
|
31
|
+
|
|
@@ -2,61 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import binascii
|
|
5
|
+
from io import BufferedReader
|
|
5
6
|
import logging
|
|
6
7
|
|
|
7
8
|
from datetime import datetime, timedelta
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
ScomServiceId,
|
|
24
|
-
ScomQspId,
|
|
25
|
-
XcomApiReadException,
|
|
26
|
-
XcomApiWriteException,
|
|
27
|
-
XcomApiUnpackException,
|
|
28
|
-
XcomApiTimeoutException,
|
|
29
|
-
XcomApiResponseIsError,
|
|
30
|
-
XcomParamException,
|
|
31
|
-
safe_len,
|
|
32
|
-
)
|
|
33
|
-
from .data import (
|
|
34
|
-
XcomData,
|
|
35
|
-
XcomDataMessageRsp,
|
|
36
|
-
MULTI_INFO_REQ_MAX,
|
|
37
|
-
)
|
|
38
|
-
from .datapoints import (
|
|
39
|
-
XcomDatapoint,
|
|
40
|
-
)
|
|
41
|
-
from .factory_async import (
|
|
42
|
-
AsyncXcomFactory,
|
|
43
|
-
)
|
|
44
|
-
from .factory_sync import (
|
|
45
|
-
XcomFactory,
|
|
46
|
-
)
|
|
47
|
-
from .families import (
|
|
48
|
-
XcomDeviceFamilies
|
|
49
|
-
)
|
|
50
|
-
from .messages import (
|
|
51
|
-
XcomMessage,
|
|
52
|
-
)
|
|
53
|
-
from .protocol import (
|
|
54
|
-
XcomPackage,
|
|
55
|
-
)
|
|
56
|
-
from .values import (
|
|
57
|
-
XcomValues,
|
|
58
|
-
XcomValuesItem,
|
|
59
|
-
)
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .shared.studer_interfaces_async import AsyncStuderApi
|
|
12
|
+
from .shared.studer_interfaces_sync import StuderApi
|
|
13
|
+
from .shared.studer_types import StuderAccess, StuderDataType, StuderDiscoveredDevice, StuderTarget, StuderUserLevel
|
|
14
|
+
from .shared.studer_dataset import StuderDatapoint
|
|
15
|
+
from .const import START_TIMEOUT, STOP_TIMEOUT, REQ_TIMEOUT, REQ_RETRIES, REQ_BURST_PERIOD
|
|
16
|
+
from .const import ScomAddress, XcomAggregationType, ScomFrameFlag, ScomObjType, ScomObjId, ScomServiceId, ScomQspId
|
|
17
|
+
from .const import XcomApiReadException, XcomApiWriteException, XcomApiUnpackException, XcomApiTimeoutException, XcomApiResponseIsError, XcomParamException
|
|
18
|
+
from .const import safe_len
|
|
19
|
+
from .data import AsyncReader, SyncReader, XcomData, XcomDataMessageRsp, MULTI_INFO_REQ_MAX, read_bytes
|
|
20
|
+
from .families import XcomDeviceFamilies
|
|
21
|
+
from .messages import XcomMessage, XcomMessageSet
|
|
22
|
+
from .protocol import XcomFrame, XcomHeader, XcomPackage
|
|
23
|
+
from .values import XcomValueSet, XcomValueItem
|
|
60
24
|
|
|
61
25
|
|
|
62
26
|
_LOGGER = logging.getLogger(__name__)
|
|
@@ -65,7 +29,7 @@ _LOGGER = logging.getLogger(__name__)
|
|
|
65
29
|
##
|
|
66
30
|
## Base cass abstracting Xcom Api
|
|
67
31
|
##
|
|
68
|
-
class AsyncXcomApiBase:
|
|
32
|
+
class AsyncXcomApiBase(AsyncStuderApi):
|
|
69
33
|
|
|
70
34
|
def __init__(self):
|
|
71
35
|
"""
|
|
@@ -77,6 +41,8 @@ class AsyncXcomApiBase:
|
|
|
77
41
|
self._request_id = 0
|
|
78
42
|
self._sendRequestLock = asyncio.Lock() # to make sure _sendRequest_inner is never called concurrently
|
|
79
43
|
|
|
44
|
+
self._families = XcomDeviceFamilies.get_instance() # singleton instance
|
|
45
|
+
|
|
80
46
|
# Cached values
|
|
81
47
|
self._msg_set = None
|
|
82
48
|
self._latest_frame_flags: int = None # most recent received frame flags, used for various status flags
|
|
@@ -86,7 +52,7 @@ class AsyncXcomApiBase:
|
|
|
86
52
|
self._diag_durations = {}
|
|
87
53
|
|
|
88
54
|
|
|
89
|
-
async def start(self
|
|
55
|
+
async def start(self) -> bool:
|
|
90
56
|
"""
|
|
91
57
|
Start the Xcom Server and listening to the Xcom client.
|
|
92
58
|
"""
|
|
@@ -179,14 +145,14 @@ class AsyncXcomApiBase:
|
|
|
179
145
|
if response is not None:
|
|
180
146
|
# Unpack the response value
|
|
181
147
|
try:
|
|
182
|
-
return XcomData.unpack(response.frame_data.service_data.property_data,
|
|
148
|
+
return XcomData.unpack(response.frame_data.service_data.property_data, StuderDataType.GUID)
|
|
183
149
|
|
|
184
150
|
except Exception as e:
|
|
185
151
|
msg = f"Failed to unpack response package for GUID:{request.header.dst_addr}, data={response.frame_data.service_data.property_data.hex()}: {e}"
|
|
186
152
|
raise XcomApiUnpackException(msg) from None
|
|
187
153
|
|
|
188
154
|
|
|
189
|
-
async def request_value(self, parameter:
|
|
155
|
+
async def request_value(self, parameter: StuderDatapoint, device: StuderDiscoveredDevice|int|str=None, retries = None, timeout = None, verbose=False):
|
|
190
156
|
"""
|
|
191
157
|
Request a param or info.
|
|
192
158
|
Returns None if not connected, otherwise returns the requested value
|
|
@@ -198,19 +164,24 @@ class AsyncXcomApiBase:
|
|
|
198
164
|
"""
|
|
199
165
|
|
|
200
166
|
# Sanity check
|
|
201
|
-
if parameter.target !=
|
|
167
|
+
if parameter.target != StuderTarget.STANDARD:
|
|
202
168
|
raise XcomParamException(f"Invalid datapoint passed to request_value; must have target STANDARD. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
203
|
-
|
|
204
|
-
# Check/convert input parameters
|
|
205
|
-
if type(dstAddr) is str:
|
|
206
|
-
dstAddr = XcomDeviceFamilies.get_addr_by_code(dstAddr)
|
|
207
169
|
|
|
170
|
+
if isinstance(device, StuderDiscoveredDevice):
|
|
171
|
+
dstAddr = device.address
|
|
172
|
+
elif isinstance(device, int):
|
|
173
|
+
dstAddr = device
|
|
174
|
+
elif isinstance(device, str):
|
|
175
|
+
dstAddr = self._families.get_addr_by_code(code=device)
|
|
176
|
+
else:
|
|
177
|
+
raise StuderParamException(f"Parameter 'device' must be a XcomDiscoverdDevice, device address or a device code in call to request_value")
|
|
178
|
+
|
|
208
179
|
# Compose the request and send it
|
|
209
180
|
request: XcomPackage = XcomPackage.gen_package(
|
|
210
181
|
service_id = ScomServiceId.READ,
|
|
211
|
-
object_type = ScomObjType.PARAMETER if parameter.
|
|
182
|
+
object_type = ScomObjType.PARAMETER if parameter.access in [StuderAccess.WRITE, StuderAccess.READ_WRITE] else ScomObjType.INFO,
|
|
212
183
|
object_id = parameter.nr,
|
|
213
|
-
property_id = ScomQspId.UNSAVED_VALUE if parameter.
|
|
184
|
+
property_id = ScomQspId.UNSAVED_VALUE if parameter.access in [StuderAccess.WRITE, StuderAccess.READ_WRITE] else ScomQspId.VALUE,
|
|
214
185
|
property_data = XcomData.NONE,
|
|
215
186
|
dst_addr = dstAddr
|
|
216
187
|
)
|
|
@@ -219,14 +190,14 @@ class AsyncXcomApiBase:
|
|
|
219
190
|
if response is not None:
|
|
220
191
|
# Unpack the response value
|
|
221
192
|
try:
|
|
222
|
-
return XcomData.unpack(response.frame_data.service_data.property_data, parameter.
|
|
193
|
+
return XcomData.unpack(response.frame_data.service_data.property_data, parameter.data_type)
|
|
223
194
|
|
|
224
195
|
except Exception as e:
|
|
225
196
|
msg = f"Failed to unpack response package for {parameter.nr}:{dstAddr}, data={response.frame_data.service_data.property_data.hex()}: {e}"
|
|
226
197
|
raise XcomApiUnpackException(msg) from None
|
|
227
198
|
|
|
228
199
|
|
|
229
|
-
async def request_virtual(self, parameter:
|
|
200
|
+
async def request_virtual(self, parameter: StuderDatapoint, device: StuderDiscoveredDevice|int|str=None, retries = None, timeout = None, verbose=False):
|
|
230
201
|
"""
|
|
231
202
|
Request a virtual param or info.
|
|
232
203
|
Returns None if not connected, otherwise returns the requested value
|
|
@@ -240,9 +211,18 @@ class AsyncXcomApiBase:
|
|
|
240
211
|
if not self.connected:
|
|
241
212
|
return None
|
|
242
213
|
|
|
243
|
-
if parameter.target !=
|
|
214
|
+
if parameter.target != StuderTarget.VIRTUAL:
|
|
244
215
|
raise XcomParamException(f"Invalid datapoint passed to request_value; must have target VIRTUAL. Violated by datapoint '{parameter.name}' ({parameter.nr})")
|
|
245
216
|
|
|
217
|
+
if isinstance(device, StuderDiscoveredDevice):
|
|
218
|
+
dstAddr = device.address
|
|
219
|
+
elif isinstance(device, int):
|
|
220
|
+
dstAddr = device
|
|
221
|
+
elif isinstance(device, str):
|
|
222
|
+
dstAddr = self._families.get_addr_by_code(code=device)
|
|
223
|
+
else:
|
|
224
|
+
raise StuderParamException(f"Parameter 'device' must be a XcomDiscoverdDevice, device address or a device code in call to request_value")
|
|
225
|
+
|
|
246
226
|
# Virtual request
|
|
247
227
|
match parameter.nr:
|
|
248
228
|
case 99000: return self.is_message_pending
|
|
@@ -266,7 +246,7 @@ class AsyncXcomApiBase:
|
|
|
266
246
|
return None
|
|
267
247
|
|
|
268
248
|
|
|
269
|
-
async def request_infos(self, request_data:
|
|
249
|
+
async def request_infos(self, request_data: XcomValueSet, retries = None, timeout = None, verbose=False) -> XcomValueSet:
|
|
270
250
|
"""
|
|
271
251
|
Request multiple infos in one call.
|
|
272
252
|
Per info you can indicate what device to get it from, or to get Average or Sum of multiple devices
|
|
@@ -284,10 +264,10 @@ class AsyncXcomApiBase:
|
|
|
284
264
|
|
|
285
265
|
# Sanity check
|
|
286
266
|
for item in request_data.items:
|
|
287
|
-
if item.datapoint.target !=
|
|
267
|
+
if item.datapoint.target != StuderTarget.STANDARD:
|
|
288
268
|
raise XcomParamException(f"Invalid datapoint passed to request_infos; must have target STANDARD. Violated by datapoint '{item.datapoint.name}' ({item.datapoint.nr})")
|
|
289
269
|
|
|
290
|
-
if item.datapoint.
|
|
270
|
+
if item.datapoint.userlevel_r != StuderUserLevel.INFO:
|
|
291
271
|
raise XcomParamException(f"Invalid datapoint passed to request_infos; must have type INFO. Violated by datapoint '{item.datapoint.name}' ({item.datapoint.nr})")
|
|
292
272
|
|
|
293
273
|
if item.aggregation_type not in XcomAggregationType:
|
|
@@ -307,14 +287,14 @@ class AsyncXcomApiBase:
|
|
|
307
287
|
if response is not None:
|
|
308
288
|
try:
|
|
309
289
|
# Unpack the response value
|
|
310
|
-
return
|
|
290
|
+
return XcomValueSet.unpack_response(response.frame_data.service_data.property_data, request_data)
|
|
311
291
|
|
|
312
292
|
except Exception as e:
|
|
313
293
|
msg = f"Failed to unpack response package for multi-info request, data={response.frame_data.service_data.property_data.hex()}: {e}"
|
|
314
294
|
raise XcomApiUnpackException(msg) from None
|
|
315
295
|
|
|
316
296
|
|
|
317
|
-
async def request_values(self, request_data:
|
|
297
|
+
async def request_values(self, request_data: XcomValueSet, retries = None, timeout = None, verbose=False) -> XcomValueSet:
|
|
318
298
|
"""
|
|
319
299
|
Request multiple infos, params or virtuals in one call.
|
|
320
300
|
Can only retrieve actual device values, NOT the average or sum over multiple devices.
|
|
@@ -331,28 +311,29 @@ class AsyncXcomApiBase:
|
|
|
331
311
|
XcomApiResponseIsError
|
|
332
312
|
"""
|
|
333
313
|
|
|
334
|
-
# Sort out which
|
|
335
|
-
req_virtuals: list[
|
|
336
|
-
req_singles: list[
|
|
337
|
-
req_multi_items: list[
|
|
338
|
-
req_multis: list[
|
|
314
|
+
# Sort out which XcomValueSet can be done via multi request_values and which must be done via single request_value
|
|
315
|
+
req_virtuals: list[XcomValueItem] = []
|
|
316
|
+
req_singles: list[XcomValueItem] = []
|
|
317
|
+
req_multi_items: list[XcomValueItem] = []
|
|
318
|
+
req_multis: list[XcomValueSet] = []
|
|
339
319
|
idx_last = safe_len(request_data.items)-1
|
|
340
320
|
|
|
341
321
|
for idx,item in enumerate(request_data.items):
|
|
342
322
|
|
|
343
323
|
match item.datapoint.target:
|
|
344
|
-
case
|
|
324
|
+
case StuderTarget.VIRTUAL:
|
|
345
325
|
if item.address is not None:
|
|
346
326
|
# Needs to be done via an individual request_virtual call
|
|
347
327
|
req_virtuals.append(item)
|
|
348
328
|
|
|
349
329
|
else:
|
|
350
|
-
raise XcomParamException(f"Invalid
|
|
330
|
+
raise XcomParamException(f"Invalid XcomValueItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
351
331
|
|
|
352
|
-
case
|
|
332
|
+
case StuderTarget.STANDARD:
|
|
353
333
|
# Standard datapoints are handled depending on category and aggregation type
|
|
354
|
-
match item.datapoint.
|
|
355
|
-
case
|
|
334
|
+
match item.datapoint.access:
|
|
335
|
+
case StuderAccess.READ:
|
|
336
|
+
# Info
|
|
356
337
|
if item.aggregation_type is not None and item.aggregation_type in range(XcomAggregationType.DEVICE1, XcomAggregationType.DEVICE15+1):
|
|
357
338
|
# Can be combined with other infos in a request_values call
|
|
358
339
|
req_multi_items.append(item)
|
|
@@ -362,25 +343,26 @@ class AsyncXcomApiBase:
|
|
|
362
343
|
req_singles.append(item)
|
|
363
344
|
|
|
364
345
|
else:
|
|
365
|
-
raise XcomParamException(f"Invalid
|
|
346
|
+
raise XcomParamException(f"Invalid XcomValueItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
366
347
|
|
|
367
|
-
case
|
|
348
|
+
case StuderAccess.WRITE | StuderAccess.READ_WRITE:
|
|
349
|
+
# Parameter
|
|
368
350
|
if item.address is not None:
|
|
369
351
|
# Needs to be done via an individual request_value call
|
|
370
352
|
req_singles.append(item)
|
|
371
353
|
|
|
372
354
|
else:
|
|
373
|
-
raise XcomParamException(f"Invalid
|
|
355
|
+
raise XcomParamException(f"Invalid XcomValueItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
|
|
374
356
|
|
|
375
357
|
if (len(req_multi_items) == MULTI_INFO_REQ_MAX) or \
|
|
376
358
|
(len(req_multi_items) > 0 and idx == idx_last):
|
|
377
359
|
|
|
378
360
|
# Start a new multi-items if current one if full or on last item of enumerate
|
|
379
|
-
req_multis.append(
|
|
361
|
+
req_multis.append( XcomValueSet(items=req_multi_items) )
|
|
380
362
|
req_multi_items = []
|
|
381
363
|
|
|
382
364
|
# Now perform all the multi request_values requests
|
|
383
|
-
result_items: list[
|
|
365
|
+
result_items: list[XcomValueSet] = []
|
|
384
366
|
burst_start = datetime.now()
|
|
385
367
|
|
|
386
368
|
for req_multi in req_multis:
|
|
@@ -396,7 +378,7 @@ class AsyncXcomApiBase:
|
|
|
396
378
|
# Fail; do not retry as single request_value also expected to give timeout
|
|
397
379
|
value = None
|
|
398
380
|
error = str(tex)
|
|
399
|
-
result_items.extend( [
|
|
381
|
+
result_items.extend( [XcomValueItem(datapoint=req.datapoint, device=req.code, aggregation_type=req.aggregation_type, value=value, error=error) for req in req_multi.items] )
|
|
400
382
|
|
|
401
383
|
except Exception as ex:
|
|
402
384
|
_LOGGER.debug(f"Failed to retrieve infos via single call; will retry retrieve one-by-one. {ex}")
|
|
@@ -424,10 +406,9 @@ class AsyncXcomApiBase:
|
|
|
424
406
|
_LOGGER.debug(f"Failed to retrieve info or param {req_single.datapoint.nr}:{req_single.address}; {error}")
|
|
425
407
|
|
|
426
408
|
# Add to results
|
|
427
|
-
rsp_single =
|
|
409
|
+
rsp_single = XcomValueItem(
|
|
428
410
|
datapoint = req_single.datapoint,
|
|
429
|
-
|
|
430
|
-
address = req_single.address,
|
|
411
|
+
device = req_single.code,
|
|
431
412
|
aggregation_type=req_single.aggregation_type,
|
|
432
413
|
value = value,
|
|
433
414
|
error = error,
|
|
@@ -454,10 +435,9 @@ class AsyncXcomApiBase:
|
|
|
454
435
|
_LOGGER.debug(f"Failed to retrieve virtual info or param {req_virtual.datapoint.nr}:{req_virtual.address}; {error}")
|
|
455
436
|
|
|
456
437
|
# Add to results
|
|
457
|
-
rsp_virtual =
|
|
438
|
+
rsp_virtual = XcomValueItem(
|
|
458
439
|
datapoint = req_virtual.datapoint,
|
|
459
|
-
|
|
460
|
-
address = req_virtual.address,
|
|
440
|
+
device = req_virtual.code,
|
|
461
441
|
aggregation_type=req_virtual.aggregation_type,
|
|
462
442
|
value = value,
|
|
463
443
|
error = error,
|
|
@@ -470,11 +450,11 @@ class AsyncXcomApiBase:
|
|
|
470
450
|
await asyncio.sleep(1)
|
|
471
451
|
burst_start = datetime.now()
|
|
472
452
|
|
|
473
|
-
# Return all reponse items as one
|
|
474
|
-
return
|
|
453
|
+
# Return all reponse items as one XcomValueSet object
|
|
454
|
+
return XcomValueSet(result_items)
|
|
475
455
|
|
|
476
456
|
|
|
477
|
-
async def update_value(self, parameter:
|
|
457
|
+
async def update_value(self, parameter: StuderDatapoint, value: Any, device: StuderDiscoveredDevice|int|str=None, retries = None, timeout = None, verbose=False):
|
|
478
458
|
"""
|
|
479
459
|
Update a param
|
|
480
460
|
Returns None if not connected, otherwise returns True on success
|
|
@@ -485,14 +465,20 @@ class AsyncXcomApiBase:
|
|
|
485
465
|
XcomApiResponseIsError
|
|
486
466
|
"""
|
|
487
467
|
# Sanity check: the parameter/datapoint must have category == XcomDatapointType.PARAMETER
|
|
488
|
-
if parameter.
|
|
489
|
-
_LOGGER.warning(f"Ignoring attempt to update readonly
|
|
468
|
+
if parameter.access not in [StuderAccess.WRITE, StuderAccess.READ_WRITE]:
|
|
469
|
+
_LOGGER.warning(f"Ignoring attempt to update readonly value {parameter}")
|
|
490
470
|
return None
|
|
491
471
|
|
|
492
|
-
if
|
|
493
|
-
dstAddr =
|
|
472
|
+
if isinstance(device, StuderDiscoveredDevice):
|
|
473
|
+
dstAddr = device.address
|
|
474
|
+
elif isinstance(device, int):
|
|
475
|
+
dstAddr = device
|
|
476
|
+
elif isinstance(device, str):
|
|
477
|
+
dstAddr = self._families.get_addr_by_code(code=device)
|
|
478
|
+
else:
|
|
479
|
+
raise StuderParamException(f"Parameter 'device' must be a XcomDiscoverdDevice, device address or a device code in call to request_value")
|
|
494
480
|
|
|
495
|
-
_LOGGER.debug(f"Update value {parameter} on
|
|
481
|
+
_LOGGER.debug(f"Update value {parameter} on address {dstAddr}")
|
|
496
482
|
|
|
497
483
|
# Compose the request and send it
|
|
498
484
|
request: XcomPackage = XcomPackage.gen_package(
|
|
@@ -500,7 +486,7 @@ class AsyncXcomApiBase:
|
|
|
500
486
|
object_type = ScomObjType.PARAMETER,
|
|
501
487
|
object_id = parameter.nr,
|
|
502
488
|
property_id = ScomQspId.UNSAVED_VALUE,
|
|
503
|
-
property_data = XcomData.pack(value, parameter.
|
|
489
|
+
property_data = XcomData.pack(value, parameter.data_type),
|
|
504
490
|
dst_addr = dstAddr
|
|
505
491
|
)
|
|
506
492
|
|
|
@@ -535,7 +521,7 @@ class AsyncXcomApiBase:
|
|
|
535
521
|
|
|
536
522
|
# Make sure we have access to the message_set
|
|
537
523
|
if self._msg_set is None:
|
|
538
|
-
self._msg_set = await
|
|
524
|
+
self._msg_set = await XcomMessageSet.async_get_instance()
|
|
539
525
|
|
|
540
526
|
# Compose the request and send it
|
|
541
527
|
request: XcomPackage = XcomPackage.gen_package(
|
|
@@ -689,6 +675,39 @@ class AsyncXcomApiBase:
|
|
|
689
675
|
return self._request_id
|
|
690
676
|
|
|
691
677
|
|
|
678
|
+
async def _parse_package(self, f: BufferedReader, timeout:float=REQ_TIMEOUT, verbose=False) -> XcomPackage:
|
|
679
|
+
# package sometimes starts with 0xff
|
|
680
|
+
skipped = bytearray(b'')
|
|
681
|
+
ts_end = datetime.now() + timedelta(seconds=timeout)
|
|
682
|
+
|
|
683
|
+
while datetime.now() < ts_end:
|
|
684
|
+
sb = await read_bytes(f, 1)
|
|
685
|
+
if sb == XcomPackage.start_byte:
|
|
686
|
+
break
|
|
687
|
+
|
|
688
|
+
skipped.extend(sb)
|
|
689
|
+
|
|
690
|
+
if verbose and len(skipped) > 0:
|
|
691
|
+
_LOGGER.debug(f"skip {len(skipped)} bytes until start-byte ({binascii.hexlify(skipped).decode('ascii')})")
|
|
692
|
+
|
|
693
|
+
h_raw = await read_bytes(f, XcomHeader.length)
|
|
694
|
+
h_chk = await read_bytes(f, 2)
|
|
695
|
+
assert XcomPackage.checksum(h_raw) == h_chk
|
|
696
|
+
header = XcomHeader.parse_bytes(h_raw)
|
|
697
|
+
|
|
698
|
+
f_raw = await read_bytes(f, header.data_length)
|
|
699
|
+
f_chk = await read_bytes(f, 2)
|
|
700
|
+
assert XcomPackage.checksum(f_raw) == f_chk
|
|
701
|
+
frame = XcomFrame.parse_bytes(f_raw)
|
|
702
|
+
|
|
703
|
+
return XcomPackage(header, frame)
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
async def _parse_package_bytes(self, buf: bytes, timeout:float=REQ_TIMEOUT, verbose=False) -> XcomPackage:
|
|
707
|
+
reader = AsyncReader(buf)
|
|
708
|
+
return await self._parse_package(reader, timeout, verbose)
|
|
709
|
+
|
|
710
|
+
|
|
692
711
|
async def _add_diagnostics(self, retries: int = None, duration: timedelta = None):
|
|
693
712
|
if retries is not None:
|
|
694
713
|
if retries not in self._diag_retries:
|