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