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