s2-python 0.4.1__py3-none-any.whl → 0.6.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 (82) hide show
  1. {s2_python-0.4.1.dist-info → s2_python-0.6.0.dist-info}/METADATA +17 -12
  2. s2_python-0.6.0.dist-info/RECORD +89 -0
  3. {s2_python-0.4.1.dist-info → s2_python-0.6.0.dist-info}/WHEEL +1 -1
  4. s2_python-0.6.0.dist-info/licenses/LICENSE +201 -0
  5. s2python/common/__init__.py +32 -0
  6. s2python/common/duration.py +3 -1
  7. s2python/common/handshake.py +2 -2
  8. s2python/common/handshake_response.py +2 -2
  9. s2python/common/instruction_status_update.py +3 -3
  10. s2python/common/number_range.py +5 -2
  11. s2python/common/power_forecast.py +3 -3
  12. s2python/common/power_forecast_element.py +28 -6
  13. s2python/common/power_forecast_value.py +1 -1
  14. s2python/common/power_measurement.py +27 -5
  15. s2python/common/power_range.py +4 -2
  16. s2python/common/power_value.py +1 -1
  17. s2python/common/reception_status.py +2 -2
  18. s2python/common/resource_manager_details.py +6 -6
  19. s2python/common/revoke_object.py +3 -3
  20. s2python/common/role.py +1 -1
  21. s2python/common/select_control_type.py +2 -2
  22. s2python/common/session_request.py +2 -2
  23. s2python/common/timer.py +3 -3
  24. s2python/common/transition.py +8 -8
  25. s2python/ddbc/__init__.py +21 -0
  26. s2python/ddbc/ddbc_actuator_description.py +30 -0
  27. s2python/ddbc/ddbc_actuator_status.py +22 -0
  28. s2python/ddbc/ddbc_average_demand_rate_forecast.py +28 -0
  29. s2python/ddbc/ddbc_average_demand_rate_forecast_element.py +21 -0
  30. s2python/ddbc/ddbc_instruction.py +19 -0
  31. s2python/ddbc/ddbc_operation_mode.py +26 -0
  32. s2python/ddbc/ddbc_system_description.py +29 -0
  33. s2python/ddbc/ddbc_timer_status.py +18 -0
  34. s2python/frbc/__init__.py +19 -3
  35. s2python/frbc/frbc_actuator_description.py +15 -9
  36. s2python/frbc/frbc_actuator_status.py +5 -5
  37. s2python/frbc/frbc_fill_level_target_profile.py +3 -3
  38. s2python/frbc/frbc_fill_level_target_profile_element.py +7 -6
  39. s2python/frbc/frbc_instruction.py +5 -5
  40. s2python/frbc/frbc_leakage_behaviour.py +3 -3
  41. s2python/frbc/frbc_leakage_behaviour_element.py +9 -6
  42. s2python/frbc/frbc_operation_mode.py +13 -6
  43. s2python/frbc/frbc_operation_mode_element.py +5 -5
  44. s2python/frbc/frbc_storage_description.py +2 -2
  45. s2python/frbc/frbc_storage_status.py +2 -2
  46. s2python/frbc/frbc_system_description.py +4 -4
  47. s2python/frbc/frbc_timer_status.py +4 -4
  48. s2python/frbc/frbc_usage_forecast.py +3 -3
  49. s2python/frbc/frbc_usage_forecast_element.py +2 -2
  50. s2python/generated/gen_s2.py +508 -543
  51. s2python/message.py +101 -6
  52. s2python/ombc/__init__.py +5 -0
  53. s2python/ombc/ombc_instruction.py +19 -0
  54. s2python/ombc/ombc_operation_mode.py +25 -0
  55. s2python/ombc/ombc_status.py +17 -0
  56. s2python/ombc/ombc_system_description.py +25 -0
  57. s2python/ombc/ombc_timer_status.py +17 -0
  58. s2python/pebc/__init__.py +21 -0
  59. s2python/pebc/pebc_allowed_limit_range.py +42 -0
  60. s2python/pebc/pebc_energy_constraint.py +25 -0
  61. s2python/pebc/pebc_instruction.py +27 -0
  62. s2python/pebc/pebc_power_constraints.py +77 -0
  63. s2python/pebc/pebc_power_envelope.py +23 -0
  64. s2python/pebc/pebc_power_envelope_element.py +16 -0
  65. s2python/ppbc/__init__.py +15 -6
  66. s2python/ppbc/ppbc_end_interruption_instruction.py +6 -8
  67. s2python/ppbc/ppbc_power_profile_definition.py +4 -6
  68. s2python/ppbc/ppbc_power_profile_status.py +2 -4
  69. s2python/ppbc/ppbc_power_sequence.py +6 -6
  70. s2python/ppbc/ppbc_power_sequence_container.py +5 -7
  71. s2python/ppbc/ppbc_power_sequence_container_status.py +7 -9
  72. s2python/ppbc/ppbc_power_sequence_element.py +3 -5
  73. s2python/ppbc/ppbc_schedule_instruction.py +6 -8
  74. s2python/ppbc/ppbc_start_interruption_instruction.py +6 -8
  75. s2python/s2_connection.py +93 -32
  76. s2python/s2_control_type.py +36 -0
  77. s2python/s2_parser.py +4 -0
  78. s2python/s2_validation_error.py +3 -1
  79. s2python/validate_values_mixin.py +29 -14
  80. s2_python-0.4.1.dist-info/RECORD +0 -66
  81. {s2_python-0.4.1.dist-info → s2_python-0.6.0.dist-info}/entry_points.txt +0 -0
  82. {s2_python-0.4.1.dist-info → s2_python-0.6.0.dist-info}/top_level.txt +0 -0
@@ -12,21 +12,19 @@ from s2python.validate_values_mixin import (
12
12
 
13
13
 
14
14
  @catch_and_convert_exceptions
15
- class PPBCPowerSequenceContainerStatus(
16
- GenPPBCPowerSequenceContainerStatus, S2MessageComponent["PPBCPowerSequenceContainerStatus"]
17
- ):
15
+ class PPBCPowerSequenceContainerStatus(GenPPBCPowerSequenceContainerStatus, S2MessageComponent):
18
16
  model_config = GenPPBCPowerSequenceContainerStatus.model_config
19
17
  model_config["validate_assignment"] = True
20
18
 
21
- power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[
19
+ power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
22
20
  "power_profile_id" # type: ignore[assignment]
23
21
  ]
24
- sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[
22
+ sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
25
23
  "sequence_container_id" # type: ignore[assignment]
26
24
  ]
27
- selected_sequence_id: Union[uuid.UUID, None] = (
28
- GenPPBCPowerSequenceContainerStatus.model_fields["selected_sequence_id"] # type: ignore[assignment]
29
- )
30
- progress: Union[uuid.UUID, None] = GenPPBCPowerSequenceContainerStatus.model_fields[
25
+ selected_sequence_id: Union[uuid.UUID, None] = GenPPBCPowerSequenceContainerStatus.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
26
+ "selected_sequence_id"
27
+ ] # type: ignore[assignment]
28
+ progress: Union[uuid.UUID, None] = GenPPBCPowerSequenceContainerStatus.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
31
29
  "progress" # type: ignore[assignment]
32
30
  ]
@@ -13,13 +13,11 @@ from s2python.common import Duration, PowerForecastValue
13
13
 
14
14
 
15
15
  @catch_and_convert_exceptions
16
- class PPBCPowerSequenceElement(
17
- GenPPBCPowerSequenceElement, S2MessageComponent["PPBCPowerSequenceElement"]
18
- ):
16
+ class PPBCPowerSequenceElement(GenPPBCPowerSequenceElement, S2MessageComponent):
19
17
  model_config = GenPPBCPowerSequenceElement.model_config
20
18
  model_config["validate_assignment"] = True
21
19
 
22
- duration: Duration = GenPPBCPowerSequenceElement.model_fields["duration"] # type: ignore[assignment]
23
- power_values: List[PowerForecastValue] = GenPPBCPowerSequenceElement.model_fields[
20
+ duration: Duration = GenPPBCPowerSequenceElement.model_fields["duration"] # type: ignore[assignment,reportIncompatibleVariableOverride]
21
+ power_values: List[PowerForecastValue] = GenPPBCPowerSequenceElement.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
24
22
  "power_values"
25
23
  ] # type: ignore[assignment]
@@ -10,24 +10,22 @@ from s2python.validate_values_mixin import (
10
10
 
11
11
 
12
12
  @catch_and_convert_exceptions
13
- class PPBCScheduleInstruction(
14
- GenPPBCScheduleInstruction, S2MessageComponent["PPBCScheduleInstruction"]
15
- ):
13
+ class PPBCScheduleInstruction(GenPPBCScheduleInstruction, S2MessageComponent):
16
14
  model_config = GenPPBCScheduleInstruction.model_config
17
15
  model_config["validate_assignment"] = True
18
16
 
19
- id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["id"] # type: ignore[assignment]
17
+ id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["id"] # type: ignore[assignment,reportIncompatibleVariableOverride]
20
18
 
21
- power_profile_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[
19
+ power_profile_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
22
20
  "power_profile_id"
23
21
  ] # type: ignore[assignment]
24
22
 
25
- message_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["message_id"] # type: ignore[assignment]
23
+ message_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["message_id"] # type: ignore[assignment,reportIncompatibleVariableOverride]
26
24
 
27
- sequence_container_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[
25
+ sequence_container_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
28
26
  "sequence_container_id"
29
27
  ] # type: ignore[assignment]
30
28
 
31
- power_sequence_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[
29
+ power_sequence_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
32
30
  "power_sequence_id"
33
31
  ] # type: ignore[assignment]
@@ -11,22 +11,20 @@ from s2python.validate_values_mixin import (
11
11
 
12
12
 
13
13
  @catch_and_convert_exceptions
14
- class PPBCStartInterruptionInstruction(
15
- GenPPBCStartInterruptionInstruction, S2MessageComponent["PPBCStartInterruptionInstruction"]
16
- ):
14
+ class PPBCStartInterruptionInstruction(GenPPBCStartInterruptionInstruction, S2MessageComponent):
17
15
  model_config = GenPPBCStartInterruptionInstruction.model_config
18
16
  model_config["validate_assignment"] = True
19
17
 
20
- id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields["id"] # type: ignore[assignment]
21
- power_profile_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[
18
+ id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields["id"] # type: ignore[assignment,reportIncompatibleVariableOverride]
19
+ power_profile_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
22
20
  "power_profile_id"
23
21
  ] # type: ignore[assignment]
24
- sequence_container_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[
22
+ sequence_container_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
25
23
  "sequence_container_id"
26
24
  ] # type: ignore[assignment]
27
- power_sequence_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[
25
+ power_sequence_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
28
26
  "power_sequence_id"
29
27
  ] # type: ignore[assignment]
30
- abnormal_condition: bool = GenPPBCStartInterruptionInstruction.model_fields[
28
+ abnormal_condition: bool = GenPPBCStartInterruptionInstruction.model_fields[ # type: ignore[reportIncompatibleVariableOverride]
31
29
  "abnormal_condition"
32
30
  ] # type: ignore[assignment]
s2python/s2_connection.py CHANGED
@@ -1,14 +1,24 @@
1
+ try:
2
+ import websockets
3
+ except ImportError as exc:
4
+ raise ImportError(
5
+ "The 'websockets' package is required. Run 'pip install s2-python[ws]' to use this feature."
6
+ ) from exc
7
+
1
8
  import asyncio
2
9
  import json
3
10
  import logging
4
11
  import time
5
12
  import threading
6
13
  import uuid
14
+ import ssl
7
15
  from dataclasses import dataclass
8
- from typing import Optional, List, Type, Dict, Callable, Awaitable, Union
16
+ from typing import Any, Optional, List, Type, Dict, Callable, Awaitable, Union
9
17
 
10
- import websockets
11
- from websockets.asyncio.client import ClientConnection as WSConnection, connect as ws_connect
18
+ from websockets.asyncio.client import (
19
+ ClientConnection as WSConnection,
20
+ connect as ws_connect,
21
+ )
12
22
 
13
23
  from s2python.common import (
14
24
  ReceptionStatusValues,
@@ -35,7 +45,7 @@ logger = logging.getLogger("s2python")
35
45
 
36
46
  @dataclass
37
47
  class AssetDetails: # pylint: disable=too-many-instance-attributes
38
- resource_id: str
48
+ resource_id: uuid.UUID
39
49
 
40
50
  provides_forecast: bool
41
51
  provides_power_measurements: List[CommodityQuantity]
@@ -55,7 +65,8 @@ class AssetDetails: # pylint: disable=too-many-instance-attributes
55
65
  ) -> ResourceManagerDetails:
56
66
  return ResourceManagerDetails(
57
67
  available_control_types=[
58
- control_type.get_protocol_control_type() for control_type in control_types
68
+ control_type.get_protocol_control_type()
69
+ for control_type in control_types
59
70
  ],
60
71
  currency=self.currency,
61
72
  firmware_version=self.firmware_version,
@@ -92,7 +103,7 @@ class SendOkay:
92
103
  self.status_is_send.set()
93
104
 
94
105
  await self.connection.respond_with_reception_status(
95
- subject_message_id=str(self.subject_message_id),
106
+ subject_message_id=self.subject_message_id,
96
107
  status=ReceptionStatusValues.OK,
97
108
  diagnostic_label="Processed okay.",
98
109
  )
@@ -101,7 +112,7 @@ class SendOkay:
101
112
  self.status_is_send.set()
102
113
 
103
114
  self.connection.respond_with_reception_status_sync(
104
- subject_message_id=str(self.subject_message_id),
115
+ subject_message_id=self.subject_message_id,
105
116
  status=ReceptionStatusValues.OK,
106
117
  diagnostic_label="Processed okay.",
107
118
  )
@@ -158,7 +169,7 @@ class MessageHandlers:
158
169
  except Exception:
159
170
  if not send_okay.status_is_send.is_set():
160
171
  await connection.respond_with_reception_status(
161
- subject_message_id=str(msg.message_id), # type: ignore[attr-defined, union-attr]
172
+ subject_message_id=msg.message_id, # type: ignore[attr-defined, union-attr]
162
173
  status=ReceptionStatusValues.PERMANENT_ERROR,
163
174
  diagnostic_label=f"While processing message {msg.message_id} " # type: ignore[attr-defined, union-attr] # pylint: disable=line-too-long
164
175
  f"an unrecoverable error occurred.",
@@ -170,7 +181,9 @@ class MessageHandlers:
170
181
  type(msg),
171
182
  )
172
183
 
173
- def register_handler(self, msg_type: Type[S2Message], handler: S2MessageHandler) -> None:
184
+ def register_handler(
185
+ self, msg_type: Type[S2Message], handler: S2MessageHandler
186
+ ) -> None:
174
187
  """Register a coroutine function or a normal function as the handler for a specific S2 message type.
175
188
 
176
189
  :param msg_type: The S2 message type to attach the handler to.
@@ -198,6 +211,8 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
198
211
  _eventloop: asyncio.AbstractEventLoop
199
212
  _stop_event: asyncio.Event
200
213
  _restart_connection_event: asyncio.Event
214
+ _verify_certificate: bool
215
+ _bearer_token: Optional[str]
201
216
 
202
217
  def __init__( # pylint: disable=too-many-arguments
203
218
  self,
@@ -206,6 +221,8 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
206
221
  control_types: List[S2ControlType],
207
222
  asset_details: AssetDetails,
208
223
  reconnect: bool = False,
224
+ verify_certificate: bool = True,
225
+ bearer_token: Optional[str] = None,
209
226
  ) -> None:
210
227
  self.url = url
211
228
  self.reconnect = reconnect
@@ -221,10 +238,14 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
221
238
  self.control_types = control_types
222
239
  self.role = role
223
240
  self.asset_details = asset_details
241
+ self._verify_certificate = verify_certificate
224
242
 
225
- self._handlers.register_handler(SelectControlType, self.handle_select_control_type_as_rm)
243
+ self._handlers.register_handler(
244
+ SelectControlType, self.handle_select_control_type_as_rm
245
+ )
226
246
  self._handlers.register_handler(Handshake, self.handle_handshake)
227
247
  self._handlers.register_handler(HandshakeResponse, self.handle_handshake_response_as_rm)
248
+ self._bearer_token = bearer_token
228
249
 
229
250
  def start_as_rm(self) -> None:
230
251
  self._run_eventloop(self._run_as_rm())
@@ -247,8 +268,7 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
247
268
  """
248
269
  if threading.current_thread() == self._thread:
249
270
  raise RuntimeError(
250
- "Do not call stop from the thread running the S2 connection. This results in an "
251
- "infinite block!"
271
+ "Do not call stop from the thread running the S2 connection. This results in an infinite block!"
252
272
  )
253
273
  if self._eventloop.is_running():
254
274
  asyncio.run_coroutine_threadsafe(self._do_stop(), self._eventloop).result()
@@ -304,7 +324,10 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
304
324
  await task
305
325
  except asyncio.CancelledError:
306
326
  pass
307
- except (websockets.ConnectionClosedError, websockets.ConnectionClosedOK):
327
+ except (
328
+ websockets.ConnectionClosedError,
329
+ websockets.ConnectionClosedOK,
330
+ ):
308
331
  logger.info("The other party closed the websocket connection.")
309
332
 
310
333
  for task in pending:
@@ -319,17 +342,33 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
319
342
 
320
343
  async def _connect_ws(self) -> None:
321
344
  try:
322
- self.ws = await ws_connect(uri=self.url)
345
+ # set up connection arguments for SSL and bearer token, if required
346
+ connection_kwargs: Dict[str, Any] = {}
347
+ if self.url.startswith("wss://") and not self._verify_certificate:
348
+ connection_kwargs["ssl"] = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
349
+ connection_kwargs["ssl"].check_hostname = False
350
+ connection_kwargs["ssl"].verify_mode = ssl.CERT_NONE
351
+
352
+ if self._bearer_token:
353
+ connection_kwargs["additional_headers"] = {
354
+ "Authorization": f"Bearer {self._bearer_token}"
355
+ }
356
+
357
+ self.ws = await ws_connect(uri=self.url, **connection_kwargs)
323
358
  except (EOFError, OSError) as e:
324
359
  logger.info("Could not connect due to: %s", str(e))
325
360
 
326
361
  async def _connect_as_rm(self) -> None:
327
362
  await self.send_msg_and_await_reception_status_async(
328
363
  Handshake(
329
- message_id=uuid.uuid4(), role=self.role, supported_protocol_versions=[S2_VERSION]
364
+ message_id=uuid.uuid4(),
365
+ role=self.role,
366
+ supported_protocol_versions=[S2_VERSION],
330
367
  )
331
368
  )
332
- logger.debug("Send handshake to CEM. Expecting Handshake and HandshakeResponse from CEM.")
369
+ logger.debug(
370
+ "Send handshake to CEM. Expecting Handshake and HandshakeResponse from CEM."
371
+ )
333
372
 
334
373
  await self._handle_received_messages()
335
374
 
@@ -338,7 +377,8 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
338
377
  ) -> None:
339
378
  if not isinstance(message, Handshake):
340
379
  logger.error(
341
- "Handler for Handshake received a message of the wrong type: %s", type(message)
380
+ "Handler for Handshake received a message of the wrong type: %s",
381
+ type(message),
342
382
  )
343
383
  return
344
384
 
@@ -361,7 +401,9 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
361
401
 
362
402
  logger.debug("Received HandshakeResponse %s", message.to_json())
363
403
 
364
- logger.debug("CEM selected to use version %s", message.selected_protocol_version)
404
+ logger.debug(
405
+ "CEM selected to use version %s", message.selected_protocol_version
406
+ )
365
407
  await send_okay
366
408
  logger.debug("Handshake complete. Sending first ResourceManagerDetails.")
367
409
 
@@ -381,22 +423,29 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
381
423
 
382
424
  await send_okay
383
425
 
384
- logger.debug("CEM selected control type %s. Activating control type.", message.control_type)
426
+ logger.debug(
427
+ "CEM selected control type %s. Activating control type.",
428
+ message.control_type,
429
+ )
385
430
 
386
431
  control_types_by_protocol_name = {
387
432
  c.get_protocol_control_type(): c for c in self.control_types
388
433
  }
389
- selected_control_type: Optional[S2ControlType] = control_types_by_protocol_name.get(
390
- message.control_type
434
+ selected_control_type: Optional[S2ControlType] = (
435
+ control_types_by_protocol_name.get(message.control_type)
391
436
  )
392
437
 
393
438
  if self._current_control_type is not None:
394
- await self._eventloop.run_in_executor(None, self._current_control_type.deactivate, self)
439
+ await self._eventloop.run_in_executor(
440
+ None, self._current_control_type.deactivate, self
441
+ )
395
442
 
396
443
  self._current_control_type = selected_control_type
397
444
 
398
445
  if self._current_control_type is not None:
399
- await self._eventloop.run_in_executor(None, self._current_control_type.activate, self)
446
+ await self._eventloop.run_in_executor(
447
+ None, self._current_control_type.activate, self
448
+ )
400
449
  self._current_control_type.register_handlers(self._handlers)
401
450
 
402
451
  async def _receive_messages(self) -> None:
@@ -418,7 +467,7 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
418
467
  except json.JSONDecodeError:
419
468
  await self._send_and_forget(
420
469
  ReceptionStatus(
421
- subject_message_id="00000000-0000-0000-0000-000000000000",
470
+ subject_message_id=uuid.UUID("00000000-0000-0000-0000-000000000000"),
422
471
  status=ReceptionStatusValues.INVALID_DATA,
423
472
  diagnostic_label="Not valid json.",
424
473
  )
@@ -434,7 +483,7 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
434
483
  )
435
484
  else:
436
485
  await self.respond_with_reception_status(
437
- subject_message_id="00000000-0000-0000-0000-000000000000",
486
+ subject_message_id=uuid.UUID("00000000-0000-0000-0000-000000000000"),
438
487
  status=ReceptionStatusValues.INVALID_DATA,
439
488
  diagnostic_label="Message appears valid json but could not find a message_id field.",
440
489
  )
@@ -465,9 +514,11 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
465
514
  self._restart_connection_event.set()
466
515
 
467
516
  async def respond_with_reception_status(
468
- self, subject_message_id: str, status: ReceptionStatusValues, diagnostic_label: str
517
+ self, subject_message_id: uuid.UUID, status: ReceptionStatusValues, diagnostic_label: str
469
518
  ) -> None:
470
- logger.debug("Responding to message %s with status %s", subject_message_id, status)
519
+ logger.debug(
520
+ "Responding to message %s with status %s", subject_message_id, status
521
+ )
471
522
  await self._send_and_forget(
472
523
  ReceptionStatus(
473
524
  subject_message_id=subject_message_id,
@@ -477,15 +528,20 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
477
528
  )
478
529
 
479
530
  def respond_with_reception_status_sync(
480
- self, subject_message_id: str, status: ReceptionStatusValues, diagnostic_label: str
531
+ self, subject_message_id: uuid.UUID, status: ReceptionStatusValues, diagnostic_label: str
481
532
  ) -> None:
482
533
  asyncio.run_coroutine_threadsafe(
483
- self.respond_with_reception_status(subject_message_id, status, diagnostic_label),
534
+ self.respond_with_reception_status(
535
+ subject_message_id, status, diagnostic_label
536
+ ),
484
537
  self._eventloop,
485
538
  ).result()
486
539
 
487
540
  async def send_msg_and_await_reception_status_async(
488
- self, s2_msg: S2Message, timeout_reception_status: float = 5.0, raise_on_error: bool = True
541
+ self,
542
+ s2_msg: S2Message,
543
+ timeout_reception_status: float = 5.0,
544
+ raise_on_error: bool = True,
489
545
  ) -> ReceptionStatus:
490
546
  await self._send_and_forget(s2_msg)
491
547
  logger.debug(
@@ -506,12 +562,17 @@ class S2Connection: # pylint: disable=too-many-instance-attributes
506
562
  raise
507
563
 
508
564
  if reception_status.status != ReceptionStatusValues.OK and raise_on_error:
509
- raise RuntimeError(f"ReceptionStatus was not OK but rather {reception_status.status}")
565
+ raise RuntimeError(
566
+ f"ReceptionStatus was not OK but rather {reception_status.status}"
567
+ )
510
568
 
511
569
  return reception_status
512
570
 
513
571
  def send_msg_and_await_reception_status_sync(
514
- self, s2_msg: S2Message, timeout_reception_status: float = 5.0, raise_on_error: bool = True
572
+ self,
573
+ s2_msg: S2Message,
574
+ timeout_reception_status: float = 5.0,
575
+ raise_on_error: bool = True,
515
576
  ) -> ReceptionStatus:
516
577
  return asyncio.run_coroutine_threadsafe(
517
578
  self.send_msg_and_await_reception_status_async(
@@ -4,6 +4,7 @@ import typing
4
4
  from s2python.common import ControlType as ProtocolControlType
5
5
  from s2python.frbc import FRBCInstruction
6
6
  from s2python.ppbc import PPBCScheduleInstruction
7
+ from s2python.ombc import OMBCInstruction
7
8
  from s2python.message import S2Message
8
9
 
9
10
  if typing.TYPE_CHECKING:
@@ -66,6 +67,41 @@ class PPBCControlType(S2ControlType):
66
67
  """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type."""
67
68
 
68
69
 
70
+ class OMBCControlType(S2ControlType):
71
+ def get_protocol_control_type(self) -> ProtocolControlType:
72
+ return ProtocolControlType.OPERATION_MODE_BASED_CONTROL
73
+
74
+ def register_handlers(self, handlers: "MessageHandlers") -> None:
75
+ handlers.register_handler(OMBCInstruction, self.handle_instruction)
76
+
77
+ @abc.abstractmethod
78
+ def handle_instruction(
79
+ self, conn: "S2Connection", msg: S2Message, send_okay: typing.Callable[[], None]
80
+ ) -> None: ...
81
+
82
+ @abc.abstractmethod
83
+ def activate(self, conn: "S2Connection") -> None:
84
+ """Overwrite with the actual dctivation logic of your Resource Manager for this particular control type."""
85
+
86
+ @abc.abstractmethod
87
+ def deactivate(self, conn: "S2Connection") -> None:
88
+ """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type."""
89
+
90
+
91
+ class PEBCControlType(S2ControlType):
92
+ def get_protocol_control_type(self) -> ProtocolControlType:
93
+ return ProtocolControlType.POWER_ENVELOPE_BASED_CONTROL
94
+
95
+ def register_handlers(self, handlers: "MessageHandlers") -> None:
96
+ pass
97
+
98
+ @abc.abstractmethod
99
+ def activate(self, conn: "S2Connection") -> None: ...
100
+
101
+ @abc.abstractmethod
102
+ def deactivate(self, conn: "S2Connection") -> None: ...
103
+
104
+
69
105
  class NoControlControlType(S2ControlType):
70
106
  def get_protocol_control_type(self) -> ProtocolControlType:
71
107
  return ProtocolControlType.NOT_CONTROLABLE
s2python/s2_parser.py CHANGED
@@ -24,6 +24,7 @@ from s2python.frbc import (
24
24
  FRBCTimerStatus,
25
25
  FRBCUsageForecast,
26
26
  )
27
+ from s2python.pebc import PEBCPowerConstraints, PEBCEnergyConstraint, PEBCInstruction
27
28
  from s2python.ppbc import PPBCScheduleInstruction
28
29
 
29
30
  from s2python.message import S2Message
@@ -48,6 +49,9 @@ TYPE_TO_MESSAGE_CLASS: Dict[str, Type[S2Message]] = {
48
49
  "FRBC.TimerStatus": FRBCTimerStatus,
49
50
  "FRBC.UsageForecast": FRBCUsageForecast,
50
51
  "PPBC.ScheduleInstruction": PPBCScheduleInstruction,
52
+ "PEBC.PowerConstraints": PEBCPowerConstraints,
53
+ "PEBC.Instruction": PEBCInstruction,
54
+ "PEBC.EnergyConstraint": PEBCEnergyConstraint,
51
55
  "Handshake": Handshake,
52
56
  "HandshakeResponse": HandshakeResponse,
53
57
  "InstructionStatusUpdate": InstructionStatusUpdate,
@@ -10,4 +10,6 @@ class S2ValidationError(Exception):
10
10
  class_: Optional[Type]
11
11
  obj: object
12
12
  msg: str
13
- pydantic_validation_error: Union[ValidationErrorV1, ValidationError, TypeError, None]
13
+ pydantic_validation_error: Union[
14
+ ValidationErrorV1, ValidationError, TypeError, None
15
+ ]
@@ -1,22 +1,34 @@
1
- from typing import TypeVar, Generic, Type, Callable, Any, Union, AbstractSet, Mapping, List, Dict
1
+ from typing import (
2
+ TypeVar,
3
+ Type,
4
+ Callable,
5
+ Any,
6
+ Union,
7
+ AbstractSet,
8
+ Mapping,
9
+ List,
10
+ Dict,
11
+ )
12
+
13
+ from typing_extensions import Self
2
14
 
3
- from pydantic import BaseModel, ValidationError # pylint: disable=no-name-in-module
4
15
  from pydantic.v1.error_wrappers import display_errors # pylint: disable=no-name-in-module
5
16
 
17
+ from pydantic import ( # pylint: disable=no-name-in-module
18
+ BaseModel,
19
+ ValidationError,
20
+ )
21
+
6
22
  from s2python.s2_validation_error import S2ValidationError
7
23
 
8
- B_co = TypeVar("B_co", bound=BaseModel, covariant=True)
9
24
 
10
25
  IntStr = Union[int, str]
11
26
  AbstractSetIntStr = AbstractSet[IntStr]
12
27
  MappingIntStrAny = Mapping[IntStr, Any]
13
28
 
14
29
 
15
- C = TypeVar("C", bound="BaseModel")
16
-
17
-
18
- class S2MessageComponent(BaseModel, Generic[C]):
19
- def to_json(self: C) -> str:
30
+ class S2MessageComponent(BaseModel):
31
+ def to_json(self) -> str:
20
32
  try:
21
33
  return self.model_dump_json(by_alias=True, exclude_none=True)
22
34
  except (ValidationError, TypeError) as e:
@@ -24,17 +36,17 @@ class S2MessageComponent(BaseModel, Generic[C]):
24
36
  type(self), self, "Pydantic raised a format validation error.", e
25
37
  ) from e
26
38
 
27
- def to_dict(self: C) -> Dict:
39
+ def to_dict(self) -> Dict[str, Any]:
28
40
  return self.model_dump()
29
41
 
30
42
  @classmethod
31
- def from_json(cls: Type[C], json_str: str) -> C:
32
- gen_model: C = cls.model_validate_json(json_str)
43
+ def from_json(cls, json_str: str) -> Self:
44
+ gen_model = cls.model_validate_json(json_str)
33
45
  return gen_model
34
46
 
35
47
  @classmethod
36
- def from_dict(cls: Type[C], json_dict: dict) -> C:
37
- gen_model: C = cls.model_validate(json_dict)
48
+ def from_dict(cls, json_dict: Dict[str, Any]) -> Self:
49
+ gen_model = cls.model_validate(json_dict)
38
50
  return gen_model
39
51
 
40
52
 
@@ -59,7 +71,10 @@ def convert_to_s2exception(f: Callable) -> Callable:
59
71
  return inner
60
72
 
61
73
 
62
- def catch_and_convert_exceptions(input_class: Type[S2MessageComponent[B_co]]) -> Type[S2MessageComponent[B_co]]:
74
+ S = TypeVar("S", bound=S2MessageComponent)
75
+
76
+
77
+ def catch_and_convert_exceptions(input_class: Type[S]) -> Type[S]:
63
78
  input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign]
64
79
  input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign]
65
80
  input_class.model_validate_json = convert_to_s2exception( # type: ignore[method-assign]
@@ -1,66 +0,0 @@
1
- s2python/__init__.py,sha256=e5lwvqsPl-z7IfEd0hRQhLBRKBYcuw2eqrecXnMfLdg,384
2
- s2python/message.py,sha256=Id-CleYk6ClVh3o5meVtRECLNwQHlyddNSOq0-d2bZk,1027
3
- s2python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- s2python/reception_status_awaiter.py,sha256=jKMliFk1XxwsEGtx3vFESbJhUtClB7cTu-td90-qBN8,2137
5
- s2python/s2_connection.py,sha256=CTTTGzJ6IG9PcQpC5XtnThaYQRpp66gQrauPK8KygOE,20166
6
- s2python/s2_control_type.py,sha256=4L6o_QG9ilYA8c3QWpSjc77xwxEHHsAGZO8WQDgxDr0,2860
7
- s2python/s2_parser.py,sha256=x2JSCSEvXMCt-0kPDH4zER--CsyFYyRAaezr76V_8Qc,4354
8
- s2python/s2_validation_error.py,sha256=BkOLoNsrcQ3MzdCYaPDgs1Wu6lPdlQDpZsTpykKGQmE,384
9
- s2python/utils.py,sha256=QX9b-mi-H_YUGTmGmJsrAbaWWM3dgaoaRLRXHHlaZDE,212
10
- s2python/validate_values_mixin.py,sha256=3WbauPprzWpGdyDUYPoELICi2NbHrCFzlQ7AtrpDSis,2568
11
- s2python/version.py,sha256=IBzoytgbYYYekQnSTfSmWeYAZ4c_yUFU2oLIAG4UYjs,45
12
- s2python/common/__init__.py,sha256=yEAXCS59XpNOEXbEXoN04SwultP-3evgVzFuEUSgB5I,1345
13
- s2python/common/duration.py,sha256=5_zi0wv98dr8lHXh2RdHATzW4Un8ZNUx4-zaI1t77Ak,667
14
- s2python/common/handshake.py,sha256=bsx64flKxVUUMT18O47pV1pnnpXcAGXMJbRi9q4GjwU,467
15
- s2python/common/handshake_response.py,sha256=5cAbtLTZ8fkhUw-XdMlGBro3XKWqXXLw3YUavvwLYNg,523
16
- s2python/common/instruction_status_update.py,sha256=lo5HxoEGFp8bU0-AQ7bNxuDj1StZ73GE_2sGDA7o5vI,692
17
- s2python/common/number_range.py,sha256=N0OUoGtkdYg4GDUwvVHKLjdXxcy9mijgwqmmA079P6k,741
18
- s2python/common/power_forecast.py,sha256=C0D6XOphdcEVjfb0py-l2zLnNC-udWmlDD7g2Hre2uo,704
19
- s2python/common/power_forecast_element.py,sha256=M3weDKlBASjS_VHt4XKdms2fpxEZy17kWuLTUXsFibU,805
20
- s2python/common/power_forecast_value.py,sha256=im5kLyTfDjLuODFWoIuVgVxKjX2FpyPXYoxonjuOpu0,411
21
- s2python/common/power_measurement.py,sha256=mMVpe3T86Xu6ctA2Rpc50aB4WS7xuLXgGVSbB4lykHQ,693
22
- s2python/common/power_range.py,sha256=FKhJRkT3dzUCeK8P3mrcRVtJHPf2WH-eQDottvEMpBs,686
23
- s2python/common/power_value.py,sha256=ujo0yxHnAd-LCIQQuIpNU01jv611UCbcXKl8ilQIILY,363
24
- s2python/common/reception_status.py,sha256=HXdaTu5B2iuHb1pg8d5TMprfi80rUmCgCxrrDFLJB0w,525
25
- s2python/common/resource_manager_details.py,sha256=QTW6VTn-Y_fvVKX6qGgDMuvry5twmDqHjRvOZXgkuLI,1044
26
- s2python/common/revoke_object.py,sha256=7OVNuwu5aMfZ3GJ_b_SRNoTxiX80FY9obp7JHIySzaY,585
27
- s2python/common/role.py,sha256=eHKwnie_7eK8k1CNA7S0TbwmkQR84Pzkwdk8aUaUldw,327
28
- s2python/common/select_control_type.py,sha256=Rbvem30LrrPYTSjwspc-PJyxoNScv9XRVXJ58-WRotk,523
29
- s2python/common/session_request.py,sha256=ztbvGjMwiEmdJ_ToFuPV9Lj_IHJ78fP7IsCt8rMvMu8,502
30
- s2python/common/support.py,sha256=Kbrf_KGB45Wfr8j2pqDe1lLde6CIr3nl_LYkWnilmV0,1015
31
- s2python/common/timer.py,sha256=PWJjHXfL6yr2VQ8O2HwtYB0o3Jw4TdP2iMX2Wdp7Ic4,556
32
- s2python/common/transition.py,sha256=2v0fkxudPhVqmnd-WajJhBsYRnJLAqJU9z1eqQJy8zw,1064
33
- s2python/frbc/__init__.py,sha256=ROV3qZoldPkdgVFfMQr5Mf3GDfBzXaMfhNNCuXY6T0s,1104
34
- s2python/frbc/frbc_actuator_description.py,sha256=fsgYdKvxZnUCuUuyoN0Ibu5FoA0vUBSrKy6WQJGBfGk,6166
35
- s2python/frbc/frbc_actuator_status.py,sha256=cPyAz0B0rThfueKr3CWx61E3eJ8Lp29a-nJPyTPpbls,973
36
- s2python/frbc/frbc_fill_level_target_profile.py,sha256=suRBgT8b9HQjfafW-sjqzjFpgJ2vOf73nmL5-Q6WhOo,880
37
- s2python/frbc/frbc_fill_level_target_profile_element.py,sha256=V7O7oDDfifLcJsSXYWEd00X3C2TPoU8TcrRu2yLWVVU,1280
38
- s2python/frbc/frbc_instruction.py,sha256=Q8w9KB4x6mGsLR-3ADzsyWWf0-uTWGZ31rnKGC9TkIc,809
39
- s2python/frbc/frbc_leakage_behaviour.py,sha256=E23McMu4zbjDIUR_MNBlyUuh1chGM-EKTMuiHqjV_wg,794
40
- s2python/frbc/frbc_leakage_behaviour_element.py,sha256=B3Tzlu3iMVV3NA1WJds_LDl3T_lBGL3HjGiEin02_pQ,1103
41
- s2python/frbc/frbc_operation_mode.py,sha256=w0--55ZByWQukbotR6VpoL9GvNbeNyHcs3NqHAzjq6w,1899
42
- s2python/frbc/frbc_operation_mode_element.py,sha256=64_FJzfCRoUgRCdfA1HpLO9lTE1WBxL3Pbn0vaNeX8c,1073
43
- s2python/frbc/frbc_storage_description.py,sha256=Jc0zpAwf3E7Grl6v0fkSa4yA8-2-klO1aY-50l-u56E,622
44
- s2python/frbc/frbc_storage_status.py,sha256=h6cHTb7Msw5TckPzKsiI3KTSa7Hr5BcXIelF8gmFIdw,523
45
- s2python/frbc/frbc_system_description.py,sha256=_scCAVe4sHEhQMHkDvD2pKGnGLzdnm4uARbdcw0inKk,980
46
- s2python/frbc/frbc_timer_status.py,sha256=sRsvXxsT_TDlPiacLkZlCZM3y36NZRGqj6RKJeil-yc,711
47
- s2python/frbc/frbc_usage_forecast.py,sha256=MWY4Nr2T9NGjQhf7rxTtkqnuSdApTjro81-o_buLABk,747
48
- s2python/frbc/frbc_usage_forecast_element.py,sha256=wQJCBoYFt2HXNaKNL3vK_FpTx5wXvRH03NOw_JWOal8,601
49
- s2python/frbc/rm.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
- s2python/generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
- s2python/generated/gen_s2.py,sha256=eg9J0pcWxCy0oXO8eq9AtoB-GONHZLNd8JANpMhKQCQ,63573
52
- s2python/ppbc/__init__.py,sha256=Rnfl_1n0gkc9mASSzBBMFEKYzJqugV5POtttjLWx_pI,665
53
- s2python/ppbc/ppbc_end_interruption_instruction.py,sha256=0ZXQATN8L35xm9-QWCU8ftnU1_Na9Oq3HzSYna_SMcU,1197
54
- s2python/ppbc/ppbc_power_profile_definition.py,sha256=3y5lw7eeDVVa6JecoGlZrqr6bcQRaDoLO9EISPle3ks,991
55
- s2python/ppbc/ppbc_power_profile_status.py,sha256=E-mNhdaPiuB6qFsG_F1ftIlwaxF75972nvIHKSpau5w,767
56
- s2python/ppbc/ppbc_power_sequence.py,sha256=DCeNstEAUvwCiN1bKDd85gE6pgiataJbwGui6pEk5_M,1158
57
- s2python/ppbc/ppbc_power_sequence_container.py,sha256=L0kD7zkJj9OBafzzA6zcUgFnn5wCx6VzlGoLX3GDf9U,829
58
- s2python/ppbc/ppbc_power_sequence_container_status.py,sha256=3Wq3NasIh_mLDEZvnNsgB8AY8CSd4qQKpPJxXwS7ZiA,1160
59
- s2python/ppbc/ppbc_power_sequence_element.py,sha256=sQMJlIRhMhj_crpaRFDWhbP4eCCw4haFq5sD-ptQsS0,797
60
- s2python/ppbc/ppbc_schedule_instruction.py,sha256=yHDO5omwKYV_P_v70D4gZ26K5MEIFFboX1sp3J6_atA,1098
61
- s2python/ppbc/ppbc_start_interruption_instruction.py,sha256=6Atu6E-Hfs8S2KIdHdGJd9F5gRFqZLNb-dpnphj8hNU,1219
62
- s2_python-0.4.1.dist-info/METADATA,sha256=CJILWeqNUp0VSPUdlLafySNPJag6SvoeYAPO9b5_cQs,3595
63
- s2_python-0.4.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
64
- s2_python-0.4.1.dist-info/entry_points.txt,sha256=feX-xmgJZgSe5-jxMgFKPKCJz4Ys3eQcGrsXsirNZyM,61
65
- s2_python-0.4.1.dist-info/top_level.txt,sha256=OLFq0oDhr77Mp-EYLEcWk5P3jvooOt4IHkTI5KYJMc8,9
66
- s2_python-0.4.1.dist-info/RECORD,,