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.
Files changed (67) hide show
  1. {pystuderxcom-3.5.0/src/pystuderxcom.egg-info → pystuderxcom-3.6.1}/PKG-INFO +2 -2
  2. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/README.md +1 -1
  3. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/pyproject.toml +1 -1
  4. pystuderxcom-3.6.1/src/pystuderxcom/__init__.py +32 -0
  5. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/api_base_async.py +127 -103
  6. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/api_base_sync.py +129 -104
  7. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/api_serial.py +4 -10
  8. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/api_tcp.py +2 -33
  9. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/api_udp.py +4 -36
  10. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/const.py +18 -65
  11. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/data.py +42 -55
  12. pystuderxcom-3.6.1/src/pystuderxcom/datapoints.py +301 -0
  13. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/discover_async.py +48 -77
  14. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/discover_sync.py +49 -78
  15. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/families.py +132 -101
  16. pystuderxcom-3.6.1/src/pystuderxcom/messages.py +177 -0
  17. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/protocol.py +4 -2
  18. pystuderxcom-3.6.1/src/pystuderxcom/shared/helpers.py +106 -0
  19. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_dataset.py +208 -0
  20. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_families.py +68 -0
  21. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_interfaces_async.py +101 -0
  22. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_interfaces_sync.py +103 -0
  23. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_messageset.py +59 -0
  24. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_types.py +123 -0
  25. pystuderxcom-3.6.1/src/pystuderxcom/shared/studer_valueset.py +53 -0
  26. pystuderxcom-3.6.1/src/pystuderxcom/values.py +140 -0
  27. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1/src/pystuderxcom.egg-info}/PKG-INFO +2 -2
  28. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom.egg-info/SOURCES.txt +10 -6
  29. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_base_async.py +86 -86
  30. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_base_sync.py +86 -86
  31. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_serial_async.py +1 -5
  32. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_serial_sync.py +1 -5
  33. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_tcp_async.py +1 -5
  34. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_tcp_sync.py +1 -5
  35. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_udp_async.py +1 -5
  36. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_api_udp_sync.py +1 -5
  37. pystuderxcom-3.6.1/tests/test_const.py +69 -0
  38. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_data.py +10 -9
  39. pystuderxcom-3.6.1/tests/test_datapoints.py +189 -0
  40. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_discover_async.py +72 -71
  41. pystuderxcom-3.6.1/tests/test_discover_sync.py +217 -0
  42. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_families.py +23 -18
  43. pystuderxcom-3.6.1/tests/test_messages.py +64 -0
  44. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_package_async.py +6 -5
  45. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_package_sync.py +6 -5
  46. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/tests/test_values.py +17 -18
  47. pystuderxcom-3.5.0/src/pystuderxcom/__init__.py +0 -26
  48. pystuderxcom-3.5.0/src/pystuderxcom/datapoints.py +0 -162
  49. pystuderxcom-3.5.0/src/pystuderxcom/factory_async.py +0 -164
  50. pystuderxcom-3.5.0/src/pystuderxcom/factory_sync.py +0 -166
  51. pystuderxcom-3.5.0/src/pystuderxcom/messages.py +0 -97
  52. pystuderxcom-3.5.0/src/pystuderxcom/values.py +0 -139
  53. pystuderxcom-3.5.0/tests/test_const.py +0 -104
  54. pystuderxcom-3.5.0/tests/test_datapoints_async.py +0 -147
  55. pystuderxcom-3.5.0/tests/test_datapoints_sync.py +0 -149
  56. pystuderxcom-3.5.0/tests/test_discover_sync.py +0 -210
  57. pystuderxcom-3.5.0/tests/test_messageset_async.py +0 -51
  58. pystuderxcom-3.5.0/tests/test_messageset_sync.py +0 -53
  59. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/LICENSE +0 -0
  60. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/setup.cfg +0 -0
  61. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/datapoints_120v.json +0 -0
  62. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/datapoints_240v.json +0 -0
  63. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/datapoints_xcom.json +0 -0
  64. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom/messages_en.json +0 -0
  65. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom.egg-info/dependency_links.txt +0 -0
  66. {pystuderxcom-3.5.0 → pystuderxcom-3.6.1}/src/pystuderxcom.egg-info/requires.txt +0 -0
  67. {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.5.0
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 = XcomFactory.create_dataset(XcomVoltage.AC240, XcomVoltage.DC48)
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 = XcomFactory.create_dataset(XcomVoltage.AC240, XcomVoltage.DC48)
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")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pystuderxcom"
3
- version = "3.5.0"
3
+ version = "3.6.1"
4
4
  authors = [
5
5
  { name="Anko Hanse", email="anko_hanse@hotmail.com" },
6
6
  ]
@@ -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
- from .const import (
10
- START_TIMEOUT,
11
- STOP_TIMEOUT,
12
- REQ_TIMEOUT,
13
- REQ_RETRIES,
14
- REQ_BURST_PERIOD,
15
- ScomAddress,
16
- XcomFormat,
17
- XcomTarget,
18
- XcomCategory,
19
- XcomAggregationType,
20
- ScomFrameFlag,
21
- ScomObjType,
22
- ScomObjId,
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 .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, timeout=START_TIMEOUT) -> bool:
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, XcomFormat.GUID)
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: XcomDatapoint, dstAddr = 100, retries = None, timeout = None, verbose=False):
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 != XcomTarget.STANDARD:
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.category == XcomCategory.PARAMETER else ScomObjType.INFO,
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.category == XcomCategory.PARAMETER else ScomQspId.VALUE,
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.format)
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: XcomDatapoint, dstAddr = 100, retries = None, timeout = None, verbose=False):
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 != XcomTarget.VIRTUAL:
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: XcomValues, retries = None, timeout = None, verbose=False) -> XcomValues:
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 != XcomTarget.STANDARD:
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.category != XcomCategory.INFO:
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 XcomValues.unpack_response(response.frame_data.service_data.property_data, request_data)
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: XcomValues, retries = None, timeout = None, verbose=False) -> XcomValues:
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 XcomValues can be done via multi request_values and which must be done via single request_value
335
- req_virtuals: list[XcomValuesItem] = []
336
- req_singles: list[XcomValuesItem] = []
337
- req_multi_items: list[XcomValuesItem] = []
338
- req_multis: list[XcomValues] = []
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 XcomTarget.VIRTUAL:
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 XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
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 XcomTarget.STANDARD:
337
+ case StuderTarget.STANDARD:
353
338
  # Standard datapoints are handled depending on category and aggregation type
354
- match item.datapoint.category:
355
- case XcomCategory.INFO:
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 XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
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 XcomCategory.PARAMETER:
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 XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
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( XcomValues(items=req_multi_items) )
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[XcomValues] = []
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( [XcomValuesItem(req.datapoint, code=req.code, address=req.address, aggregation_type=req.aggregation_type, value=value, error=error) for req in req_multi.items] )
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 = XcomValuesItem(
414
+ rsp_single = XcomValueItem(
428
415
  datapoint = req_single.datapoint,
429
- code = req_single.code,
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 = XcomValuesItem(
443
+ rsp_virtual = XcomValueItem(
458
444
  datapoint = req_virtual.datapoint,
459
- code = req_virtual.code,
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 XcomValues object
474
- return XcomValues(result_items)
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: XcomDatapoint, value, dstAddr = 100, retries = None, timeout = None, verbose=False):
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.category != XcomCategory.PARAMETER:
489
- _LOGGER.warning(f"Ignoring attempt to update readonly infos value {parameter}")
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 type(dstAddr) is str:
493
- dstAddr = XcomDeviceFamilies.get_addr_by_code(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 addr {dstAddr}")
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.format),
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 AsyncXcomFactory.create_messageset()
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: