From fb326a729179ce8889d478a7ae907ff8894191e2 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Wed, 19 Mar 2025 11:49:52 +0100 Subject: [PATCH 1/4] All imports for the all the controll types now pass the unit tests Signed-off-by: Vlad Iftime --- src/s2python/ddbc/ddbc_operation_mode.py | 2 +- src/s2python/message.py | 36 ++++++++++++++++++++++++ src/s2python/pebc/pebc_instruction.py | 2 +- tests/unit/message_test.py | 4 +-- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/s2python/ddbc/ddbc_operation_mode.py b/src/s2python/ddbc/ddbc_operation_mode.py index 7a2ebe2..13b35ff 100644 --- a/src/s2python/ddbc/ddbc_operation_mode.py +++ b/src/s2python/ddbc/ddbc_operation_mode.py @@ -20,7 +20,7 @@ class DDBCOperationMode(GenDDBCOperationMode, S2MessageComponent["DDBCOperationM # ? Id vs id id: uuid.UUID = GenDDBCOperationMode.model_fields["Id"] # type: ignore[assignment] power_ranges: List[PowerRange] = GenDDBCOperationMode.model_fields["power_ranges"] # type: ignore[assignment] - supply_ranges: List[NumberRange] = GenDDBCOperationMode.model_fields["supply_ranges"] # type: ignore[assignment] + supply_range: List[NumberRange] = GenDDBCOperationMode.model_fields["supply_range"] # type: ignore[assignment] abnormal_condition_only: bool = GenDDBCOperationMode.model_fields[ "abnormal_condition_only" ] # type: ignore[assignment] diff --git a/src/s2python/message.py b/src/s2python/message.py index 030d8e2..f88145d 100644 --- a/src/s2python/message.py +++ b/src/s2python/message.py @@ -28,7 +28,27 @@ PPBCScheduleInstruction, PPBCStartInterruptionInstruction, ) +from s2python.ddbc import ( + DDBCActuatorDescription, + DDBCActuatorStatus, + DDBCAverageDemandRateForecast, + DDBCAverageDemandRateForecastElement, + DDBCInstruction, + DDBCOperationMode, + DDBCSystemDescription, + DDBCTimerStatus, +) +from s2python.pebc import ( + PEBCAllowedLimitRange, + PEBCEnergyConstraint, + PEBCInstruction, + PEBCPowerConstraints, + PEBCPowerEnvelope, + PEBCPowerEnvelopeConsequenceType, + PEBCPowerEnvelopeElement, + PEBCPowerEnvelopeLimitType +) from s2python.common import ( Duration, Handshake, @@ -76,6 +96,22 @@ PPBCPowerSequenceElement, PPBCScheduleInstruction, PPBCStartInterruptionInstruction, + PEBCAllowedLimitRange, + PEBCEnergyConstraint, + PEBCInstruction, + PEBCPowerConstraints, + PEBCPowerEnvelope, + PEBCPowerEnvelopeConsequenceType, + PEBCPowerEnvelopeElement, + PEBCPowerEnvelopeLimitType, + DDBCActuatorDescription, + DDBCActuatorStatus, + DDBCAverageDemandRateForecast, + DDBCAverageDemandRateForecastElement, + DDBCInstruction, + DDBCOperationMode, + DDBCSystemDescription, + DDBCTimerStatus, Duration, Handshake, HandshakeResponse, diff --git a/src/s2python/pebc/pebc_instruction.py b/src/s2python/pebc/pebc_instruction.py index 6f8192b..1e0403a 100644 --- a/src/s2python/pebc/pebc_instruction.py +++ b/src/s2python/pebc/pebc_instruction.py @@ -24,4 +24,4 @@ class PEBCInstruction(GenPEBCInstruction, S2MessageComponent["PEBCInstruction"]) power_envelopes: List[PEBCPowerEnvelope] = [ GenPEBCInstruction.model_fields["power_envelopes"] # type: ignore[assignment] ] - abnormal_conditions: bool = GenPEBCInstruction.model_fields["abnormal_conditions"] # type: ignore[assignment] + abnormal_condition: bool = GenPEBCInstruction.model_fields["abnormal_condition"] # type: ignore[assignment] diff --git a/tests/unit/message_test.py b/tests/unit/message_test.py index 06e2798..88c776e 100644 --- a/tests/unit/message_test.py +++ b/tests/unit/message_test.py @@ -41,14 +41,14 @@ def _test_import_s2_messages(self, module_name): def test_import_s2_messages__common(self): self._test_import_s2_messages("s2python.common") - @unittest.skip("Work in progress") + # @unittest.skip("Work in progress") def test_import_s2_messages__ddbc(self): self._test_import_s2_messages("s2python.ddbc") def test_import_s2_messages__frbc(self): self._test_import_s2_messages("s2python.frbc") - @unittest.skip("Work in progress") + # @unittest.skip("Work in progress") def test_import_s2_messages__pebc(self): self._test_import_s2_messages("s2python.pebc") From 1001b45e54eba25a36777074169653c8169c6556 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Wed, 19 Mar 2025 12:10:21 +0100 Subject: [PATCH 2/4] Chore: fix tests Signed-off-by: Vlad Iftime --- src/s2python/message.py | 4 --- src/s2python/s2_connection.py | 57 +++++++++++------------------------ src/s2python/s2_parser.py | 4 +-- 3 files changed, 18 insertions(+), 47 deletions(-) diff --git a/src/s2python/message.py b/src/s2python/message.py index f88145d..5473756 100644 --- a/src/s2python/message.py +++ b/src/s2python/message.py @@ -45,9 +45,7 @@ PEBCInstruction, PEBCPowerConstraints, PEBCPowerEnvelope, - PEBCPowerEnvelopeConsequenceType, PEBCPowerEnvelopeElement, - PEBCPowerEnvelopeLimitType ) from s2python.common import ( Duration, @@ -101,9 +99,7 @@ PEBCInstruction, PEBCPowerConstraints, PEBCPowerEnvelope, - PEBCPowerEnvelopeConsequenceType, PEBCPowerEnvelopeElement, - PEBCPowerEnvelopeLimitType, DDBCActuatorDescription, DDBCActuatorStatus, DDBCAverageDemandRateForecast, diff --git a/src/s2python/s2_connection.py b/src/s2python/s2_connection.py index 1518083..385d03c 100644 --- a/src/s2python/s2_connection.py +++ b/src/s2python/s2_connection.py @@ -51,13 +51,9 @@ class AssetDetails: # pylint: disable=too-many-instance-attributes firmware_version: Optional[str] = None serial_number: Optional[str] = None - def to_resource_manager_details( - self, control_types: List[S2ControlType] - ) -> ResourceManagerDetails: + def to_resource_manager_details(self, control_types: List[S2ControlType]) -> ResourceManagerDetails: return ResourceManagerDetails( - available_control_types=[ - control_type.get_protocol_control_type() for control_type in control_types - ], + available_control_types=[control_type.get_protocol_control_type() for control_type in control_types], currency=self.currency, firmware_version=self.firmware_version, instruction_processing_delay=self.instruction_processing_delay, @@ -210,7 +206,7 @@ def __init__( # pylint: disable=too-many-arguments asset_details: AssetDetails, reconnect: bool = False, verify_certificate: bool = True, - bearer_token: Optional[str] = None + bearer_token: Optional[str] = None, ) -> None: self.url = url self.reconnect = reconnect @@ -254,8 +250,7 @@ def stop(self) -> None: """ if threading.current_thread() == self._thread: raise RuntimeError( - "Do not call stop from the thread running the S2 connection. This results in an " - "infinite block!" + "Do not call stop from the thread running the S2 connection. This results in an " "infinite block!" ) if self._eventloop.is_running(): asyncio.run_coroutine_threadsafe(self._do_stop(), self._eventloop).result() @@ -299,9 +294,7 @@ async def wait_till_connection_restart() -> None: self._eventloop.create_task(wait_till_connection_restart()), ] - (done, pending) = await asyncio.wait( - background_tasks, return_when=asyncio.FIRST_COMPLETED - ) + (done, pending) = await asyncio.wait(background_tasks, return_when=asyncio.FIRST_COMPLETED) if self._current_control_type: self._current_control_type.deactivate(self) self._current_control_type = None @@ -329,12 +322,12 @@ async def _connect_ws(self) -> None: # set up connection arguments for SSL and bearer token, if required connection_kwargs: Dict[str, Any] = {} if self.url.startswith("wss://") and not self._verify_certificate: - connection_kwargs['ssl'] = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - connection_kwargs['ssl'].check_hostname = False - connection_kwargs['ssl'].verify_mode = ssl.CERT_NONE + connection_kwargs["ssl"] = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + connection_kwargs["ssl"].check_hostname = False + connection_kwargs["ssl"].verify_mode = ssl.CERT_NONE if self._bearer_token: - connection_kwargs['additional_headers'] = {"Authorization": f"Bearer {self._bearer_token}"} + connection_kwargs["additional_headers"] = {"Authorization": f"Bearer {self._bearer_token}"} self.ws = await ws_connect(uri=self.url, **connection_kwargs) except (EOFError, OSError) as e: @@ -342,21 +335,15 @@ async def _connect_ws(self) -> None: async def _connect_as_rm(self) -> None: await self.send_msg_and_await_reception_status_async( - Handshake( - message_id=uuid.uuid4(), role=self.role, supported_protocol_versions=[S2_VERSION] - ) + Handshake(message_id=uuid.uuid4(), role=self.role, supported_protocol_versions=[S2_VERSION]) ) logger.debug("Send handshake to CEM. Expecting Handshake and HandshakeResponse from CEM.") await self._handle_received_messages() - async def handle_handshake( - self, _: "S2Connection", message: S2Message, send_okay: Awaitable[None] - ) -> None: + async def handle_handshake(self, _: "S2Connection", message: S2Message, send_okay: Awaitable[None]) -> None: if not isinstance(message, Handshake): - logger.error( - "Handler for Handshake received a message of the wrong type: %s", type(message) - ) + logger.error("Handler for Handshake received a message of the wrong type: %s", type(message)) return logger.debug( @@ -400,12 +387,8 @@ async def handle_select_control_type_as_rm( logger.debug("CEM selected control type %s. Activating control type.", message.control_type) - control_types_by_protocol_name = { - c.get_protocol_control_type(): c for c in self.control_types - } - selected_control_type: Optional[S2ControlType] = control_types_by_protocol_name.get( - message.control_type - ) + control_types_by_protocol_name = {c.get_protocol_control_type(): c for c in self.control_types} + selected_control_type: Optional[S2ControlType] = control_types_by_protocol_name.get(message.control_type) if self._current_control_type is not None: await self._eventloop.run_in_executor(None, self._current_control_type.deactivate, self) @@ -423,9 +406,7 @@ async def _receive_messages(self) -> None: to any calls of `send_msg_and_await_reception_status`. """ if self.ws is None: - raise RuntimeError( - "Cannot receive messages if websocket connection is not yet established." - ) + raise RuntimeError("Cannot receive messages if websocket connection is not yet established.") logger.info("S2 connection has started to receive messages.") @@ -469,9 +450,7 @@ async def _receive_messages(self) -> None: async def _send_and_forget(self, s2_msg: S2Message) -> None: if self.ws is None: - raise RuntimeError( - "Cannot send messages if websocket connection is not yet established." - ) + raise RuntimeError("Cannot send messages if websocket connection is not yet established.") json_msg = s2_msg.to_json() logger.debug("Sending message %s", json_msg) @@ -531,9 +510,7 @@ def send_msg_and_await_reception_status_sync( self, s2_msg: S2Message, timeout_reception_status: float = 5.0, raise_on_error: bool = True ) -> ReceptionStatus: return asyncio.run_coroutine_threadsafe( - self.send_msg_and_await_reception_status_async( - s2_msg, timeout_reception_status, raise_on_error - ), + self.send_msg_and_await_reception_status_async(s2_msg, timeout_reception_status, raise_on_error), self._eventloop, ).result() diff --git a/src/s2python/s2_parser.py b/src/s2python/s2_parser.py index 403503f..e5b7ea1 100644 --- a/src/s2python/s2_parser.py +++ b/src/s2python/s2_parser.py @@ -90,9 +90,7 @@ def parse_as_any_message(unparsed_message: Union[dict, str, bytes]) -> S2Message return TYPE_TO_MESSAGE_CLASS[message_type].model_validate(message_json) @staticmethod - def parse_as_message( - unparsed_message: Union[dict, str, bytes], as_message: Type[M] - ) -> M: + def parse_as_message(unparsed_message: Union[dict, str, bytes], as_message: Type[M]) -> M: """Parse the message to a specific S2 python message. :param unparsed_message: The message as a JSON-formatted string or as a JSON-parsed dictionary. From df50ef93e74ad9b2269b70b7d9a6eaf806e13cfa Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Wed, 19 Mar 2025 12:13:19 +0100 Subject: [PATCH 3/4] Chore: fix tests Signed-off-by: Vlad Iftime --- src/s2python/s2_connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/s2python/s2_connection.py b/src/s2python/s2_connection.py index 385d03c..cfc915e 100644 --- a/src/s2python/s2_connection.py +++ b/src/s2python/s2_connection.py @@ -250,7 +250,7 @@ def stop(self) -> None: """ if threading.current_thread() == self._thread: raise RuntimeError( - "Do not call stop from the thread running the S2 connection. This results in an " "infinite block!" + "Do not call stop from the thread running the S2 connection. This results in an infinite block!" ) if self._eventloop.is_running(): asyncio.run_coroutine_threadsafe(self._do_stop(), self._eventloop).result() From 9d2432ac33b780cfb111bfd0c54cb30db9986aa2 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 24 Mar 2025 10:43:11 +0100 Subject: [PATCH 4/4] fix: remove obsolete comments Signed-off-by: F.N. Claessen --- tests/unit/message_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/message_test.py b/tests/unit/message_test.py index 88c776e..fcd039e 100644 --- a/tests/unit/message_test.py +++ b/tests/unit/message_test.py @@ -41,14 +41,12 @@ def _test_import_s2_messages(self, module_name): def test_import_s2_messages__common(self): self._test_import_s2_messages("s2python.common") - # @unittest.skip("Work in progress") def test_import_s2_messages__ddbc(self): self._test_import_s2_messages("s2python.ddbc") def test_import_s2_messages__frbc(self): self._test_import_s2_messages("s2python.frbc") - # @unittest.skip("Work in progress") def test_import_s2_messages__pebc(self): self._test_import_s2_messages("s2python.pebc")