diff --git a/development_utilities/generate_s2_message_type_to_class.py b/development_utilities/generate_s2_message_type_to_class.py index 2d15c59..9ddceaa 100644 --- a/development_utilities/generate_s2_message_type_to_class.py +++ b/development_utilities/generate_s2_message_type_to_class.py @@ -5,14 +5,20 @@ all_members.sort(key=lambda t: t[0]) -print(""" +print( + """ from s2python.common import * from s2python.frbc import * -TYPE_TO_MESSAGE_CLASS = {""") +TYPE_TO_MESSAGE_CLASS = {""" +) for name, member in all_members: - if inspect.isclass(member) and hasattr(member, '__fields__') and ('message_type' in member.__fields__): + if ( + inspect.isclass(member) + and hasattr(member, "__fields__") + and ("message_type" in member.__fields__) + ): print(f" '{member.__fields__['message_type'].default}': {name},") print("}") diff --git a/development_utilities/get_all_messages.py b/development_utilities/get_all_messages.py index 4135eaa..5b70631 100644 --- a/development_utilities/get_all_messages.py +++ b/development_utilities/get_all_messages.py @@ -8,5 +8,9 @@ all_members.sort(key=lambda t: t[0]) for name, member in all_members: - if inspect.isclass(member) and issubclass(member, BaseModel) and "message_type" in member.__fields__: - print(f"{name},") \ No newline at end of file + if ( + inspect.isclass(member) + and issubclass(member, BaseModel) + and "message_type" in member.__fields__ + ): + print(f"{name},") diff --git a/src/s2python/common/number_range.py b/src/s2python/common/number_range.py index 6181362..5b6af75 100644 --- a/src/s2python/common/number_range.py +++ b/src/s2python/common/number_range.py @@ -1,6 +1,9 @@ from typing import Any -from s2python.validate_values_mixin import S2MessageComponent, catch_and_convert_exceptions +from s2python.validate_values_mixin import ( + S2MessageComponent, + catch_and_convert_exceptions, +) from s2python.generated.gen_s2 import NumberRange as GenNumberRange diff --git a/src/s2python/common/power_range.py b/src/s2python/common/power_range.py index b0f4d90..e9c1dd2 100644 --- a/src/s2python/common/power_range.py +++ b/src/s2python/common/power_range.py @@ -17,6 +17,8 @@ class PowerRange(GenPowerRange, S2MessageComponent): @model_validator(mode="after") def validate_start_end_order(self) -> Self: if self.start_of_range > self.end_of_range: - raise ValueError(self, "start_of_range should not be higher than end_of_range") + raise ValueError( + self, "start_of_range should not be higher than end_of_range" + ) return self diff --git a/src/s2python/frbc/frbc_actuator_description.py b/src/s2python/frbc/frbc_actuator_description.py index 7d05778..f666516 100644 --- a/src/s2python/frbc/frbc_actuator_description.py +++ b/src/s2python/frbc/frbc_actuator_description.py @@ -61,7 +61,9 @@ def validate_timers_unique_ids(self) -> Self: timer: Timer for timer in self.timers: if timer.id in ids: - raise ValueError(self, f"Id {timer.id} was found multiple times in 'timers'.") + raise ValueError( + self, f"Id {timer.id} was found multiple times in 'timers'." + ) ids.append(timer.id) return self @@ -113,7 +115,9 @@ def validate_operation_mode_elements_have_all_supported_commodities(self) -> Sel power_ranges_for_commodity = [ power_range for power_range in operation_mode_element.power_ranges - if commodity_has_quantity(commodity, power_range.commodity_quantity) + if commodity_has_quantity( + commodity, power_range.commodity_quantity + ) ] if len(power_ranges_for_commodity) > 1: diff --git a/src/s2python/frbc/frbc_fill_level_target_profile_element.py b/src/s2python/frbc/frbc_fill_level_target_profile_element.py index 5a37c90..3183745 100644 --- a/src/s2python/frbc/frbc_fill_level_target_profile_element.py +++ b/src/s2python/frbc/frbc_fill_level_target_profile_element.py @@ -8,7 +8,10 @@ from s2python.generated.gen_s2 import ( FRBCFillLevelTargetProfileElement as GenFRBCFillLevelTargetProfileElement, ) -from s2python.validate_values_mixin import catch_and_convert_exceptions, S2MessageComponent +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) @catch_and_convert_exceptions diff --git a/src/s2python/frbc/frbc_leakage_behaviour_element.py b/src/s2python/frbc/frbc_leakage_behaviour_element.py index dbe6e8f..594c594 100644 --- a/src/s2python/frbc/frbc_leakage_behaviour_element.py +++ b/src/s2python/frbc/frbc_leakage_behaviour_element.py @@ -4,8 +4,13 @@ from typing_extensions import Self from s2python.common import NumberRange -from s2python.generated.gen_s2 import FRBCLeakageBehaviourElement as GenFRBCLeakageBehaviourElement -from s2python.validate_values_mixin import catch_and_convert_exceptions, S2MessageComponent +from s2python.generated.gen_s2 import ( + FRBCLeakageBehaviourElement as GenFRBCLeakageBehaviourElement, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) @catch_and_convert_exceptions diff --git a/src/s2python/frbc/frbc_operation_mode.py b/src/s2python/frbc/frbc_operation_mode.py index 270cd7e..cbee3d4 100644 --- a/src/s2python/frbc/frbc_operation_mode.py +++ b/src/s2python/frbc/frbc_operation_mode.py @@ -34,8 +34,13 @@ def validate_contiguous_fill_levels_operation_mode_elements(self) -> Self: sorted_fill_level_ranges = list(elements_by_fill_level_range.keys()) sorted_fill_level_ranges.sort(key=lambda r: r.start_of_range) - for current_fill_level_range, next_fill_level_range in pairwise(sorted_fill_level_ranges): - if current_fill_level_range.end_of_range != next_fill_level_range.start_of_range: + for current_fill_level_range, next_fill_level_range in pairwise( + sorted_fill_level_ranges + ): + if ( + current_fill_level_range.end_of_range + != next_fill_level_range.start_of_range + ): raise ValueError( self, f"Elements with fill level ranges {current_fill_level_range} and " diff --git a/src/s2python/message.py b/src/s2python/message.py index 36031e9..3467a57 100644 --- a/src/s2python/message.py +++ b/src/s2python/message.py @@ -38,6 +38,13 @@ DDBCSystemDescription, DDBCTimerStatus, ) +from s2python.ombc import ( + OMBCInstruction, + OMBCOperationMode, + OMBCTimerStatus, + OMBCStatus, + OMBCSystemDescription, +) from s2python.pebc import ( PEBCAllowedLimitRange, @@ -82,6 +89,10 @@ FRBCSystemDescription, FRBCTimerStatus, FRBCUsageForecast, + OMBCSystemDescription, + OMBCStatus, + OMBCTimerStatus, + OMBCInstruction, PEBCPowerConstraints, PPBCEndInterruptionInstruction, PPBCPowerProfileDefinition, @@ -93,7 +104,6 @@ SelectControlType, SessionRequest, DDBCActuatorStatus, - FRBCInstruction, PEBCEnergyConstraint, PEBCInstruction, Handshake, @@ -115,6 +125,7 @@ FRBCOperationModeElement, FRBCStorageDescription, FRBCUsageForecastElement, + OMBCOperationMode, PEBCAllowedLimitRange, PEBCPowerEnvelope, PEBCPowerEnvelopeElement, diff --git a/src/s2python/ombc/__init__.py b/src/s2python/ombc/__init__.py new file mode 100644 index 0000000..623f04d --- /dev/null +++ b/src/s2python/ombc/__init__.py @@ -0,0 +1,5 @@ +from s2python.ombc.ombc_instruction import OMBCInstruction +from s2python.ombc.ombc_operation_mode import OMBCOperationMode +from s2python.ombc.ombc_status import OMBCStatus +from s2python.ombc.ombc_system_description import OMBCSystemDescription +from s2python.ombc.ombc_timer_status import OMBCTimerStatus diff --git a/src/s2python/ombc/ombc_instruction.py b/src/s2python/ombc/ombc_instruction.py new file mode 100644 index 0000000..6131916 --- /dev/null +++ b/src/s2python/ombc/ombc_instruction.py @@ -0,0 +1,19 @@ +import uuid + +from s2python.generated.gen_s2 import OMBCInstruction as GenOMBCInstruction +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) + + +@catch_and_convert_exceptions +class OMBCInstruction(GenOMBCInstruction, S2MessageComponent): + model_config = GenOMBCInstruction.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenOMBCInstruction.model_fields["id"] # type: ignore[assignment] + message_id: uuid.UUID = GenOMBCInstruction.model_fields["message_id"] # type: ignore[assignment] + abnormal_condition: bool = GenOMBCInstruction.model_fields["abnormal_condition"] # type: ignore[assignment] + operation_mode_factor: float = GenOMBCInstruction.model_fields["operation_mode_factor"] # type: ignore[assignment] + operation_mode_id: uuid.UUID = GenOMBCInstruction.model_fields["operation_mode_id"] # type: ignore[assignment] diff --git a/src/s2python/ombc/ombc_operation_mode.py b/src/s2python/ombc/ombc_operation_mode.py new file mode 100644 index 0000000..4c2b778 --- /dev/null +++ b/src/s2python/ombc/ombc_operation_mode.py @@ -0,0 +1,25 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import OMBCOperationMode as GenOMBCOperationMode +from s2python.common.power_range import PowerRange + + +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) + + +@catch_and_convert_exceptions +class OMBCOperationMode(GenOMBCOperationMode, S2MessageComponent): + model_config = GenOMBCOperationMode.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenOMBCOperationMode.model_fields["id"] # type: ignore[assignment] + power_ranges: List[PowerRange] = GenOMBCOperationMode.model_fields[ + "power_ranges" + ] # type: ignore[assignment] + abnormal_condition_only: bool = GenOMBCOperationMode.model_fields[ + "abnormal_condition_only" + ] # type: ignore[assignment] diff --git a/src/s2python/ombc/ombc_status.py b/src/s2python/ombc/ombc_status.py new file mode 100644 index 0000000..c782c25 --- /dev/null +++ b/src/s2python/ombc/ombc_status.py @@ -0,0 +1,17 @@ +import uuid + +from s2python.generated.gen_s2 import OMBCStatus as GenOMBCStatus + +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) + + +@catch_and_convert_exceptions +class OMBCStatus(GenOMBCStatus, S2MessageComponent): + model_config = GenOMBCStatus.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenOMBCStatus.model_fields["message_id"] # type: ignore[assignment] + operation_mode_factor: float = GenOMBCStatus.model_fields["operation_mode_factor"] # type: ignore[assignment] diff --git a/src/s2python/ombc/ombc_system_description.py b/src/s2python/ombc/ombc_system_description.py new file mode 100644 index 0000000..efb4826 --- /dev/null +++ b/src/s2python/ombc/ombc_system_description.py @@ -0,0 +1,25 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import OMBCSystemDescription as GenOMBCSystemDescription +from s2python.ombc.ombc_operation_mode import OMBCOperationMode +from s2python.common.transition import Transition +from s2python.common.timer import Timer + +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) + + +@catch_and_convert_exceptions +class OMBCSystemDescription(GenOMBCSystemDescription, S2MessageComponent): + model_config = GenOMBCSystemDescription.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenOMBCSystemDescription.model_fields["message_id"] # type: ignore[assignment] + operation_modes: List[OMBCOperationMode] = GenOMBCSystemDescription.model_fields[ + "operation_modes" + ] # type: ignore[assignment] + transitions: List[Transition] = GenOMBCSystemDescription.model_fields["transitions"] # type: ignore[assignment] + timers: List[Timer] = GenOMBCSystemDescription.model_fields["timers"] # type: ignore[assignment] diff --git a/src/s2python/ombc/ombc_timer_status.py b/src/s2python/ombc/ombc_timer_status.py new file mode 100644 index 0000000..906ea7d --- /dev/null +++ b/src/s2python/ombc/ombc_timer_status.py @@ -0,0 +1,17 @@ +from uuid import UUID + +from s2python.generated.gen_s2 import OMBCTimerStatus as GenOMBCTimerStatus + +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2MessageComponent, +) + + +@catch_and_convert_exceptions +class OMBCTimerStatus(GenOMBCTimerStatus, S2MessageComponent): + model_config = GenOMBCTimerStatus.model_config + model_config["validate_assignment"] = True + + message_id: UUID = GenOMBCTimerStatus.model_fields["message_id"] # type: ignore[assignment] + timer_id: UUID = GenOMBCTimerStatus.model_fields["timer_id"] # type: ignore[assignment] diff --git a/src/s2python/s2_connection.py b/src/s2python/s2_connection.py index 897344c..ba497d4 100644 --- a/src/s2python/s2_connection.py +++ b/src/s2python/s2_connection.py @@ -9,7 +9,10 @@ from typing import Any, Optional, List, Type, Dict, Callable, Awaitable, Union import websockets -from websockets.asyncio.client import ClientConnection as WSConnection, connect as ws_connect +from websockets.asyncio.client import ( + ClientConnection as WSConnection, + connect as ws_connect, +) from s2python.common import ( ReceptionStatusValues, @@ -56,7 +59,8 @@ def to_resource_manager_details( ) -> ResourceManagerDetails: return ResourceManagerDetails( available_control_types=[ - control_type.get_protocol_control_type() for control_type in control_types + control_type.get_protocol_control_type() + for control_type in control_types ], currency=self.currency, firmware_version=self.firmware_version, @@ -171,7 +175,9 @@ def do_message() -> None: type(msg), ) - def register_handler(self, msg_type: Type[S2Message], handler: S2MessageHandler) -> None: + def register_handler( + self, msg_type: Type[S2Message], handler: S2MessageHandler + ) -> None: """Register a coroutine function or a normal function as the handler for a specific S2 message type. :param msg_type: The S2 message type to attach the handler to. @@ -228,7 +234,9 @@ def __init__( # pylint: disable=too-many-arguments self.asset_details = asset_details self._verify_certificate = verify_certificate - self._handlers.register_handler(SelectControlType, self.handle_select_control_type_as_rm) + self._handlers.register_handler( + SelectControlType, self.handle_select_control_type_as_rm + ) self._handlers.register_handler(Handshake, self.handle_handshake) self._handlers.register_handler(HandshakeResponse, self.handle_handshake_response_as_rm) self._bearer_token = bearer_token @@ -310,7 +318,10 @@ async def wait_till_connection_restart() -> None: await task except asyncio.CancelledError: pass - except (websockets.ConnectionClosedError, websockets.ConnectionClosedOK): + except ( + websockets.ConnectionClosedError, + websockets.ConnectionClosedOK, + ): logger.info("The other party closed the websocket connection.") for task in pending: @@ -344,10 +355,14 @@ 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] + message_id=uuid.uuid4(), + role=self.role, + supported_protocol_versions=[S2_VERSION], ) ) - logger.debug("Send handshake to CEM. Expecting Handshake and HandshakeResponse from CEM.") + logger.debug( + "Send handshake to CEM. Expecting Handshake and HandshakeResponse from CEM." + ) await self._handle_received_messages() @@ -356,7 +371,8 @@ async def handle_handshake( ) -> None: if not isinstance(message, Handshake): logger.error( - "Handler for Handshake received a message of the wrong type: %s", type(message) + "Handler for Handshake received a message of the wrong type: %s", + type(message), ) return @@ -379,7 +395,9 @@ async def handle_handshake_response_as_rm( logger.debug("Received HandshakeResponse %s", message.to_json()) - logger.debug("CEM selected to use version %s", message.selected_protocol_version) + logger.debug( + "CEM selected to use version %s", message.selected_protocol_version + ) await send_okay logger.debug("Handshake complete. Sending first ResourceManagerDetails.") @@ -399,22 +417,29 @@ async def handle_select_control_type_as_rm( await send_okay - logger.debug("CEM selected control type %s. Activating control type.", message.control_type) + 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 + 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) + await self._eventloop.run_in_executor( + None, self._current_control_type.deactivate, self + ) self._current_control_type = selected_control_type if self._current_control_type is not None: - await self._eventloop.run_in_executor(None, self._current_control_type.activate, self) + await self._eventloop.run_in_executor( + None, self._current_control_type.activate, self + ) self._current_control_type.register_handlers(self._handlers) async def _receive_messages(self) -> None: @@ -485,7 +510,9 @@ async def _send_and_forget(self, s2_msg: S2Message) -> None: async def respond_with_reception_status( self, subject_message_id: uuid.UUID, status: ReceptionStatusValues, diagnostic_label: str ) -> None: - logger.debug("Responding to message %s with status %s", subject_message_id, status) + logger.debug( + "Responding to message %s with status %s", subject_message_id, status + ) await self._send_and_forget( ReceptionStatus( subject_message_id=subject_message_id, @@ -498,12 +525,17 @@ def respond_with_reception_status_sync( self, subject_message_id: uuid.UUID, status: ReceptionStatusValues, diagnostic_label: str ) -> None: asyncio.run_coroutine_threadsafe( - self.respond_with_reception_status(subject_message_id, status, diagnostic_label), + self.respond_with_reception_status( + subject_message_id, status, diagnostic_label + ), self._eventloop, ).result() async def send_msg_and_await_reception_status_async( - self, s2_msg: S2Message, timeout_reception_status: float = 5.0, raise_on_error: bool = True + self, + s2_msg: S2Message, + timeout_reception_status: float = 5.0, + raise_on_error: bool = True, ) -> ReceptionStatus: await self._send_and_forget(s2_msg) logger.debug( @@ -524,12 +556,17 @@ async def send_msg_and_await_reception_status_async( raise if reception_status.status != ReceptionStatusValues.OK and raise_on_error: - raise RuntimeError(f"ReceptionStatus was not OK but rather {reception_status.status}") + raise RuntimeError( + f"ReceptionStatus was not OK but rather {reception_status.status}" + ) return reception_status def send_msg_and_await_reception_status_sync( - self, s2_msg: S2Message, timeout_reception_status: float = 5.0, raise_on_error: bool = True + 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( diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index 8463d04..135f775 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -4,6 +4,7 @@ from s2python.common import ControlType as ProtocolControlType from s2python.frbc import FRBCInstruction from s2python.ppbc import PPBCScheduleInstruction +from s2python.ombc import OMBCInstruction from s2python.message import S2Message if typing.TYPE_CHECKING: @@ -66,6 +67,26 @@ def deactivate(self, conn: "S2Connection") -> None: """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type.""" +class OMBCControlType(S2ControlType): + def get_protocol_control_type(self) -> ProtocolControlType: + return ProtocolControlType.OPERATION_MODE_BASED_CONTROL + + def register_handlers(self, handlers: "MessageHandlers") -> None: + handlers.register_handler(OMBCInstruction, self.handle_instruction) + + @abc.abstractmethod + def handle_instruction( + self, conn: "S2Connection", msg: S2Message, send_okay: typing.Callable[[], None] + ) -> None: ... + + @abc.abstractmethod + def activate(self, conn: "S2Connection") -> None: + """Overwrite with the actual dctivation logic of your Resource Manager for this particular control type.""" + + @abc.abstractmethod + def deactivate(self, conn: "S2Connection") -> None: + """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type.""" + class PEBCControlType(S2ControlType): def get_protocol_control_type(self) -> ProtocolControlType: diff --git a/src/s2python/s2_validation_error.py b/src/s2python/s2_validation_error.py index 8ab7664..dc43419 100644 --- a/src/s2python/s2_validation_error.py +++ b/src/s2python/s2_validation_error.py @@ -10,4 +10,6 @@ class S2ValidationError(Exception): class_: Optional[Type] obj: object msg: str - pydantic_validation_error: Union[ValidationErrorV1, ValidationError, TypeError, None] + pydantic_validation_error: Union[ + ValidationErrorV1, ValidationError, TypeError, None + ] diff --git a/tests/unit/common/handshake_test.py b/tests/unit/common/handshake_test.py index e4d78e9..715e360 100644 --- a/tests/unit/common/handshake_test.py +++ b/tests/unit/common/handshake_test.py @@ -17,7 +17,9 @@ def test__from_json__happy_path(self): handshake = Handshake.from_json(json_str) # Assert - self.assertEqual(handshake.message_id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3")) + self.assertEqual( + handshake.message_id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3") + ) self.assertEqual(handshake.role, EnergyManagementRole.RM) self.assertEqual(handshake.supported_protocol_versions, ["v1", "v2"]) diff --git a/tests/unit/common/instruction_status_update_test.py b/tests/unit/common/instruction_status_update_test.py index ef9283a..91b81f2 100644 --- a/tests/unit/common/instruction_status_update_test.py +++ b/tests/unit/common/instruction_status_update_test.py @@ -30,7 +30,9 @@ def test__from_json__happy_path(self): instruction_status_update.instruction_id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced4"), ) - self.assertEqual(instruction_status_update.status_type, InstructionStatus.SUCCEEDED) + self.assertEqual( + instruction_status_update.status_type, InstructionStatus.SUCCEEDED + ) self.assertEqual( instruction_status_update.timestamp, datetime(2023, 8, 2, 12, 48, 42, tzinfo=offset(timedelta(hours=1))), @@ -42,7 +44,9 @@ def test__to_json__happy_path(self): message_id=uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3"), instruction_id=uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced4"), status_type=InstructionStatus.SUCCEEDED, - timestamp=timezone("Europe/Amsterdam").localize(datetime(2023, 8, 2, 12, 48, 42)), + timestamp=timezone("Europe/Amsterdam").localize( + datetime(2023, 8, 2, 12, 48, 42) + ), ) # Act diff --git a/tests/unit/common/number_range_test.py b/tests/unit/common/number_range_test.py index eedb729..e597113 100644 --- a/tests/unit/common/number_range_test.py +++ b/tests/unit/common/number_range_test.py @@ -27,7 +27,9 @@ def test__from_json__happy_path_equals(self): number_range = NumberRange.from_json(json_str) # Assert - self.assertEqual(number_range, NumberRange(start_of_range=4.0, end_of_range=5.0)) + self.assertEqual( + number_range, NumberRange(start_of_range=4.0, end_of_range=5.0) + ) def test__from_json__format_validation_error(self): # Arrange @@ -45,7 +47,9 @@ def test__from_json__end_of_range_smaller_than_start(self): number_range = NumberRange.from_json(json_str) # Assert - self.assertEqual(number_range, NumberRange(start_of_range=6.0, end_of_range=5.0)) + self.assertEqual( + number_range, NumberRange(start_of_range=6.0, end_of_range=5.0) + ) def test__to_json__happy_path(self): # Arrange diff --git a/tests/unit/common/power_forecast_value_test.py b/tests/unit/common/power_forecast_value_test.py index f5949fb..4a94e0e 100644 --- a/tests/unit/common/power_forecast_value_test.py +++ b/tests/unit/common/power_forecast_value_test.py @@ -17,10 +17,14 @@ def test__from_json__happy_path(self): "value_upper_limit": 600}""" # Act - power_forecast_value: PowerForecastValue = PowerForecastValue.from_json(json_str) + power_forecast_value: PowerForecastValue = PowerForecastValue.from_json( + json_str + ) # Assert - self.assertEqual(power_forecast_value.commodity_quantity, CommodityQuantity.HEAT_FLOW_RATE) + self.assertEqual( + power_forecast_value.commodity_quantity, CommodityQuantity.HEAT_FLOW_RATE + ) self.assertEqual(power_forecast_value.value_lower_limit, 450.3) self.assertEqual(power_forecast_value.value_lower_95PPR, 470.4) self.assertEqual(power_forecast_value.value_lower_68PPR, 480.3) @@ -68,5 +72,8 @@ def test__to_json__only_value_expected(self): json_str = power_forecast_value.to_json() # Assert - expected_json = {"commodity_quantity": "HEAT.TEMPERATURE", "value_expected": 500.2} + expected_json = { + "commodity_quantity": "HEAT.TEMPERATURE", + "value_expected": 500.2, + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/common/power_measurement_test.py b/tests/unit/common/power_measurement_test.py index 037fa07..a8c555c 100644 --- a/tests/unit/common/power_measurement_test.py +++ b/tests/unit/common/power_measurement_test.py @@ -30,15 +30,25 @@ def test__from_json__happy_path(self): ) self.assertEqual( power_measurement.values, - [PowerValue(commodity_quantity=CommodityQuantity.OIL_FLOW_RATE, value=42.42)], + [ + PowerValue( + commodity_quantity=CommodityQuantity.OIL_FLOW_RATE, value=42.42 + ) + ], ) def test__to_json__happy_path(self): # Arrange power_measurement = PowerMeasurement( - values=[PowerValue(commodity_quantity=CommodityQuantity.OIL_FLOW_RATE, value=42.42)], + values=[ + PowerValue( + commodity_quantity=CommodityQuantity.OIL_FLOW_RATE, value=42.42 + ) + ], message_id=uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced8"), - measurement_timestamp=datetime(2023, 8, 3, 12, 48, 42, tzinfo=offset(timedelta(hours=1))), + measurement_timestamp=datetime( + 2023, 8, 3, 12, 48, 42, tzinfo=offset(timedelta(hours=1)) + ), ) # Act diff --git a/tests/unit/common/transition_test.py b/tests/unit/common/transition_test.py index ed91ad5..c81dbd2 100644 --- a/tests/unit/common/transition_test.py +++ b/tests/unit/common/transition_test.py @@ -25,9 +25,15 @@ def test__from_json__happy_path_full(self): transition: Transition = Transition.from_json(json_str) # Assert - self.assertEqual(transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3")) - self.assertEqual(transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2")) - self.assertEqual(transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1")) + self.assertEqual( + transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3") + ) + self.assertEqual( + transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2") + ) + self.assertEqual( + transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1") + ) self.assertEqual( transition.start_timers, [ @@ -41,7 +47,9 @@ def test__from_json__happy_path_full(self): ) self.assertEqual(transition.transition_costs, 4.3) assert transition.transition_duration is not None - self.assertEqual(transition.transition_duration.to_timedelta(), timedelta(seconds=1.5)) + self.assertEqual( + transition.transition_duration.to_timedelta(), timedelta(seconds=1.5) + ) self.assertEqual(transition.abnormal_condition_only, False) def test__from_json__happy_path_min(self): @@ -59,9 +67,15 @@ def test__from_json__happy_path_min(self): transition: Transition = Transition.from_json(json_str) # Assert - self.assertEqual(transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3")) - self.assertEqual(transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2")) - self.assertEqual(transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1")) + self.assertEqual( + transition.id, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3") + ) + self.assertEqual( + transition.from_, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced2") + ) + self.assertEqual( + transition.to, uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1") + ) self.assertEqual(transition.start_timers, []) self.assertEqual(transition.blocking_timers, []) self.assertEqual(transition.transition_costs, None) @@ -106,7 +120,9 @@ def test__to_json__happy_path(self): "to": uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced1"), "start_timers": [], "blocking_timers": [], - "transition_duration": Duration.from_timedelta(timedelta(minutes=1, seconds=1)), + "transition_duration": Duration.from_timedelta( + timedelta(minutes=1, seconds=1) + ), "abnormal_condition_only": False, } ) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py index b3bb5c8..2538381 100644 --- a/tests/unit/frbc/frbc_actuator_status_test.py +++ b/tests/unit/frbc/frbc_actuator_status_test.py @@ -65,7 +65,9 @@ def test__to_json__happy_path_full(self): message_id=uuid.UUID("07f3d559-63c5-4369-a9e0-deed4195f651"), message_type="FRBC.ActuatorStatus", operation_mode_factor=6919.960475850124, - previous_operation_mode_id=uuid.UUID("2ed8f7de-cbaa-4cab-9d25-6792317aa284"), + previous_operation_mode_id=uuid.UUID( + "2ed8f7de-cbaa-4cab-9d25-6792317aa284" + ), transition_timestamp=datetime( year=2020, month=1, diff --git a/tests/unit/frbc/frbc_fill_level_target_profile_element_test.py b/tests/unit/frbc/frbc_fill_level_target_profile_element_test.py index 62f51df..f3ea375 100644 --- a/tests/unit/frbc/frbc_fill_level_target_profile_element_test.py +++ b/tests/unit/frbc/frbc_fill_level_target_profile_element_test.py @@ -21,8 +21,8 @@ def test__from_json__happy_path_full(self): """ # Act - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json( - json_str + frbc_fill_level_target_profile_element = ( + FRBCFillLevelTargetProfileElement.from_json(json_str) ) # Assert diff --git a/tests/unit/frbc/frbc_fill_level_target_profile_test.py b/tests/unit/frbc/frbc_fill_level_target_profile_test.py index 046a0bc..5f2f2b3 100644 --- a/tests/unit/frbc/frbc_fill_level_target_profile_test.py +++ b/tests/unit/frbc/frbc_fill_level_target_profile_test.py @@ -47,7 +47,9 @@ def test__from_json__happy_path_full(self): frbc_fill_level_target_profile.message_id, uuid.UUID("04a6c8af-ca8d-420c-9c11-e96a70fe82b1"), ) - self.assertEqual(frbc_fill_level_target_profile.message_type, "FRBC.FillLevelTargetProfile") + self.assertEqual( + frbc_fill_level_target_profile.message_type, "FRBC.FillLevelTargetProfile" + ) self.assertEqual( frbc_fill_level_target_profile.start_time, datetime( diff --git a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py index 79f480a..08a5364 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py @@ -27,9 +27,13 @@ def test__from_json__happy_path_full(self): # Assert self.assertEqual( frbc_leakage_behaviour_element.fill_level_range, - NumberRange(end_of_range=40192.498918818455, start_of_range=29234.82582981918), + NumberRange( + end_of_range=40192.498918818455, start_of_range=29234.82582981918 + ), + ) + self.assertEqual( + frbc_leakage_behaviour_element.leakage_rate, 1170.4041485129987 ) - self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 1170.4041485129987) def test__to_json__happy_path_full(self): # Arrange diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py index 9950ea7..0ad8bfd 100644 --- a/tests/unit/frbc/frbc_system_description_test.py +++ b/tests/unit/frbc/frbc_system_description_test.py @@ -151,7 +151,9 @@ def test__from_json__happy_path_full(self): timers=[ Timer( diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta(timedelta(milliseconds=14099)), + duration=Duration.from_timedelta( + timedelta(milliseconds=14099) + ), id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), ) ], @@ -169,7 +171,9 @@ def test__from_json__happy_path_full(self): FRBCStorageDescription( diagnostic_label="some-test-string8418", fill_level_label="some-test-string9512", - fill_level_range=NumberRange(end_of_range=20876.752745956997, start_of_range=18324.0229135081), + fill_level_range=NumberRange( + end_of_range=20876.752745956997, start_of_range=18324.0229135081 + ), provides_fill_level_target_profile=False, provides_leakage_behaviour=True, provides_usage_forecast=False, @@ -244,7 +248,9 @@ def test__to_json__happy_path_full(self): timers=[ Timer( diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta(timedelta(milliseconds=14099)), + duration=Duration.from_timedelta( + timedelta(milliseconds=14099) + ), id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), ) ], @@ -256,7 +262,9 @@ def test__to_json__happy_path_full(self): storage=FRBCStorageDescription( diagnostic_label="some-test-string8418", fill_level_label="some-test-string9512", - fill_level_range=NumberRange(end_of_range=20876.752745956997, start_of_range=18324.0229135081), + fill_level_range=NumberRange( + end_of_range=20876.752745956997, start_of_range=18324.0229135081 + ), provides_fill_level_target_profile=False, provides_leakage_behaviour=True, provides_usage_forecast=False, diff --git a/tests/unit/reception_status_awaiter_test.py b/tests/unit/reception_status_awaiter_test.py index a2f6261..fb06630 100644 --- a/tests/unit/reception_status_awaiter_test.py +++ b/tests/unit/reception_status_awaiter_test.py @@ -29,7 +29,9 @@ async def test__wait_for_reception_status__receive_while_waiting(self): ) # Act - wait_task = asyncio.create_task(awaiter.wait_for_reception_status(message_id, 1.0)) + wait_task = asyncio.create_task( + awaiter.wait_for_reception_status(message_id, 1.0) + ) should_be_waiting_still = not wait_task.done() await awaiter.receive_reception_status(s2_reception_status) await wait_task @@ -53,7 +55,9 @@ async def test__wait_for_reception_status__already_received(self): # Act await awaiter.receive_reception_status(s2_reception_status) - received_s2_reception_status = await awaiter.wait_for_reception_status(message_id, 1.0) + received_s2_reception_status = await awaiter.wait_for_reception_status( + message_id, 1.0 + ) # Assert expected_s2_reception_status = ReceptionStatus( # pyright: ignore[reportCallIssue] @@ -70,8 +74,12 @@ async def test__wait_for_reception_status__multiple_receive_while_waiting(self): ) # Act - wait_task_1 = asyncio.create_task(awaiter.wait_for_reception_status(message_id, 1.0)) - wait_task_2 = asyncio.create_task(awaiter.wait_for_reception_status(message_id, 1.0)) + wait_task_1 = asyncio.create_task( + awaiter.wait_for_reception_status(message_id, 1.0) + ) + wait_task_2 = asyncio.create_task( + awaiter.wait_for_reception_status(message_id, 1.0) + ) should_be_waiting_still_1 = not wait_task_1.done() should_be_waiting_still_2 = not wait_task_2.done() await awaiter.receive_reception_status(s2_reception_status)