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.
Files changed (69) hide show
  1. {pystuderxcom-3.4.7/src/pystuderxcom.egg-info → pystuderxcom-3.6.0}/PKG-INFO +4 -4
  2. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/README.md +3 -3
  3. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/pyproject.toml +1 -1
  4. pystuderxcom-3.6.0/src/pystuderxcom/__init__.py +31 -0
  5. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_base_async.py +122 -103
  6. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_base_sync.py +122 -103
  7. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_serial.py +4 -10
  8. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_tcp.py +2 -33
  9. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/api_udp.py +4 -36
  10. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/const.py +15 -65
  11. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/data.py +42 -55
  12. pystuderxcom-3.6.0/src/pystuderxcom/datapoints.py +301 -0
  13. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/discover_async.py +70 -93
  14. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/discover_sync.py +71 -94
  15. pystuderxcom-3.6.0/src/pystuderxcom/families.py +356 -0
  16. pystuderxcom-3.6.0/src/pystuderxcom/messages.py +177 -0
  17. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/protocol.py +4 -2
  18. pystuderxcom-3.6.0/src/pystuderxcom/shared/helpers.py +106 -0
  19. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_dataset.py +208 -0
  20. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_families.py +68 -0
  21. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_interfaces_async.py +101 -0
  22. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_interfaces_sync.py +103 -0
  23. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_messageset.py +59 -0
  24. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_types.py +123 -0
  25. pystuderxcom-3.6.0/src/pystuderxcom/shared/studer_valueset.py +40 -0
  26. pystuderxcom-3.6.0/src/pystuderxcom/values.py +142 -0
  27. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0/src/pystuderxcom.egg-info}/PKG-INFO +4 -4
  28. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/SOURCES.txt +10 -6
  29. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_base_async.py +86 -86
  30. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_base_sync.py +86 -86
  31. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_serial_async.py +1 -5
  32. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_serial_sync.py +1 -5
  33. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_tcp_async.py +1 -5
  34. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_tcp_sync.py +1 -5
  35. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_udp_async.py +1 -5
  36. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_api_udp_sync.py +1 -5
  37. pystuderxcom-3.6.0/tests/test_const.py +69 -0
  38. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_data.py +10 -9
  39. pystuderxcom-3.6.0/tests/test_datapoints.py +189 -0
  40. pystuderxcom-3.6.0/tests/test_discover_async.py +215 -0
  41. pystuderxcom-3.6.0/tests/test_discover_sync.py +217 -0
  42. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_families.py +23 -18
  43. pystuderxcom-3.6.0/tests/test_messages.py +64 -0
  44. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_package_async.py +6 -5
  45. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_package_sync.py +6 -5
  46. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/tests/test_values.py +17 -18
  47. pystuderxcom-3.4.7/src/pystuderxcom/__init__.py +0 -26
  48. pystuderxcom-3.4.7/src/pystuderxcom/datapoints.py +0 -162
  49. pystuderxcom-3.4.7/src/pystuderxcom/factory_async.py +0 -164
  50. pystuderxcom-3.4.7/src/pystuderxcom/factory_sync.py +0 -166
  51. pystuderxcom-3.4.7/src/pystuderxcom/families.py +0 -278
  52. pystuderxcom-3.4.7/src/pystuderxcom/messages.py +0 -97
  53. pystuderxcom-3.4.7/src/pystuderxcom/values.py +0 -139
  54. pystuderxcom-3.4.7/tests/test_const.py +0 -104
  55. pystuderxcom-3.4.7/tests/test_datapoints_async.py +0 -147
  56. pystuderxcom-3.4.7/tests/test_datapoints_sync.py +0 -149
  57. pystuderxcom-3.4.7/tests/test_discover_async.py +0 -208
  58. pystuderxcom-3.4.7/tests/test_discover_sync.py +0 -210
  59. pystuderxcom-3.4.7/tests/test_messageset_async.py +0 -51
  60. pystuderxcom-3.4.7/tests/test_messageset_sync.py +0 -53
  61. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/LICENSE +0 -0
  62. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/setup.cfg +0 -0
  63. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/datapoints_120v.json +0 -0
  64. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/datapoints_240v.json +0 -0
  65. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/datapoints_xcom.json +0 -0
  66. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom/messages_en.json +0 -0
  67. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/dependency_links.txt +0 -0
  68. {pystuderxcom-3.4.7 → pystuderxcom-3.6.0}/src/pystuderxcom.egg-info/requires.txt +0 -0
  69. {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.4.7
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](https://img.shields.io/github/license/toreamun/amshan-homeassistant?style=for-the-badge)](LICENSE)
25
+ [![license](https://img.shields.io/github/license/ankohanse/pystuderxcom?style=for-the-badge)](LICENSE)
26
26
  [![buy_me_a_coffee](https://img.shields.io/badge/If%20you%20like%20it-Buy%20me%20a%20coffee-yellow.svg?style=for-the-badge)](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/) *-> Software and Updates -> Communication protocol Xcom-232i*
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 = 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")
@@ -1,4 +1,4 @@
1
- [![license](https://img.shields.io/github/license/toreamun/amshan-homeassistant?style=for-the-badge)](LICENSE)
1
+ [![license](https://img.shields.io/github/license/ankohanse/pystuderxcom?style=for-the-badge)](LICENSE)
2
2
  [![buy_me_a_coffee](https://img.shields.io/badge/If%20you%20like%20it-Buy%20me%20a%20coffee-yellow.svg?style=for-the-badge)](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/) *-> Software and Updates -> Communication protocol Xcom-232i*
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 = 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.4.7"
3
+ version = "3.6.0"
4
4
  authors = [
5
5
  { name="Anko Hanse", email="anko_hanse@hotmail.com" },
6
6
  ]
@@ -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
- 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 .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, timeout=START_TIMEOUT) -> bool:
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, XcomFormat.GUID)
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: XcomDatapoint, dstAddr = 100, retries = None, timeout = None, verbose=False):
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 != XcomTarget.STANDARD:
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.category == XcomCategory.PARAMETER else ScomObjType.INFO,
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.category == XcomCategory.PARAMETER else ScomQspId.VALUE,
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.format)
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: XcomDatapoint, dstAddr = 100, retries = None, timeout = None, verbose=False):
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 != XcomTarget.VIRTUAL:
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: XcomValues, retries = None, timeout = None, verbose=False) -> XcomValues:
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 != XcomTarget.STANDARD:
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.category != XcomCategory.INFO:
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 XcomValues.unpack_response(response.frame_data.service_data.property_data, request_data)
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: XcomValues, retries = None, timeout = None, verbose=False) -> XcomValues:
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 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] = []
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 XcomTarget.VIRTUAL:
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 XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
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 XcomTarget.STANDARD:
332
+ case StuderTarget.STANDARD:
353
333
  # Standard datapoints are handled depending on category and aggregation type
354
- match item.datapoint.category:
355
- case XcomCategory.INFO:
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 XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
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 XcomCategory.PARAMETER:
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 XcomValuesItem passed to request_values; violated by code='{item.code}', address={item.address}, aggregation_type={item.aggregation_type}")
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( XcomValues(items=req_multi_items) )
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[XcomValues] = []
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( [XcomValuesItem(req.datapoint, code=req.code, address=req.address, aggregation_type=req.aggregation_type, value=value, error=error) for req in req_multi.items] )
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 = XcomValuesItem(
409
+ rsp_single = XcomValueItem(
428
410
  datapoint = req_single.datapoint,
429
- code = req_single.code,
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 = XcomValuesItem(
438
+ rsp_virtual = XcomValueItem(
458
439
  datapoint = req_virtual.datapoint,
459
- code = req_virtual.code,
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 XcomValues object
474
- return XcomValues(result_items)
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: XcomDatapoint, value, dstAddr = 100, retries = None, timeout = None, verbose=False):
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.category != XcomCategory.PARAMETER:
489
- _LOGGER.warning(f"Ignoring attempt to update readonly infos value {parameter}")
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 type(dstAddr) is str:
493
- dstAddr = XcomDeviceFamilies.get_addr_by_code(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 addr {dstAddr}")
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.format),
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 AsyncXcomFactory.create_messageset()
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: