sgr-commhandler 0.4.dev20250512__py3-none-any.whl

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 (33) hide show
  1. sgr_commhandler/__init__.py +0 -0
  2. sgr_commhandler/api/__init__.py +18 -0
  3. sgr_commhandler/api/configuration_parameter.py +71 -0
  4. sgr_commhandler/api/data_point_api.py +316 -0
  5. sgr_commhandler/api/data_types.py +14 -0
  6. sgr_commhandler/api/device_api.py +226 -0
  7. sgr_commhandler/api/functional_profile_api.py +90 -0
  8. sgr_commhandler/device_builder.py +337 -0
  9. sgr_commhandler/driver/__init__.py +0 -0
  10. sgr_commhandler/driver/contact/__init__.py +7 -0
  11. sgr_commhandler/driver/contact/contact_interface_async.py +184 -0
  12. sgr_commhandler/driver/generic/__init__.py +7 -0
  13. sgr_commhandler/driver/generic/generic_interface_async.py +182 -0
  14. sgr_commhandler/driver/messaging/__init__.py +7 -0
  15. sgr_commhandler/driver/messaging/messaging_interface_async.py +197 -0
  16. sgr_commhandler/driver/modbus/__init__.py +7 -0
  17. sgr_commhandler/driver/modbus/modbus_client_async.py +403 -0
  18. sgr_commhandler/driver/modbus/modbus_interface_async.py +516 -0
  19. sgr_commhandler/driver/modbus/payload_decoder.py +108 -0
  20. sgr_commhandler/driver/modbus/shared_client.py +119 -0
  21. sgr_commhandler/driver/rest/__init__.py +7 -0
  22. sgr_commhandler/driver/rest/authentication.py +207 -0
  23. sgr_commhandler/driver/rest/restapi_interface_async.py +492 -0
  24. sgr_commhandler/utils/__init__.py +1 -0
  25. sgr_commhandler/utils/value_util.py +39 -0
  26. sgr_commhandler/validators/__init__.py +3 -0
  27. sgr_commhandler/validators/resolver.py +71 -0
  28. sgr_commhandler/validators/validator.py +195 -0
  29. sgr_commhandler-0.4.dev20250512.dist-info/LICENSE.md +14 -0
  30. sgr_commhandler-0.4.dev20250512.dist-info/METADATA +111 -0
  31. sgr_commhandler-0.4.dev20250512.dist-info/RECORD +33 -0
  32. sgr_commhandler-0.4.dev20250512.dist-info/WHEEL +5 -0
  33. sgr_commhandler-0.4.dev20250512.dist-info/top_level.txt +1 -0
File without changes
@@ -0,0 +1,18 @@
1
+ __all__ = [
2
+ "SGrBaseInterface",
3
+ "FunctionalProfile",
4
+ "DataPoint",
5
+ "DataPointProtocol",
6
+ "DataPointValidator",
7
+ "DeviceInformation",
8
+ "ConfigurationParameter",
9
+ ]
10
+
11
+ from sgr_commhandler.api.configuration_parameter import ConfigurationParameter
12
+ from sgr_commhandler.api.data_point_api import (
13
+ DataPoint,
14
+ DataPointProtocol,
15
+ DataPointValidator,
16
+ )
17
+ from sgr_commhandler.api.device_api import DeviceInformation, SGrBaseInterface
18
+ from sgr_commhandler.api.functional_profile_api import FunctionalProfile
@@ -0,0 +1,71 @@
1
+ from typing import Optional
2
+
3
+ from sgr_specification.v0.product import (
4
+ ConfigurationList,
5
+ ConfigurationListElement,
6
+ )
7
+
8
+
9
+ def build_configuration_parameters(params: Optional[ConfigurationList]) -> list['ConfigurationParameter']:
10
+ """
11
+ Constructs a configuration parameter list.
12
+
13
+ Parameters
14
+ ----------
15
+ params : Optional[ConfigurationList]
16
+ The configuration list of an EID
17
+
18
+ Returns
19
+ -------
20
+ list[ConfigurationParameter]
21
+ a list of configuration parameters
22
+ """
23
+ if params is None:
24
+ return []
25
+ return [
26
+ ConfigurationParameter(x) for x in params.configuration_list_element
27
+ ]
28
+
29
+
30
+ class ConfigurationParameter:
31
+ """
32
+ Implements an EID configuration parameter.
33
+ """
34
+
35
+ def __init__(self, parameter: ConfigurationListElement):
36
+ """
37
+ Constructs a configuration parameter.
38
+
39
+ Parameters
40
+ ----------
41
+ parameter : ConfigurationListElement
42
+ A configuration list element of the SGr specification
43
+ """
44
+ translation = parameter.configuration_description
45
+ self.label = translation[0].label
46
+ self.name = parameter.name
47
+ self.type = parameter.data_type
48
+ self.description = translation[0].text_element
49
+ self.default_value = parameter.default_value
50
+
51
+ def __str__(self) -> str:
52
+ """
53
+ Converts to string.
54
+
55
+ Returns
56
+ ----------
57
+ str
58
+ A string
59
+ """
60
+ return f'{self.label} - {self.name}'
61
+
62
+ def __repr__(self):
63
+ """
64
+ Gets an object description as string.
65
+
66
+ Returns
67
+ ----------
68
+ str
69
+ A string
70
+ """
71
+ return f'{self.label} - {self.name}'
@@ -0,0 +1,316 @@
1
+ from asyncio import run
2
+ from collections.abc import Callable
3
+ from typing import Any, Generic, Protocol, TypeVar
4
+
5
+ from sgr_specification.v0.generic import DataDirectionProduct, Units
6
+
7
+ from sgr_commhandler.api.data_types import DataTypes
8
+
9
+ """Defines a generic data type."""
10
+ T = TypeVar('T')
11
+
12
+
13
+ class DataPointValidator(Protocol):
14
+ """
15
+ Defines an interface for data point validators.
16
+ """
17
+
18
+ def validate(self, value: Any) -> bool:
19
+ """
20
+ Validates the compatibility of a value.
21
+
22
+ Parameters
23
+ ----------
24
+ value : Any
25
+ the value to validate
26
+
27
+ Returns
28
+ -------
29
+ bool
30
+ true if value is compatible, false otherwise
31
+ """
32
+ ...
33
+
34
+ def data_type(self) -> DataTypes:
35
+ """
36
+ Gets the validator's data type.
37
+
38
+ Returns
39
+ -------
40
+ DataTypes
41
+ the data type enumeration
42
+ """
43
+ ...
44
+
45
+ def options(self) -> list[Any]:
46
+ """
47
+ Gets the validator's options.
48
+
49
+ Returns
50
+ -------
51
+ list[Any]
52
+ the options
53
+ """
54
+ return []
55
+
56
+
57
+ class DataPointProtocol(Protocol):
58
+ """
59
+ Defines an interface for data point protocols.
60
+ """
61
+
62
+ async def set_val(self, value: Any):
63
+ """
64
+ Writes the data point value.
65
+
66
+ Parameters
67
+ ----------
68
+ value : Any
69
+ the data point value to write
70
+ """
71
+ ...
72
+
73
+ async def get_val(self, skip_cache: bool = False) -> Any:
74
+ """
75
+ Reads the data point value.
76
+
77
+ Parameters
78
+ ----------
79
+ skip_cache : bool
80
+ does not use cache if true
81
+
82
+ Returns
83
+ -------
84
+ Any
85
+ the data point value
86
+ """
87
+ ...
88
+
89
+ def name(self) -> tuple[str, str]:
90
+ """
91
+ Gets the functional profile and data point names.
92
+
93
+ Returns
94
+ -------
95
+ tuple[str, str]
96
+ the functional profile and data point names as tuple
97
+ """
98
+ ...
99
+
100
+ def direction(self) -> DataDirectionProduct:
101
+ """
102
+ Gets the data direction of the data point.
103
+
104
+ Returns
105
+ -------
106
+ DataDirectionProduct
107
+ the data point direction
108
+ """
109
+ ...
110
+
111
+ def unit(self) -> Units:
112
+ """
113
+ Gets the unit of measurement of the data point.
114
+
115
+ Returns
116
+ -------
117
+ Units
118
+ the unit
119
+ """
120
+ ...
121
+
122
+ def can_subscribe(self) -> bool:
123
+ """
124
+ Defines if subscribe() is allowed.
125
+
126
+ Returns
127
+ -------
128
+ bool
129
+ True if allowed, False otherwise
130
+ """
131
+ return False
132
+
133
+ def subscribe(self, fn: Callable[[Any], None]):
134
+ """
135
+ Subscribes to changes of the data point value.
136
+
137
+ Parameters
138
+ ----------
139
+ fn : Callable[[Any], None]
140
+ the callback method
141
+ """
142
+ raise Exception('Unsupported operation')
143
+
144
+ def unsubscribe(self):
145
+ """
146
+ Unsubscribes from changes of the data point value.
147
+ """
148
+ raise Exception('Unsupported operation')
149
+
150
+
151
+ class DataPoint(Generic[T]):
152
+ """
153
+ Implements a data point of a generic data type.
154
+ """
155
+
156
+ def __init__(
157
+ self, protocol: DataPointProtocol, validator: DataPointValidator
158
+ ):
159
+ """
160
+ Constructs a data point.
161
+
162
+ Parameters
163
+ ----------
164
+ protocol : DataPointProtocol
165
+ the underlying protocol
166
+ validator : DataPointValidator
167
+ the data point's value validator
168
+ """
169
+ self._protocol = protocol
170
+ self._validator = validator
171
+
172
+ def name(self) -> tuple[str, str]:
173
+ """
174
+ Gets the data point name.
175
+
176
+ Returns
177
+ -------
178
+ tuple[str, str]
179
+ the functional profile and data point name
180
+ """
181
+ return self._protocol.name()
182
+
183
+ async def get_value_async(self) -> T:
184
+ """
185
+ Gets the data point value asynchronously.
186
+
187
+ Returns
188
+ -------
189
+ T
190
+ the data point value
191
+
192
+ Raises
193
+ ------
194
+ Exception
195
+ when read value is not compatible with data type
196
+ """
197
+ value = await self._protocol.get_val()
198
+ if self._validator.validate(value):
199
+ return value
200
+ raise Exception(
201
+ f'invalid value read from device, {value}, validator: {self._validator.data_type()}'
202
+ )
203
+
204
+ def get_value(self) -> T:
205
+ """
206
+ Gets the data point value synchronously.
207
+
208
+ Returns
209
+ -------
210
+ T
211
+ the data point value
212
+
213
+ Raises
214
+ ------
215
+ Exception
216
+ when read value is not compatible with data type
217
+ """
218
+ return run(self.get_value_async())
219
+
220
+ async def set_value_async(self, value: T):
221
+ """
222
+ Sets the data point value asynchronously.
223
+
224
+ Parameters
225
+ ----------
226
+ value: T
227
+ the data point value
228
+ """
229
+ if self._validator.validate(value):
230
+ return await self._protocol.set_val(value)
231
+ raise Exception('invalid data to write to device')
232
+
233
+ def set_value(self, value: T):
234
+ """
235
+ Sets the data point value synchronously.
236
+
237
+ Parameters
238
+ ----------
239
+ value: T
240
+ the data point value
241
+ """
242
+ return run(self.set_value_async(value))
243
+
244
+ def subscribe(self, fn: Callable[[Any], None]):
245
+ """
246
+ Subscribes to data point value changes.
247
+
248
+ Parameters
249
+ ----------
250
+ fn : Callable[[Any], None]
251
+ the handler method
252
+ """
253
+ self._protocol.subscribe(fn)
254
+
255
+ def unsubscribe(self):
256
+ """
257
+ Unsubscribes from data point value changes.
258
+ """
259
+ self._protocol.unsubscribe()
260
+
261
+ def direction(self) -> DataDirectionProduct:
262
+ """
263
+ Gets the data point direction.
264
+
265
+ Returns
266
+ -------
267
+ DataDirectionProduct
268
+ the data point direction
269
+ """
270
+ return self._protocol.direction()
271
+
272
+ def data_type(self) -> DataTypes:
273
+ """
274
+ Gets the data point data type.
275
+
276
+ Returns
277
+ -------
278
+ DataTypes
279
+ the data point data type
280
+ """
281
+ return self._validator.data_type()
282
+
283
+ def unit(self) -> Units:
284
+ """
285
+ Gets the unit of measurement of the data point.
286
+
287
+ Returns
288
+ -------
289
+ Units
290
+ the unit
291
+ """
292
+ return self._protocol.unit()
293
+
294
+ def describe(
295
+ self,
296
+ ) -> tuple[tuple[str, str], DataDirectionProduct, DataTypes, Units]:
297
+ """
298
+ Describes the data point.
299
+
300
+ Returns
301
+ -------
302
+ tuple[tuple[str, str], DataDirectionProduct, DataTypes, Units]
303
+ the data point information
304
+ """
305
+ return self.name(), self.direction(), self.data_type(), self.unit()
306
+
307
+ def options(self) -> list[Any]:
308
+ """
309
+ Describes the data point options.
310
+
311
+ Returns
312
+ -------
313
+ list[Any]
314
+ the data point options
315
+ """
316
+ return self._validator.options()
@@ -0,0 +1,14 @@
1
+ from enum import Enum
2
+
3
+
4
+ class DataTypes(Enum):
5
+ """Defines the data point data types supported by the SGr specification."""
6
+
7
+ INT = "INT"
8
+ FLOAT = "FLOAT"
9
+ STRING = "STRING"
10
+ ENUM = "ENUM"
11
+ BOOLEAN = "BOOLEAN"
12
+ BITMAP = "BITMAP"
13
+ DATE_TIME = "DATE_TIME"
14
+ JSON = "JSON"
@@ -0,0 +1,226 @@
1
+ from asyncio import run
2
+ from collections.abc import Mapping
3
+ from dataclasses import dataclass
4
+ from typing import Any, Protocol
5
+
6
+ from sgr_specification.v0.generic import DataDirectionProduct, DeviceCategory
7
+ from sgr_specification.v0.product.product import DeviceFrame
8
+
9
+ from sgr_commhandler.api import DataPoint
10
+ from sgr_commhandler.api.configuration_parameter import (
11
+ ConfigurationParameter,
12
+ build_configuration_parameters,
13
+ )
14
+ from sgr_commhandler.api.data_types import DataTypes
15
+ from sgr_commhandler.api.functional_profile_api import FunctionalProfile
16
+
17
+
18
+ @dataclass
19
+ class DeviceInformation:
20
+ """
21
+ Implements a device information container.
22
+
23
+ Attributes
24
+ ----------
25
+ name : str
26
+ Defines the device name
27
+ manufacturer : str
28
+ Defines the manufacturer's name
29
+ software_revision : str
30
+ Defines the device software version
31
+ hardware_revision : str
32
+ Defines the device hardware version
33
+ device_category : DeviceCategory
34
+ Defines the device category
35
+ is_local : bool
36
+ Defines if the device is controlled locally or via cloud
37
+ """
38
+ name: str
39
+ manufacturer: str
40
+ software_revision: str
41
+ hardware_revision: str
42
+ device_category: DeviceCategory
43
+ is_local: bool
44
+
45
+
46
+ class SGrBaseInterface(Protocol):
47
+ """
48
+ Defines an abstract base class for all SGr device interfaces.
49
+
50
+ Attributes
51
+ ----------
52
+ device_frame : DeviceFrame
53
+ the device specification
54
+ configuration_parameters : list[ConfigurationParameter]
55
+ the configuration parameters with default values
56
+ device_information : DeviceInformation
57
+ the device information
58
+ functional_profiles : Mapping[str, FunctionalProfile]
59
+ the configured functional profiles
60
+ """
61
+ device_frame: DeviceFrame
62
+ configuration_parameters: list[ConfigurationParameter]
63
+ device_information: DeviceInformation
64
+ functional_profiles: Mapping[str, FunctionalProfile]
65
+
66
+ def _inititalize_device(
67
+ self, frame: DeviceFrame
68
+ ):
69
+ self.device_frame = frame
70
+ self.configuration_parameters = build_configuration_parameters(
71
+ frame.configuration_list
72
+ )
73
+ self.device_information = DeviceInformation(
74
+ name=frame.device_name if frame.device_name else '',
75
+ manufacturer=frame.manufacturer_name
76
+ if frame.manufacturer_name
77
+ else '',
78
+ software_revision=frame.device_information.software_revision
79
+ if frame.device_information
80
+ and frame.device_information.software_revision
81
+ else '',
82
+ hardware_revision=frame.device_information.hardware_revision
83
+ if frame.device_information
84
+ and frame.device_information.hardware_revision
85
+ else '',
86
+ device_category=frame.device_information.device_category
87
+ if frame.device_information
88
+ and frame.device_information.device_category
89
+ else DeviceCategory.DEVICE_INFORMATION,
90
+ is_local=frame.device_information.is_local_control
91
+ if frame.device_information
92
+ and frame.device_information.is_local_control
93
+ else False,
94
+ )
95
+
96
+ def connect(self):
97
+ """
98
+ Connects the device synchronously.
99
+ """
100
+ run(self.connect_async())
101
+
102
+ async def connect_async(self):
103
+ """
104
+ Connects the device asynchronously.
105
+ """
106
+ ...
107
+
108
+ def disconnect(self):
109
+ """
110
+ Disconnects the device synchronously.
111
+ """
112
+ run(self.disconnect_async())
113
+
114
+ async def disconnect_async(self):
115
+ """
116
+ Disconnects the device asynchronously.
117
+ """
118
+ ...
119
+
120
+ def is_connected(self) -> bool:
121
+ """
122
+ Gets the connection state.
123
+
124
+ Returns
125
+ -------
126
+ bool
127
+ the connection state
128
+ """
129
+ ...
130
+
131
+ def get_functional_profile(
132
+ self, functional_profile_name: str
133
+ ) -> FunctionalProfile:
134
+ """
135
+ Gets a functional profile.
136
+
137
+ Parameters
138
+ ----------
139
+ functional_profile_name : str
140
+ the functional profile name
141
+
142
+ Returns
143
+ -------
144
+ FunctionalProfile
145
+ a functional profile
146
+ """
147
+ return self.functional_profiles[functional_profile_name]
148
+
149
+ def get_data_point(self, dp: tuple[str, str]) -> DataPoint:
150
+ """
151
+ Gets a data point.
152
+
153
+ Parameters
154
+ ----------
155
+ dp : tuple[str, str]
156
+ the functional profile and data point name
157
+
158
+ Returns
159
+ -------
160
+ DataPoint
161
+ a data point
162
+ """
163
+ return self.get_functional_profile(dp[0]).get_data_point(dp[1])
164
+
165
+ def get_data_points(self) -> dict[tuple[str, str], DataPoint]:
166
+ """
167
+ Gets all data points.
168
+
169
+ Returns
170
+ -------
171
+ dict[tuple[str, str], DataPoint]
172
+ all data points
173
+ """
174
+ data_points = {}
175
+ for fp in self.functional_profiles.values():
176
+ data_points.update(fp.get_data_points())
177
+ return data_points
178
+
179
+ def get_values(self) -> dict[tuple[str, str], Any]:
180
+ """
181
+ Gets all data point values synchronously.
182
+
183
+ Returns
184
+ -------
185
+ dict[tuple[str, str], Any]
186
+ all data point values
187
+ """
188
+ return run(self.get_values_async())
189
+
190
+ async def get_values_async(self) -> dict[tuple[str, str], Any]:
191
+ """
192
+ Gets all data point values asynchronously.
193
+
194
+ Returns
195
+ -------
196
+ dict[tuple[str, str], Any]
197
+ all data point values
198
+ """
199
+ data = {}
200
+ for fp in self.functional_profiles.values():
201
+ data.update(
202
+ {
203
+ (fp.name(), key): value
204
+ for key, value in (await fp.get_value_async()).items()
205
+ }
206
+ )
207
+ return data
208
+
209
+ def describe(
210
+ self,
211
+ ) -> tuple[
212
+ str, dict[str, dict[str, tuple[DataDirectionProduct, DataTypes]]]
213
+ ]:
214
+ """
215
+ Gets the device description and all data.
216
+
217
+ Returns
218
+ -------
219
+ tuple[str, dict[str, dict[str, tuple[DataDirectionProduct, DataTypes]]]]
220
+ a tuple of device name and all data point values
221
+ """
222
+ data = {}
223
+ for fp in self.functional_profiles.values():
224
+ key, dps = fp.describe()
225
+ data[key] = dps
226
+ return self.device_information.name, data