From 2e4aefc45ffb4bcee588701ed9b920938f56618a Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 13 Dec 2024 14:10:27 +0100 Subject: [PATCH 01/27] PPBC --- src/s2python/s2_control_type.py | 20 ++++++++++++++++++++ src/s2python/s2_parser.py | 11 +++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index f9a4545..547ead7 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -3,6 +3,7 @@ from s2python.common import ControlType as ProtocolControlType from s2python.frbc import FRBCInstruction +from s2python.ppbc import PPBCScheduleInstruction from s2python.validate_values_mixin import S2Message if typing.TYPE_CHECKING: @@ -42,6 +43,25 @@ def activate(self, conn: "S2Connection") -> None: ... def deactivate(self, conn: "S2Connection") -> None: ... +class PPBCControlType(S2ControlType): + def get_protocol_control_type(self) -> ProtocolControlType: + return ProtocolControlType.POWER_PROFILE_BASED_CONTROL + + def register_handlers(self, handlers: "MessageHandlers") -> None: + handlers.register_handler(PPBCScheduleInstruction, 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: ... + + @abc.abstractmethod + def deactivate(self, conn: "S2Connection") -> None: ... + + class NoControlControlType(S2ControlType): def get_protocol_control_type(self) -> ProtocolControlType: return ProtocolControlType.NOT_CONTROLABLE diff --git a/src/s2python/s2_parser.py b/src/s2python/s2_parser.py index 906a286..e1a5c43 100644 --- a/src/s2python/s2_parser.py +++ b/src/s2python/s2_parser.py @@ -24,6 +24,8 @@ FRBCTimerStatus, FRBCUsageForecast, ) +from s2python.ppbc import PPBCScheduleInstruction + from s2python.validate_values_mixin import S2Message from s2python.s2_validation_error import S2ValidationError @@ -44,6 +46,7 @@ "FRBC.SystemDescription": FRBCSystemDescription, "FRBC.TimerStatus": FRBCTimerStatus, "FRBC.UsageForecast": FRBCUsageForecast, + "PPBC.ScheduleInstruction": PPBCScheduleInstruction, "Handshake": Handshake, "HandshakeResponse": HandshakeResponse, "InstructionStatusUpdate": InstructionStatusUpdate, @@ -86,7 +89,9 @@ 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. @@ -98,7 +103,9 @@ def parse_as_message(unparsed_message: Union[dict, str, bytes], as_message: Type return as_message.from_dict(message_json) @staticmethod - def parse_message_type(unparsed_message: Union[dict, str, bytes]) -> Optional[S2MessageType]: + def parse_message_type( + unparsed_message: Union[dict, str, bytes], + ) -> Optional[S2MessageType]: """Parse only the message type from the unparsed message. This is useful to call before `parse_as_message` to retrieve the message type and allows for strictly-typed From ced07d2093693472ecd8ec2089251a5291bfac5e Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 13 Dec 2024 14:17:34 +0100 Subject: [PATCH 02/27] Generated classes --- build/lib/s2python/__init__.py | 10 + build/lib/s2python/common/__init__.py | 32 + build/lib/s2python/common/duration.py | 22 + build/lib/s2python/common/handshake.py | 15 + .../lib/s2python/common/handshake_response.py | 15 + .../common/instruction_status_update.py | 18 + build/lib/s2python/common/number_range.py | 22 + build/lib/s2python/common/power_forecast.py | 18 + .../s2python/common/power_forecast_element.py | 20 + .../s2python/common/power_forecast_value.py | 11 + .../lib/s2python/common/power_measurement.py | 18 + build/lib/s2python/common/power_range.py | 22 + build/lib/s2python/common/power_value.py | 11 + build/lib/s2python/common/reception_status.py | 15 + .../common/resource_manager_details.py | 25 + build/lib/s2python/common/revoke_object.py | 16 + build/lib/s2python/common/role.py | 11 + .../s2python/common/select_control_type.py | 15 + build/lib/s2python/common/session_request.py | 15 + build/lib/s2python/common/support.py | 27 + build/lib/s2python/common/timer.py | 17 + build/lib/s2python/common/transition.py | 24 + build/lib/s2python/frbc/__init__.py | 17 + .../frbc/frbc_actuator_description.py | 143 ++ .../lib/s2python/frbc/frbc_actuator_status.py | 23 + .../frbc/frbc_fill_level_target_profile.py | 24 + .../frbc_fill_level_target_profile_element.py | 34 + build/lib/s2python/frbc/frbc_instruction.py | 18 + .../s2python/frbc/frbc_leakage_behaviour.py | 20 + .../frbc/frbc_leakage_behaviour_element.py | 30 + .../lib/s2python/frbc/frbc_operation_mode.py | 42 + .../frbc/frbc_operation_mode_element.py | 27 + .../s2python/frbc/frbc_storage_description.py | 18 + .../lib/s2python/frbc/frbc_storage_status.py | 15 + .../s2python/frbc/frbc_system_description.py | 22 + build/lib/s2python/frbc/frbc_timer_status.py | 17 + .../lib/s2python/frbc/frbc_usage_forecast.py | 18 + .../frbc/frbc_usage_forecast_element.py | 17 + build/lib/s2python/frbc/rm.py | 0 build/lib/s2python/generated/__init__.py | 0 build/lib/s2python/generated/gen_s2.py | 1611 +++++++++++++++++ build/lib/s2python/ppbc/__init__.py | 1 + .../ppbc/ppbc_schedule_instruction.py | 33 + build/lib/s2python/py.typed | 0 .../lib/s2python/reception_status_awaiter.py | 60 + build/lib/s2python/s2_connection.py | 526 ++++++ build/lib/s2python/s2_control_type.py | 76 + build/lib/s2python/s2_parser.py | 120 ++ build/lib/s2python/s2_validation_error.py | 13 + build/lib/s2python/utils.py | 8 + build/lib/s2python/validate_values_mixin.py | 70 + build/lib/s2python/version.py | 3 + src/s2python/ppbc/__init__.py | 1 + .../ppbc/ppbc_schedule_instruction.py | 33 + 54 files changed, 3439 insertions(+) create mode 100644 build/lib/s2python/__init__.py create mode 100644 build/lib/s2python/common/__init__.py create mode 100644 build/lib/s2python/common/duration.py create mode 100644 build/lib/s2python/common/handshake.py create mode 100644 build/lib/s2python/common/handshake_response.py create mode 100644 build/lib/s2python/common/instruction_status_update.py create mode 100644 build/lib/s2python/common/number_range.py create mode 100644 build/lib/s2python/common/power_forecast.py create mode 100644 build/lib/s2python/common/power_forecast_element.py create mode 100644 build/lib/s2python/common/power_forecast_value.py create mode 100644 build/lib/s2python/common/power_measurement.py create mode 100644 build/lib/s2python/common/power_range.py create mode 100644 build/lib/s2python/common/power_value.py create mode 100644 build/lib/s2python/common/reception_status.py create mode 100644 build/lib/s2python/common/resource_manager_details.py create mode 100644 build/lib/s2python/common/revoke_object.py create mode 100644 build/lib/s2python/common/role.py create mode 100644 build/lib/s2python/common/select_control_type.py create mode 100644 build/lib/s2python/common/session_request.py create mode 100644 build/lib/s2python/common/support.py create mode 100644 build/lib/s2python/common/timer.py create mode 100644 build/lib/s2python/common/transition.py create mode 100644 build/lib/s2python/frbc/__init__.py create mode 100644 build/lib/s2python/frbc/frbc_actuator_description.py create mode 100644 build/lib/s2python/frbc/frbc_actuator_status.py create mode 100644 build/lib/s2python/frbc/frbc_fill_level_target_profile.py create mode 100644 build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py create mode 100644 build/lib/s2python/frbc/frbc_instruction.py create mode 100644 build/lib/s2python/frbc/frbc_leakage_behaviour.py create mode 100644 build/lib/s2python/frbc/frbc_leakage_behaviour_element.py create mode 100644 build/lib/s2python/frbc/frbc_operation_mode.py create mode 100644 build/lib/s2python/frbc/frbc_operation_mode_element.py create mode 100644 build/lib/s2python/frbc/frbc_storage_description.py create mode 100644 build/lib/s2python/frbc/frbc_storage_status.py create mode 100644 build/lib/s2python/frbc/frbc_system_description.py create mode 100644 build/lib/s2python/frbc/frbc_timer_status.py create mode 100644 build/lib/s2python/frbc/frbc_usage_forecast.py create mode 100644 build/lib/s2python/frbc/frbc_usage_forecast_element.py create mode 100644 build/lib/s2python/frbc/rm.py create mode 100644 build/lib/s2python/generated/__init__.py create mode 100644 build/lib/s2python/generated/gen_s2.py create mode 100644 build/lib/s2python/ppbc/__init__.py create mode 100644 build/lib/s2python/ppbc/ppbc_schedule_instruction.py create mode 100644 build/lib/s2python/py.typed create mode 100644 build/lib/s2python/reception_status_awaiter.py create mode 100644 build/lib/s2python/s2_connection.py create mode 100644 build/lib/s2python/s2_control_type.py create mode 100644 build/lib/s2python/s2_parser.py create mode 100644 build/lib/s2python/s2_validation_error.py create mode 100644 build/lib/s2python/utils.py create mode 100644 build/lib/s2python/validate_values_mixin.py create mode 100644 build/lib/s2python/version.py create mode 100644 src/s2python/ppbc/__init__.py create mode 100644 src/s2python/ppbc/ppbc_schedule_instruction.py diff --git a/build/lib/s2python/__init__.py b/build/lib/s2python/__init__.py new file mode 100644 index 0000000..0ab0a42 --- /dev/null +++ b/build/lib/s2python/__init__.py @@ -0,0 +1,10 @@ +from importlib.metadata import PackageNotFoundError, version # pragma: no cover + +try: + # Change here if project is renamed and does not equal the package name + dist_name = "s2-python" # pylint: disable=invalid-name + __version__ = version(dist_name) +except PackageNotFoundError: # pragma: no cover + __version__ = "unknown" +finally: + del version, PackageNotFoundError diff --git a/build/lib/s2python/common/__init__.py b/build/lib/s2python/common/__init__.py new file mode 100644 index 0000000..806de7e --- /dev/null +++ b/build/lib/s2python/common/__init__.py @@ -0,0 +1,32 @@ +from s2python.generated.gen_s2 import ( + RoleType, + Currency, + CommodityQuantity, + Commodity, + InstructionStatus, + ReceptionStatusValues, + EnergyManagementRole, + SessionRequestType, + ControlType, + RevokableObjects, +) + +from s2python.common.duration import Duration +from s2python.common.role import Role +from s2python.common.handshake import Handshake +from s2python.common.handshake_response import HandshakeResponse +from s2python.common.instruction_status_update import InstructionStatusUpdate +from s2python.common.number_range import NumberRange +from s2python.common.power_forecast_value import PowerForecastValue +from s2python.common.power_forecast_element import PowerForecastElement +from s2python.common.power_forecast import PowerForecast +from s2python.common.power_value import PowerValue +from s2python.common.power_measurement import PowerMeasurement +from s2python.common.power_range import PowerRange +from s2python.common.reception_status import ReceptionStatus +from s2python.common.resource_manager_details import ResourceManagerDetails +from s2python.common.revoke_object import RevokeObject +from s2python.common.select_control_type import SelectControlType +from s2python.common.session_request import SessionRequest +from s2python.common.timer import Timer +from s2python.common.transition import Transition diff --git a/build/lib/s2python/common/duration.py b/build/lib/s2python/common/duration.py new file mode 100644 index 0000000..65663c0 --- /dev/null +++ b/build/lib/s2python/common/duration.py @@ -0,0 +1,22 @@ +from datetime import timedelta +import math + +from s2python.generated.gen_s2 import Duration as GenDuration +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class Duration(GenDuration, S2Message["Duration"]): + def to_timedelta(self) -> timedelta: + return timedelta(milliseconds=self.root) + + @staticmethod + def from_timedelta(duration: timedelta) -> "Duration": + return Duration(root=math.ceil(duration.total_seconds() * 1000)) + + @staticmethod + def from_milliseconds(milliseconds: int) -> "Duration": + return Duration(root=milliseconds) diff --git a/build/lib/s2python/common/handshake.py b/build/lib/s2python/common/handshake.py new file mode 100644 index 0000000..c068150 --- /dev/null +++ b/build/lib/s2python/common/handshake.py @@ -0,0 +1,15 @@ +import uuid + +from s2python.generated.gen_s2 import Handshake as GenHandshake +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class Handshake(GenHandshake, S2Message["Handshake"]): + model_config = GenHandshake.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenHandshake.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/handshake_response.py b/build/lib/s2python/common/handshake_response.py new file mode 100644 index 0000000..fcc2eb5 --- /dev/null +++ b/build/lib/s2python/common/handshake_response.py @@ -0,0 +1,15 @@ +import uuid + +from s2python.generated.gen_s2 import HandshakeResponse as GenHandshakeResponse +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class HandshakeResponse(GenHandshakeResponse, S2Message["HandshakeResponse"]): + model_config = GenHandshakeResponse.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenHandshakeResponse.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/instruction_status_update.py b/build/lib/s2python/common/instruction_status_update.py new file mode 100644 index 0000000..5a8c45f --- /dev/null +++ b/build/lib/s2python/common/instruction_status_update.py @@ -0,0 +1,18 @@ +import uuid + +from s2python.generated.gen_s2 import ( + InstructionStatusUpdate as GenInstructionStatusUpdate, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class InstructionStatusUpdate(GenInstructionStatusUpdate, S2Message["InstructionStatusUpdate"]): + model_config = GenInstructionStatusUpdate.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenInstructionStatusUpdate.model_fields["message_id"] # type: ignore[assignment] + instruction_id: uuid.UUID = GenInstructionStatusUpdate.model_fields["instruction_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/number_range.py b/build/lib/s2python/common/number_range.py new file mode 100644 index 0000000..070b74a --- /dev/null +++ b/build/lib/s2python/common/number_range.py @@ -0,0 +1,22 @@ +from typing import Any + +from s2python.validate_values_mixin import S2Message, catch_and_convert_exceptions +from s2python.generated.gen_s2 import NumberRange as GenNumberRange + + +@catch_and_convert_exceptions +class NumberRange(GenNumberRange, S2Message["NumberRange"]): + model_config = GenNumberRange.model_config + model_config["validate_assignment"] = True + + def __hash__(self) -> int: + return hash(f"{self.start_of_range}|{self.end_of_range}") + + def __eq__(self, other: Any) -> bool: + if isinstance(other, NumberRange): + return ( + self.start_of_range == other.start_of_range + and self.end_of_range == other.end_of_range + ) + + return False diff --git a/build/lib/s2python/common/power_forecast.py b/build/lib/s2python/common/power_forecast.py new file mode 100644 index 0000000..31c595d --- /dev/null +++ b/build/lib/s2python/common/power_forecast.py @@ -0,0 +1,18 @@ +from typing import List +import uuid + +from s2python.common.power_forecast_element import PowerForecastElement +from s2python.generated.gen_s2 import PowerForecast as GenPowerForecast +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class PowerForecast(GenPowerForecast, S2Message["PowerForecast"]): + model_config = GenPowerForecast.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenPowerForecast.model_fields["message_id"] # type: ignore[assignment] + elements: List[PowerForecastElement] = GenPowerForecast.model_fields["elements"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/power_forecast_element.py b/build/lib/s2python/common/power_forecast_element.py new file mode 100644 index 0000000..10460f7 --- /dev/null +++ b/build/lib/s2python/common/power_forecast_element.py @@ -0,0 +1,20 @@ +from typing import List + +from s2python.generated.gen_s2 import PowerForecastElement as GenPowerForecastElement +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) +from s2python.common.duration import Duration +from s2python.common.power_forecast_value import PowerForecastValue + + +@catch_and_convert_exceptions +class PowerForecastElement(GenPowerForecastElement, S2Message["PowerForecastElement"]): + model_config = GenPowerForecastElement.model_config + model_config["validate_assignment"] = True + + duration: Duration = GenPowerForecastElement.model_fields["duration"] # type: ignore[assignment] + power_values: List[PowerForecastValue] = GenPowerForecastElement.model_fields[ + "power_values" + ] # type: ignore[assignment] diff --git a/build/lib/s2python/common/power_forecast_value.py b/build/lib/s2python/common/power_forecast_value.py new file mode 100644 index 0000000..3ee2cc3 --- /dev/null +++ b/build/lib/s2python/common/power_forecast_value.py @@ -0,0 +1,11 @@ +from s2python.generated.gen_s2 import PowerForecastValue as GenPowerForecastValue +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class PowerForecastValue(GenPowerForecastValue, S2Message["PowerForecastValue"]): + model_config = GenPowerForecastValue.model_config + model_config["validate_assignment"] = True diff --git a/build/lib/s2python/common/power_measurement.py b/build/lib/s2python/common/power_measurement.py new file mode 100644 index 0000000..27896c9 --- /dev/null +++ b/build/lib/s2python/common/power_measurement.py @@ -0,0 +1,18 @@ +from typing import List +import uuid + +from s2python.common.power_value import PowerValue +from s2python.generated.gen_s2 import PowerMeasurement as GenPowerMeasurement +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class PowerMeasurement(GenPowerMeasurement, S2Message["PowerMeasurement"]): + model_config = GenPowerMeasurement.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenPowerMeasurement.model_fields["message_id"] # type: ignore[assignment] + values: List[PowerValue] = GenPowerMeasurement.model_fields["values"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/power_range.py b/build/lib/s2python/common/power_range.py new file mode 100644 index 0000000..4ca1ec8 --- /dev/null +++ b/build/lib/s2python/common/power_range.py @@ -0,0 +1,22 @@ +from typing_extensions import Self + +from pydantic import model_validator + +from s2python.generated.gen_s2 import PowerRange as GenPowerRange +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class PowerRange(GenPowerRange, S2Message["PowerRange"]): + model_config = GenPowerRange.model_config + model_config["validate_assignment"] = True + + @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") + + return self diff --git a/build/lib/s2python/common/power_value.py b/build/lib/s2python/common/power_value.py new file mode 100644 index 0000000..c623627 --- /dev/null +++ b/build/lib/s2python/common/power_value.py @@ -0,0 +1,11 @@ +from s2python.generated.gen_s2 import PowerValue as GenPowerValue +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class PowerValue(GenPowerValue, S2Message["PowerValue"]): + model_config = GenPowerValue.model_config + model_config["validate_assignment"] = True diff --git a/build/lib/s2python/common/reception_status.py b/build/lib/s2python/common/reception_status.py new file mode 100644 index 0000000..a759897 --- /dev/null +++ b/build/lib/s2python/common/reception_status.py @@ -0,0 +1,15 @@ +import uuid + +from s2python.generated.gen_s2 import ReceptionStatus as GenReceptionStatus +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class ReceptionStatus(GenReceptionStatus, S2Message["ReceptionStatus"]): + model_config = GenReceptionStatus.model_config + model_config["validate_assignment"] = True + + subject_message_id: uuid.UUID = GenReceptionStatus.model_fields["subject_message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/resource_manager_details.py b/build/lib/s2python/common/resource_manager_details.py new file mode 100644 index 0000000..82ce844 --- /dev/null +++ b/build/lib/s2python/common/resource_manager_details.py @@ -0,0 +1,25 @@ +from typing import List +import uuid + +from s2python.common.duration import Duration +from s2python.common.role import Role +from s2python.generated.gen_s2 import ( + ResourceManagerDetails as GenResourceManagerDetails, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class ResourceManagerDetails(GenResourceManagerDetails, S2Message["ResourceManagerDetails"]): + model_config = GenResourceManagerDetails.model_config + model_config["validate_assignment"] = True + + instruction_processing_delay: Duration = GenResourceManagerDetails.model_fields[ + "instruction_processing_delay" + ] # type: ignore[assignment] + message_id: uuid.UUID = GenResourceManagerDetails.model_fields["message_id"] # type: ignore[assignment] + resource_id: uuid.UUID = GenResourceManagerDetails.model_fields["resource_id"] # type: ignore[assignment] + roles: List[Role] = GenResourceManagerDetails.model_fields["roles"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/revoke_object.py b/build/lib/s2python/common/revoke_object.py new file mode 100644 index 0000000..d133c79 --- /dev/null +++ b/build/lib/s2python/common/revoke_object.py @@ -0,0 +1,16 @@ +import uuid + +from s2python.generated.gen_s2 import RevokeObject as GenRevokeObject +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class RevokeObject(GenRevokeObject, S2Message["RevokeObject"]): + model_config = GenRevokeObject.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenRevokeObject.model_fields["message_id"] # type: ignore[assignment] + object_id: uuid.UUID = GenRevokeObject.model_fields["object_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/role.py b/build/lib/s2python/common/role.py new file mode 100644 index 0000000..4a3d3ef --- /dev/null +++ b/build/lib/s2python/common/role.py @@ -0,0 +1,11 @@ +from s2python.generated.gen_s2 import Role as GenRole +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class Role(GenRole, S2Message["Role"]): + model_config = GenRole.model_config + model_config["validate_assignment"] = True diff --git a/build/lib/s2python/common/select_control_type.py b/build/lib/s2python/common/select_control_type.py new file mode 100644 index 0000000..5f02954 --- /dev/null +++ b/build/lib/s2python/common/select_control_type.py @@ -0,0 +1,15 @@ +import uuid + +from s2python.generated.gen_s2 import SelectControlType as GenSelectControlType +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class SelectControlType(GenSelectControlType, S2Message["SelectControlType"]): + model_config = GenSelectControlType.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenSelectControlType.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/session_request.py b/build/lib/s2python/common/session_request.py new file mode 100644 index 0000000..f962427 --- /dev/null +++ b/build/lib/s2python/common/session_request.py @@ -0,0 +1,15 @@ +import uuid + +from s2python.generated.gen_s2 import SessionRequest as GenSessionRequest +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class SessionRequest(GenSessionRequest, S2Message["SessionRequest"]): + model_config = GenSessionRequest.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenSessionRequest.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/support.py b/build/lib/s2python/common/support.py new file mode 100644 index 0000000..027f65b --- /dev/null +++ b/build/lib/s2python/common/support.py @@ -0,0 +1,27 @@ +from s2python.common import CommodityQuantity, Commodity + + +def commodity_has_quantity(commodity: "Commodity", quantity: CommodityQuantity) -> bool: + if commodity == Commodity.HEAT: + result = quantity in [ + CommodityQuantity.HEAT_THERMAL_POWER, + CommodityQuantity.HEAT_TEMPERATURE, + CommodityQuantity.HEAT_FLOW_RATE, + ] + elif commodity == Commodity.ELECTRICITY: + result = quantity in [ + CommodityQuantity.ELECTRIC_POWER_3_PHASE_SYMMETRIC, + CommodityQuantity.ELECTRIC_POWER_L1, + CommodityQuantity.ELECTRIC_POWER_L2, + CommodityQuantity.ELECTRIC_POWER_L3, + ] + elif commodity == Commodity.GAS: + result = quantity in [CommodityQuantity.NATURAL_GAS_FLOW_RATE] + elif commodity == Commodity.OIL: + result = quantity in [CommodityQuantity.OIL_FLOW_RATE] + else: + raise RuntimeError( + f"Unsupported commodity {commodity}. Missing implementation." + ) + + return result diff --git a/build/lib/s2python/common/timer.py b/build/lib/s2python/common/timer.py new file mode 100644 index 0000000..3811082 --- /dev/null +++ b/build/lib/s2python/common/timer.py @@ -0,0 +1,17 @@ +import uuid + +from s2python.common.duration import Duration +from s2python.generated.gen_s2 import Timer as GenTimer +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class Timer(GenTimer, S2Message["Timer"]): + model_config = GenTimer.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenTimer.model_fields["id"] # type: ignore[assignment] + duration: Duration = GenTimer.model_fields["duration"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/transition.py b/build/lib/s2python/common/transition.py new file mode 100644 index 0000000..e1e1a25 --- /dev/null +++ b/build/lib/s2python/common/transition.py @@ -0,0 +1,24 @@ +import uuid +from typing import Optional, List + +from s2python.common.duration import Duration +from s2python.generated.gen_s2 import Transition as GenTransition +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class Transition(GenTransition, S2Message["Transition"]): + model_config = GenTransition.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenTransition.model_fields["id"] # type: ignore[assignment] + from_: uuid.UUID = GenTransition.model_fields["from_"] # type: ignore[assignment] + to: uuid.UUID = GenTransition.model_fields["to"] # type: ignore[assignment] + start_timers: List[uuid.UUID] = GenTransition.model_fields["start_timers"] # type: ignore[assignment] + blocking_timers: List[uuid.UUID] = GenTransition.model_fields["blocking_timers"] # type: ignore[assignment] + transition_duration: Optional[Duration] = GenTransition.model_fields[ + "transition_duration" + ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/__init__.py b/build/lib/s2python/frbc/__init__.py new file mode 100644 index 0000000..da3d5bc --- /dev/null +++ b/build/lib/s2python/frbc/__init__.py @@ -0,0 +1,17 @@ +from s2python.frbc.frbc_fill_level_target_profile_element import ( + FRBCFillLevelTargetProfileElement, +) +from s2python.frbc.frbc_fill_level_target_profile import FRBCFillLevelTargetProfile +from s2python.frbc.frbc_instruction import FRBCInstruction +from s2python.frbc.frbc_leakage_behaviour_element import FRBCLeakageBehaviourElement +from s2python.frbc.frbc_leakage_behaviour import FRBCLeakageBehaviour +from s2python.frbc.frbc_usage_forecast_element import FRBCUsageForecastElement +from s2python.frbc.frbc_usage_forecast import FRBCUsageForecast +from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement +from s2python.frbc.frbc_operation_mode import FRBCOperationMode +from s2python.frbc.frbc_actuator_description import FRBCActuatorDescription +from s2python.frbc.frbc_actuator_status import FRBCActuatorStatus +from s2python.frbc.frbc_storage_description import FRBCStorageDescription +from s2python.frbc.frbc_storage_status import FRBCStorageStatus +from s2python.frbc.frbc_system_description import FRBCSystemDescription +from s2python.frbc.frbc_timer_status import FRBCTimerStatus diff --git a/build/lib/s2python/frbc/frbc_actuator_description.py b/build/lib/s2python/frbc/frbc_actuator_description.py new file mode 100644 index 0000000..08afce6 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_actuator_description.py @@ -0,0 +1,143 @@ +import uuid + +from typing import List +from typing_extensions import Self + +from pydantic import model_validator + +from s2python.common import Transition, Timer, Commodity +from s2python.common.support import commodity_has_quantity +from s2python.frbc.frbc_operation_mode import FRBCOperationMode +from s2python.generated.gen_s2 import ( + FRBCActuatorDescription as GenFRBCActuatorDescription, +) +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class FRBCActuatorDescription(GenFRBCActuatorDescription, S2Message["FRBCActuatorDescription"]): + model_config = GenFRBCActuatorDescription.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenFRBCActuatorDescription.model_fields["id"] # type: ignore[assignment] + operation_modes: List[FRBCOperationMode] = GenFRBCActuatorDescription.model_fields[ + "operation_modes" + ] # type: ignore[assignment] + transitions: List[Transition] = GenFRBCActuatorDescription.model_fields["transitions"] # type: ignore[assignment] + timers: List[Timer] = GenFRBCActuatorDescription.model_fields["timers"] # type: ignore[assignment] + supported_commodities: List[Commodity] = GenFRBCActuatorDescription.model_fields[ + "supported_commodities" + ] # type: ignore[assignment] + + @model_validator(mode="after") + def validate_timers_in_transitions(self) -> Self: + timers_by_id = {timer.id: timer for timer in self.timers} + transition: Transition + for transition in self.transitions: + for start_timer_id in transition.start_timers: + if start_timer_id not in timers_by_id: + raise ValueError( + self, + f"{start_timer_id} was referenced as start timer in transition " + f"{transition.id} but was not defined in 'timers'.", + ) + + for blocking_timer_id in transition.blocking_timers: + if blocking_timer_id not in timers_by_id: + raise ValueError( + self, + f"{blocking_timer_id} was referenced as blocking timer in transition " + f"{transition.id} but was not defined in 'timers'.", + ) + + return self + + @model_validator(mode="after") + def validate_timers_unique_ids(self) -> Self: + ids = [] + 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'.") + ids.append(timer.id) + + return self + + @model_validator(mode="after") + def validate_operation_modes_in_transitions(self) -> Self: + operation_mode_by_id = {operation_mode.id: operation_mode for operation_mode in self.operation_modes} + transition: Transition + for transition in self.transitions: + if transition.from_ not in operation_mode_by_id: + raise ValueError( + self, + f"Operation mode {transition.from_} was referenced as 'from' in transition " + f"{transition.id} but was not defined in 'operation_modes'.", + ) + + if transition.to not in operation_mode_by_id: + raise ValueError( + self, + f"Operation mode {transition.to} was referenced as 'to' in transition " + f"{transition.id} but was not defined in 'operation_modes'.", + ) + + return self + + @model_validator(mode="after") + def validate_operation_modes_unique_ids(self) -> Self: + ids = [] + operation_mode: FRBCOperationMode + for operation_mode in self.operation_modes: + if operation_mode.id in ids: + raise ValueError( + self, + f"Id {operation_mode.id} was found multiple times in 'operation_modes'.", + ) + ids.append(operation_mode.id) + + return self + + @model_validator(mode="after") + def validate_operation_mode_elements_have_all_supported_commodities(self) -> Self: + supported_commodities = self.supported_commodities + operation_mode: FRBCOperationMode + for operation_mode in self.operation_modes: + for operation_mode_element in operation_mode.elements: + for commodity in supported_commodities: + 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 len(power_ranges_for_commodity) > 1: + raise ValueError( + self, + f"Multiple power ranges defined for commodity {commodity} in operation " + f"mode {operation_mode.id} and element with fill_level_range " + f"{operation_mode_element.fill_level_range}", + ) + if not power_ranges_for_commodity: + raise ValueError( + self, + f"No power ranges defined for commodity {commodity} in operation " + f"mode {operation_mode.id} and element with fill_level_range " + f"{operation_mode_element.fill_level_range}", + ) + return self + + @model_validator(mode="after") + def validate_unique_supported_commodities(self) -> Self: + supported_commodities: List[Commodity] = self.supported_commodities + + for supported_commodity in supported_commodities: + if supported_commodities.count(supported_commodity) > 1: + raise ValueError( + self, + f"Found duplicate {supported_commodity} commodity in 'supported_commodities'", + ) + return self diff --git a/build/lib/s2python/frbc/frbc_actuator_status.py b/build/lib/s2python/frbc/frbc_actuator_status.py new file mode 100644 index 0000000..585a23d --- /dev/null +++ b/build/lib/s2python/frbc/frbc_actuator_status.py @@ -0,0 +1,23 @@ +from typing import Optional +import uuid + +from s2python.generated.gen_s2 import FRBCActuatorStatus as GenFRBCActuatorStatus +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCActuatorStatus(GenFRBCActuatorStatus, S2Message["FRBCActuatorStatus"]): + model_config = GenFRBCActuatorStatus.model_config + model_config["validate_assignment"] = True + + active_operation_mode_id: uuid.UUID = GenFRBCActuatorStatus.model_fields[ + "active_operation_mode_id" + ] # type: ignore[assignment] + actuator_id: uuid.UUID = GenFRBCActuatorStatus.model_fields["actuator_id"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCActuatorStatus.model_fields["message_id"] # type: ignore[assignment] + previous_operation_mode_id: Optional[uuid.UUID] = GenFRBCActuatorStatus.model_fields[ + "previous_operation_mode_id" + ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_fill_level_target_profile.py b/build/lib/s2python/frbc/frbc_fill_level_target_profile.py new file mode 100644 index 0000000..38ef83b --- /dev/null +++ b/build/lib/s2python/frbc/frbc_fill_level_target_profile.py @@ -0,0 +1,24 @@ +from typing import List +import uuid + +from s2python.frbc.frbc_fill_level_target_profile_element import ( + FRBCFillLevelTargetProfileElement, +) +from s2python.generated.gen_s2 import ( + FRBCFillLevelTargetProfile as GenFRBCFillLevelTargetProfile, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCFillLevelTargetProfile(GenFRBCFillLevelTargetProfile, S2Message["FRBCFillLevelTargetProfile"]): + model_config = GenFRBCFillLevelTargetProfile.model_config + model_config["validate_assignment"] = True + + elements: List[FRBCFillLevelTargetProfileElement] = GenFRBCFillLevelTargetProfile.model_fields[ + "elements" + ] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCFillLevelTargetProfile.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py b/build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py new file mode 100644 index 0000000..cdb7d84 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py @@ -0,0 +1,34 @@ +# pylint: disable=duplicate-code + +from typing_extensions import Self + +from pydantic import model_validator + +from s2python.common import Duration, NumberRange +from s2python.generated.gen_s2 import ( + FRBCFillLevelTargetProfileElement as GenFRBCFillLevelTargetProfileElement, +) +from s2python.validate_values_mixin import catch_and_convert_exceptions, S2Message + + +@catch_and_convert_exceptions +class FRBCFillLevelTargetProfileElement( + GenFRBCFillLevelTargetProfileElement, S2Message["FRBCFillLevelTargetProfileElement"] +): + model_config = GenFRBCFillLevelTargetProfileElement.model_config + model_config["validate_assignment"] = True + + duration: Duration = GenFRBCFillLevelTargetProfileElement.model_fields["duration"] # type: ignore[assignment] + fill_level_range: NumberRange = GenFRBCFillLevelTargetProfileElement.model_fields[ + "fill_level_range" + ] # type: ignore[assignment] + + @model_validator(mode="after") + def validate_start_end_order(self) -> Self: + if self.fill_level_range.start_of_range > self.fill_level_range.end_of_range: + raise ValueError( + self, + "start_of_range should not be higher than end_of_range for the fill_level_range", + ) + + return self diff --git a/build/lib/s2python/frbc/frbc_instruction.py b/build/lib/s2python/frbc/frbc_instruction.py new file mode 100644 index 0000000..584cfba --- /dev/null +++ b/build/lib/s2python/frbc/frbc_instruction.py @@ -0,0 +1,18 @@ +import uuid + +from s2python.generated.gen_s2 import FRBCInstruction as GenFRBCInstruction +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCInstruction(GenFRBCInstruction, S2Message["FRBCInstruction"]): + model_config = GenFRBCInstruction.model_config + model_config["validate_assignment"] = True + + actuator_id: uuid.UUID = GenFRBCInstruction.model_fields["actuator_id"] # type: ignore[assignment] + id: uuid.UUID = GenFRBCInstruction.model_fields["id"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCInstruction.model_fields["message_id"] # type: ignore[assignment] + operation_mode: uuid.UUID = GenFRBCInstruction.model_fields["operation_mode"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_leakage_behaviour.py b/build/lib/s2python/frbc/frbc_leakage_behaviour.py new file mode 100644 index 0000000..fda7d3b --- /dev/null +++ b/build/lib/s2python/frbc/frbc_leakage_behaviour.py @@ -0,0 +1,20 @@ +from typing import List +import uuid + +from s2python.frbc.frbc_leakage_behaviour_element import FRBCLeakageBehaviourElement +from s2python.generated.gen_s2 import FRBCLeakageBehaviour as GenFRBCLeakageBehaviour +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCLeakageBehaviour(GenFRBCLeakageBehaviour, S2Message["FRBCLeakageBehaviour"]): + model_config = GenFRBCLeakageBehaviour.model_config + model_config["validate_assignment"] = True + + elements: List[FRBCLeakageBehaviourElement] = GenFRBCLeakageBehaviour.model_fields[ + "elements" + ] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCLeakageBehaviour.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_leakage_behaviour_element.py b/build/lib/s2python/frbc/frbc_leakage_behaviour_element.py new file mode 100644 index 0000000..b9ca2eb --- /dev/null +++ b/build/lib/s2python/frbc/frbc_leakage_behaviour_element.py @@ -0,0 +1,30 @@ +# pylint: disable=duplicate-code + +from pydantic import model_validator +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, S2Message + + +@catch_and_convert_exceptions +class FRBCLeakageBehaviourElement( + GenFRBCLeakageBehaviourElement, S2Message["FRBCLeakageBehaviourElement"] +): + model_config = GenFRBCLeakageBehaviourElement.model_config + model_config["validate_assignment"] = True + + fill_level_range: NumberRange = GenFRBCLeakageBehaviourElement.model_fields[ + "fill_level_range" + ] # type: ignore[assignment] + + @model_validator(mode="after") + def validate_start_end_order(self) -> Self: + if self.fill_level_range.start_of_range > self.fill_level_range.end_of_range: + raise ValueError( + self, + "start_of_range should not be higher than end_of_range for the fill_level_range", + ) + + return self diff --git a/build/lib/s2python/frbc/frbc_operation_mode.py b/build/lib/s2python/frbc/frbc_operation_mode.py new file mode 100644 index 0000000..c6758ad --- /dev/null +++ b/build/lib/s2python/frbc/frbc_operation_mode.py @@ -0,0 +1,42 @@ +# from itertools import pairwise +import uuid +from typing import List, Dict +from typing_extensions import Self + +from pydantic import model_validator + +from s2python.common import NumberRange +from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement +from s2python.generated.gen_s2 import FRBCOperationMode as GenFRBCOperationMode +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) +from s2python.utils import pairwise + + +@catch_and_convert_exceptions +class FRBCOperationMode(GenFRBCOperationMode, S2Message["FRBCOperationMode"]): + model_config = GenFRBCOperationMode.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenFRBCOperationMode.model_fields["id"] # type: ignore[assignment] + elements: List[FRBCOperationModeElement] = GenFRBCOperationMode.model_fields["elements"] # type: ignore[assignment] + + @model_validator(mode="after") + def validate_contiguous_fill_levels_operation_mode_elements(self) -> Self: + elements_by_fill_level_range: Dict[NumberRange, FRBCOperationModeElement] + elements_by_fill_level_range = {element.fill_level_range: element for element in self.elements} + + sorted_fill_level_ranges: List[NumberRange] + 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: + raise ValueError( + self, + f"Elements with fill level ranges {current_fill_level_range} and " + f"{next_fill_level_range} are closest match to each other but not contiguous.", + ) + return self diff --git a/build/lib/s2python/frbc/frbc_operation_mode_element.py b/build/lib/s2python/frbc/frbc_operation_mode_element.py new file mode 100644 index 0000000..d154d11 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_operation_mode_element.py @@ -0,0 +1,27 @@ +from typing import Optional, List + +from s2python.common import NumberRange, PowerRange +from s2python.generated.gen_s2 import ( + FRBCOperationModeElement as GenFRBCOperationModeElement, +) +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class FRBCOperationModeElement(GenFRBCOperationModeElement, S2Message["FRBCOperationModeElement"]): + model_config = GenFRBCOperationModeElement.model_config + model_config["validate_assignment"] = True + + fill_level_range: NumberRange = GenFRBCOperationModeElement.model_fields[ + "fill_level_range" + ] # type: ignore[assignment] + fill_rate: NumberRange = GenFRBCOperationModeElement.model_fields["fill_rate"] # type: ignore[assignment] + power_ranges: List[PowerRange] = GenFRBCOperationModeElement.model_fields[ + "power_ranges" + ] # type: ignore[assignment] + running_costs: Optional[NumberRange] = GenFRBCOperationModeElement.model_fields[ + "running_costs" + ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_storage_description.py b/build/lib/s2python/frbc/frbc_storage_description.py new file mode 100644 index 0000000..eb141b8 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_storage_description.py @@ -0,0 +1,18 @@ +from s2python.common import NumberRange +from s2python.generated.gen_s2 import ( + FRBCStorageDescription as GenFRBCStorageDescription, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCStorageDescription(GenFRBCStorageDescription, S2Message["FRBCStorageDescription"]): + model_config = GenFRBCStorageDescription.model_config + model_config["validate_assignment"] = True + + fill_level_range: NumberRange = GenFRBCStorageDescription.model_fields[ + "fill_level_range" + ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_storage_status.py b/build/lib/s2python/frbc/frbc_storage_status.py new file mode 100644 index 0000000..7940b79 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_storage_status.py @@ -0,0 +1,15 @@ +import uuid + +from s2python.generated.gen_s2 import FRBCStorageStatus as GenFRBCStorageStatus +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCStorageStatus(GenFRBCStorageStatus, S2Message["FRBCStorageStatus"]): + model_config = GenFRBCStorageStatus.model_config + model_config["validate_assignment"] = True + + message_id: uuid.UUID = GenFRBCStorageStatus.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_system_description.py b/build/lib/s2python/frbc/frbc_system_description.py new file mode 100644 index 0000000..2eb5899 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_system_description.py @@ -0,0 +1,22 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import FRBCSystemDescription as GenFRBCSystemDescription +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) +from s2python.frbc.frbc_actuator_description import FRBCActuatorDescription +from s2python.frbc.frbc_storage_description import FRBCStorageDescription + + +@catch_and_convert_exceptions +class FRBCSystemDescription(GenFRBCSystemDescription, S2Message["FRBCSystemDescription"]): + model_config = GenFRBCSystemDescription.model_config + model_config["validate_assignment"] = True + + actuators: List[FRBCActuatorDescription] = GenFRBCSystemDescription.model_fields[ + "actuators" + ] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCSystemDescription.model_fields["message_id"] # type: ignore[assignment] + storage: FRBCStorageDescription = GenFRBCSystemDescription.model_fields["storage"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_timer_status.py b/build/lib/s2python/frbc/frbc_timer_status.py new file mode 100644 index 0000000..80c86d6 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_timer_status.py @@ -0,0 +1,17 @@ +import uuid + +from s2python.generated.gen_s2 import FRBCTimerStatus as GenFRBCTimerStatus +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCTimerStatus(GenFRBCTimerStatus, S2Message["FRBCTimerStatus"]): + model_config = GenFRBCTimerStatus.model_config + model_config["validate_assignment"] = True + + actuator_id: uuid.UUID = GenFRBCTimerStatus.model_fields["actuator_id"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCTimerStatus.model_fields["message_id"] # type: ignore[assignment] + timer_id: uuid.UUID = GenFRBCTimerStatus.model_fields["timer_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_usage_forecast.py b/build/lib/s2python/frbc/frbc_usage_forecast.py new file mode 100644 index 0000000..f71fda4 --- /dev/null +++ b/build/lib/s2python/frbc/frbc_usage_forecast.py @@ -0,0 +1,18 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import FRBCUsageForecast as GenFRBCUsageForecast +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) +from s2python.frbc.frbc_usage_forecast_element import FRBCUsageForecastElement + + +@catch_and_convert_exceptions +class FRBCUsageForecast(GenFRBCUsageForecast, S2Message["FRBCUsageForecast"]): + model_config = GenFRBCUsageForecast.model_config + model_config["validate_assignment"] = True + + elements: List[FRBCUsageForecastElement] = GenFRBCUsageForecast.model_fields["elements"] # type: ignore[assignment] + message_id: uuid.UUID = GenFRBCUsageForecast.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_usage_forecast_element.py b/build/lib/s2python/frbc/frbc_usage_forecast_element.py new file mode 100644 index 0000000..370c04e --- /dev/null +++ b/build/lib/s2python/frbc/frbc_usage_forecast_element.py @@ -0,0 +1,17 @@ +from s2python.common import Duration + +from s2python.generated.gen_s2 import ( + FRBCUsageForecastElement as GenFRBCUsageForecastElement, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class FRBCUsageForecastElement(GenFRBCUsageForecastElement, S2Message["FRBCUsageForecastElement"]): + model_config = GenFRBCUsageForecastElement.model_config + model_config["validate_assignment"] = True + + duration: Duration = GenFRBCUsageForecastElement.model_fields["duration"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/rm.py b/build/lib/s2python/frbc/rm.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/s2python/generated/__init__.py b/build/lib/s2python/generated/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/s2python/generated/gen_s2.py b/build/lib/s2python/generated/gen_s2.py new file mode 100644 index 0000000..c7febd6 --- /dev/null +++ b/build/lib/s2python/generated/gen_s2.py @@ -0,0 +1,1611 @@ +# generated by datamodel-codegen: +# filename: openapi.yml +# timestamp: 2024-07-29T10:18:52+00:00 + +from __future__ import annotations + +from enum import Enum +from typing import List, Optional + +from pydantic import ( + AwareDatetime, + BaseModel, + ConfigDict, + Field, + RootModel, + conint, + constr, +) +from typing_extensions import Literal + + +class Duration(RootModel[conint(ge=0)]): + root: conint(ge=0) = Field(..., description='Duration in milliseconds') + + +class ID(RootModel[constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}')]): + root: constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') + + +class Currency(Enum): + AED = 'AED' + ANG = 'ANG' + AUD = 'AUD' + CHE = 'CHE' + CHF = 'CHF' + CHW = 'CHW' + EUR = 'EUR' + GBP = 'GBP' + LBP = 'LBP' + LKR = 'LKR' + LRD = 'LRD' + LSL = 'LSL' + LYD = 'LYD' + MAD = 'MAD' + MDL = 'MDL' + MGA = 'MGA' + MKD = 'MKD' + MMK = 'MMK' + MNT = 'MNT' + MOP = 'MOP' + MRO = 'MRO' + MUR = 'MUR' + MVR = 'MVR' + MWK = 'MWK' + MXN = 'MXN' + MXV = 'MXV' + MYR = 'MYR' + MZN = 'MZN' + NAD = 'NAD' + NGN = 'NGN' + NIO = 'NIO' + NOK = 'NOK' + NPR = 'NPR' + NZD = 'NZD' + OMR = 'OMR' + PAB = 'PAB' + PEN = 'PEN' + PGK = 'PGK' + PHP = 'PHP' + PKR = 'PKR' + PLN = 'PLN' + PYG = 'PYG' + QAR = 'QAR' + RON = 'RON' + RSD = 'RSD' + RUB = 'RUB' + RWF = 'RWF' + SAR = 'SAR' + SBD = 'SBD' + SCR = 'SCR' + SDG = 'SDG' + SEK = 'SEK' + SGD = 'SGD' + SHP = 'SHP' + SLL = 'SLL' + SOS = 'SOS' + SRD = 'SRD' + SSP = 'SSP' + STD = 'STD' + SYP = 'SYP' + SZL = 'SZL' + THB = 'THB' + TJS = 'TJS' + TMT = 'TMT' + TND = 'TND' + TOP = 'TOP' + TRY = 'TRY' + TTD = 'TTD' + TWD = 'TWD' + TZS = 'TZS' + UAH = 'UAH' + UGX = 'UGX' + USD = 'USD' + USN = 'USN' + UYI = 'UYI' + UYU = 'UYU' + UZS = 'UZS' + VEF = 'VEF' + VND = 'VND' + VUV = 'VUV' + WST = 'WST' + XAG = 'XAG' + XAU = 'XAU' + XBA = 'XBA' + XBB = 'XBB' + XBC = 'XBC' + XBD = 'XBD' + XCD = 'XCD' + XOF = 'XOF' + XPD = 'XPD' + XPF = 'XPF' + XPT = 'XPT' + XSU = 'XSU' + XTS = 'XTS' + XUA = 'XUA' + XXX = 'XXX' + YER = 'YER' + ZAR = 'ZAR' + ZMW = 'ZMW' + ZWL = 'ZWL' + + +class SessionRequestType(Enum): + RECONNECT = 'RECONNECT' + TERMINATE = 'TERMINATE' + + +class RevokableObjects(Enum): + PEBC_PowerConstraints = 'PEBC.PowerConstraints' + PEBC_EnergyConstraint = 'PEBC.EnergyConstraint' + PEBC_Instruction = 'PEBC.Instruction' + PPBC_PowerProfileDefinition = 'PPBC.PowerProfileDefinition' + PPBC_ScheduleInstruction = 'PPBC.ScheduleInstruction' + PPBC_StartInterruptionInstruction = 'PPBC.StartInterruptionInstruction' + PPBC_EndInterruptionInstruction = 'PPBC.EndInterruptionInstruction' + OMBC_SystemDescription = 'OMBC.SystemDescription' + OMBC_Instruction = 'OMBC.Instruction' + FRBC_SystemDescription = 'FRBC.SystemDescription' + FRBC_Instruction = 'FRBC.Instruction' + DDBC_SystemDescription = 'DDBC.SystemDescription' + DDBC_Instruction = 'DDBC.Instruction' + + +class EnergyManagementRole(Enum): + CEM = 'CEM' + RM = 'RM' + + +class ReceptionStatusValues(Enum): + INVALID_DATA = 'INVALID_DATA' + INVALID_MESSAGE = 'INVALID_MESSAGE' + INVALID_CONTENT = 'INVALID_CONTENT' + TEMPORARY_ERROR = 'TEMPORARY_ERROR' + PERMANENT_ERROR = 'PERMANENT_ERROR' + OK = 'OK' + + +class NumberRange(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start_of_range: float = Field( + ..., description='Number that defines the start of the range' + ) + end_of_range: float = Field( + ..., description='Number that defines the end of the range' + ) + + +class Transition(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', + ) + from_: ID = Field( + ..., + alias='from', + description='ID of the OperationMode (exact type differs per ControlType) that should be switched from.', + ) + to: ID = Field( + ..., + description='ID of the OperationMode (exact type differs per ControlType) that will be switched to.', + ) + start_timers: List[ID] = Field( + ..., + description='List of IDs of Timers that will be (re)started when this transition is initiated', + max_length=1000, + min_length=0, + ) + blocking_timers: List[ID] = Field( + ..., + description='List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished', + max_length=1000, + min_length=0, + ) + transition_costs: Optional[float] = Field( + None, + description='Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.', + ) + transition_duration: Optional[Duration] = Field( + None, + description='Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.', + ) + abnormal_condition_only: bool = Field( + ..., + description='Indicates if this Transition may only be used during an abnormal condition (see Clause )', + ) + + +class Timer(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.', + ) + duration: Duration = Field( + ..., + description='The time it takes for the Timer to finish after it has been started', + ) + + +class PEBCPowerEnvelopeElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + duration: Duration = Field(..., description='The duration of the element') + upper_limit: float = Field( + ..., + description='Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.', + ) + lower_limit: float = Field( + ..., + description='Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.', + ) + + +class FRBCStorageDescription(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.', + ) + fill_level_label: Optional[str] = Field( + None, + description='Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.', + ) + provides_leakage_behaviour: bool = Field( + ..., + description='Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.', + ) + provides_fill_level_target_profile: bool = Field( + ..., + description='Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.', + ) + provides_usage_forecast: bool = Field( + ..., + description='Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.', + ) + fill_level_range: NumberRange = Field( + ..., + description='The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ', + ) + + +class FRBCLeakageBehaviourElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + fill_level_range: NumberRange = Field( + ..., + description='The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.', + ) + leakage_rate: float = Field( + ..., + description='Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.', + ) + + +class FRBCUsageForecastElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + duration: Duration = Field( + ..., description='Indicator for how long the given usage_rate is valid.' + ) + usage_rate_upper_limit: Optional[float] = Field( + None, + description='The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + ) + usage_rate_upper_95PPR: Optional[float] = Field( + None, + description='The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + ) + usage_rate_upper_68PPR: Optional[float] = Field( + None, + description='The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + ) + usage_rate_expected: float = Field( + ..., + description='The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.', + ) + usage_rate_lower_68PPR: Optional[float] = Field( + None, + description='The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + ) + usage_rate_lower_95PPR: Optional[float] = Field( + None, + description='The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + ) + usage_rate_lower_limit: Optional[float] = Field( + None, + description='The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + ) + + +class FRBCFillLevelTargetProfileElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + duration: Duration = Field(..., description='The duration of the element.') + fill_level_range: NumberRange = Field( + ..., + description='The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.', + ) + + +class DDBCAverageDemandRateForecastElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + duration: Duration = Field(..., description='Duration of the element') + demand_rate_upper_limit: Optional[float] = Field( + None, + description='The upper limit of the range with a 100 % probability that the demand rate is within that range', + ) + demand_rate_upper_95PPR: Optional[float] = Field( + None, + description='The upper limit of the range with a 95 % probability that the demand rate is within that range', + ) + demand_rate_upper_68PPR: Optional[float] = Field( + None, + description='The upper limit of the range with a 68 % probability that the demand rate is within that range', + ) + demand_rate_expected: float = Field( + ..., + description='The most likely value for the demand rate; the expected increase or decrease of the fill_level per second', + ) + demand_rate_lower_68PPR: Optional[float] = Field( + None, + description='The lower limit of the range with a 68 % probability that the demand rate is within that range', + ) + demand_rate_lower_95PPR: Optional[float] = Field( + None, + description='The lower limit of the range with a 95 % probability that the demand rate is within that range', + ) + demand_rate_lower_limit: Optional[float] = Field( + None, + description='The lower limit of the range with a 100 % probability that the demand rate is within that range', + ) + + +class RoleType(Enum): + ENERGY_PRODUCER = 'ENERGY_PRODUCER' + ENERGY_CONSUMER = 'ENERGY_CONSUMER' + ENERGY_STORAGE = 'ENERGY_STORAGE' + + +class Commodity(Enum): + GAS = 'GAS' + HEAT = 'HEAT' + ELECTRICITY = 'ELECTRICITY' + OIL = 'OIL' + + +class CommodityQuantity(Enum): + ELECTRIC_POWER_L1 = 'ELECTRIC.POWER.L1' + ELECTRIC_POWER_L2 = 'ELECTRIC.POWER.L2' + ELECTRIC_POWER_L3 = 'ELECTRIC.POWER.L3' + ELECTRIC_POWER_3_PHASE_SYMMETRIC = 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' + NATURAL_GAS_FLOW_RATE = 'NATURAL_GAS.FLOW_RATE' + HYDROGEN_FLOW_RATE = 'HYDROGEN.FLOW_RATE' + HEAT_TEMPERATURE = 'HEAT.TEMPERATURE' + HEAT_FLOW_RATE = 'HEAT.FLOW_RATE' + HEAT_THERMAL_POWER = 'HEAT.THERMAL_POWER' + OIL_FLOW_RATE = 'OIL.FLOW_RATE' + + +class InstructionStatus(Enum): + NEW = 'NEW' + ACCEPTED = 'ACCEPTED' + REJECTED = 'REJECTED' + REVOKED = 'REVOKED' + STARTED = 'STARTED' + SUCCEEDED = 'SUCCEEDED' + ABORTED = 'ABORTED' + + +class ControlType(Enum): + POWER_ENVELOPE_BASED_CONTROL = 'POWER_ENVELOPE_BASED_CONTROL' + POWER_PROFILE_BASED_CONTROL = 'POWER_PROFILE_BASED_CONTROL' + OPERATION_MODE_BASED_CONTROL = 'OPERATION_MODE_BASED_CONTROL' + FILL_RATE_BASED_CONTROL = 'FILL_RATE_BASED_CONTROL' + DEMAND_DRIVEN_BASED_CONTROL = 'DEMAND_DRIVEN_BASED_CONTROL' + NOT_CONTROLABLE = 'NOT_CONTROLABLE' + NO_SELECTION = 'NO_SELECTION' + + +class PEBCPowerEnvelopeLimitType(Enum): + UPPER_LIMIT = 'UPPER_LIMIT' + LOWER_LIMIT = 'LOWER_LIMIT' + + +class PEBCPowerEnvelopeConsequenceType(Enum): + VANISH = 'VANISH' + DEFER = 'DEFER' + + +class PPBCPowerSequenceStatus(Enum): + NOT_SCHEDULED = 'NOT_SCHEDULED' + SCHEDULED = 'SCHEDULED' + EXECUTING = 'EXECUTING' + INTERRUPTED = 'INTERRUPTED' + FINISHED = 'FINISHED' + ABORTED = 'ABORTED' + + +class OMBCTimerStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.TimerStatus'] = 'OMBC.TimerStatus' + message_id: ID + timer_id: ID = Field(..., description='The ID of the timer this message refers to') + finished_at: AwareDatetime = Field( + ..., + description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + ) + + +class FRBCTimerStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.TimerStatus'] = 'FRBC.TimerStatus' + message_id: ID + timer_id: ID = Field(..., description='The ID of the timer this message refers to') + actuator_id: ID = Field( + ..., description='The ID of the actuator the timer belongs to' + ) + finished_at: AwareDatetime = Field( + ..., + description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + ) + + +class DDBCTimerStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.TimerStatus'] = 'DDBC.TimerStatus' + message_id: ID + timer_id: ID = Field(..., description='The ID of the timer this message refers to') + actuator_id: ID = Field( + ..., description='The ID of the actuator the timer belongs to' + ) + finished_at: AwareDatetime = Field( + ..., + description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + ) + + +class SelectControlType(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['SelectControlType'] = 'SelectControlType' + message_id: ID + control_type: ControlType = Field( + ..., + description='The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails', + ) + + +class SessionRequest(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['SessionRequest'] = 'SessionRequest' + message_id: ID + request: SessionRequestType = Field(..., description='The type of request') + diagnostic_label: Optional[str] = Field( + None, + description='Optional field for a human readible descirption for debugging purposes', + ) + + +class RevokeObject(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['RevokeObject'] = 'RevokeObject' + message_id: ID + object_type: RevokableObjects = Field( + ..., description='The type of object that needs to be revoked' + ) + object_id: ID = Field(..., description='The ID of object that needs to be revoked') + + +class Handshake(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['Handshake'] = 'Handshake' + message_id: ID + role: EnergyManagementRole = Field( + ..., description='The role of the sender of this message' + ) + supported_protocol_versions: Optional[List[str]] = Field( + None, + description='Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.', + min_length=1, + ) + + +class HandshakeResponse(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['HandshakeResponse'] = 'HandshakeResponse' + message_id: ID + selected_protocol_version: str = Field( + ..., description='The protocol version the CEM selected for this session' + ) + + +class ReceptionStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['ReceptionStatus'] = 'ReceptionStatus' + subject_message_id: ID = Field( + ..., description='The message this ReceptionStatus refers to' + ) + status: ReceptionStatusValues = Field( + ..., description='Enumeration of status values' + ) + diagnostic_label: Optional[str] = Field( + None, + description='Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.', + ) + + +class InstructionStatusUpdate(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['InstructionStatusUpdate'] = 'InstructionStatusUpdate' + message_id: ID + instruction_id: ID = Field( + ..., description='ID of this instruction (as provided by the CEM) ' + ) + status_type: InstructionStatus = Field( + ..., description='Present status of this instruction.' + ) + timestamp: AwareDatetime = Field( + ..., description='Timestamp when status_type has changed the last time.' + ) + + +class PEBCEnergyConstraint(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PEBC.EnergyConstraint'] = 'PEBC.EnergyConstraint' + message_id: ID + id: ID = Field( + ..., + description='Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + valid_from: AwareDatetime = Field( + ..., + description='Moment this PEBC.EnergyConstraints information starts to be valid', + ) + valid_until: AwareDatetime = Field( + ..., + description='Moment until this PEBC.EnergyConstraints information is valid.', + ) + upper_average_power: float = Field( + ..., + description='Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', + ) + lower_average_power: float = Field( + ..., + description='Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', + ) + commodity_quantity: CommodityQuantity = Field( + ..., + description='Type of power quantity which applies to upper_average_power and lower_average_power', + ) + + +class PPBCScheduleInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.ScheduleInstruction'] = 'PPBC.ScheduleInstruction' + message_id: ID + id: ID = Field( + ..., + description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + power_profile_id: ID = Field( + ..., + description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', + ) + sequence_container_id: ID = Field( + ..., + description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', + ) + power_sequence_id: ID = Field( + ..., + description='ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.', + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition', + ) + + +class PPBCStartInterruptionInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.StartInterruptionInstruction'] = ( + 'PPBC.StartInterruptionInstruction' + ) + message_id: ID + id: ID = Field( + ..., + description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + power_profile_id: ID = Field( + ..., + description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.', + ) + sequence_container_id: ID = Field( + ..., + description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.', + ) + power_sequence_id: ID = Field( + ..., description='ID of the PPBC.PowerSequence that the CEM wants to interrupt.' + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition', + ) + + +class PPBCEndInterruptionInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.EndInterruptionInstruction'] = ( + 'PPBC.EndInterruptionInstruction' + ) + message_id: ID + id: ID = Field( + ..., + description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + power_profile_id: ID = Field( + ..., + description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.', + ) + sequence_container_id: ID = Field( + ..., + description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.', + ) + power_sequence_id: ID = Field( + ..., + description='ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.', + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition', + ) + + +class OMBCStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.Status'] = 'OMBC.Status' + message_id: ID + active_operation_mode_id: ID = Field( + ..., description='ID of the active OMBC.OperationMode.' + ) + operation_mode_factor: float = Field( + ..., + description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', + ) + previous_operation_mode_id: Optional[ID] = Field( + None, + description='ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', + ) + transition_timestamp: Optional[AwareDatetime] = Field( + None, + description='Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', + ) + + +class OMBCInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.Instruction'] = 'OMBC.Instruction' + message_id: ID + id: ID = Field( + ..., + description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + ) + operation_mode_id: ID = Field( + ..., description='ID of the OMBC.OperationMode that should be activated' + ) + operation_mode_factor: float = Field( + ..., + description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition', + ) + + +class FRBCActuatorStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.ActuatorStatus'] = 'FRBC.ActuatorStatus' + message_id: ID + actuator_id: ID = Field( + ..., description='ID of the actuator this messages refers to' + ) + active_operation_mode_id: ID = Field( + ..., description='ID of the FRBC.OperationMode that is presently active.' + ) + operation_mode_factor: float = Field( + ..., + description='The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.', + ) + previous_operation_mode_id: Optional[ID] = Field( + None, + description='ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', + ) + transition_timestamp: Optional[AwareDatetime] = Field( + None, + description='Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', + ) + + +class FRBCStorageStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.StorageStatus'] = 'FRBC.StorageStatus' + message_id: ID + present_fill_level: float = Field( + ..., description='Present fill level of the Storage' + ) + + +class FRBCLeakageBehaviour(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.LeakageBehaviour'] = 'FRBC.LeakageBehaviour' + message_id: ID + valid_from: AwareDatetime = Field( + ..., + description='Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.', + ) + elements: List[FRBCLeakageBehaviourElement] = Field( + ..., + description='List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.', + max_length=288, + min_length=1, + ) + + +class FRBCInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.Instruction'] = 'FRBC.Instruction' + message_id: ID + id: ID = Field( + ..., + description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + actuator_id: ID = Field( + ..., description='ID of the actuator this instruction belongs to.' + ) + operation_mode: ID = Field( + ..., description='ID of the FRBC.OperationMode that should be activated.' + ) + operation_mode_factor: float = Field( + ..., + description='The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition.', + ) + + +class FRBCUsageForecast(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.UsageForecast'] = 'FRBC.UsageForecast' + message_id: ID + start_time: AwareDatetime = Field( + ..., description='Time at which the FRBC.UsageForecast starts.' + ) + elements: List[FRBCUsageForecastElement] = Field( + ..., + description='Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.', + max_length=288, + min_length=1, + ) + + +class FRBCFillLevelTargetProfile(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.FillLevelTargetProfile'] = 'FRBC.FillLevelTargetProfile' + message_id: ID + start_time: AwareDatetime = Field( + ..., description='Time at which the FRBC.FillLevelTargetProfile starts.' + ) + elements: List[FRBCFillLevelTargetProfileElement] = Field( + ..., + description='List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.', + max_length=288, + min_length=1, + ) + + +class DDBCActuatorStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.ActuatorStatus'] = 'DDBC.ActuatorStatus' + message_id: ID + actuator_id: ID = Field( + ..., description='ID of the actuator this messages refers to' + ) + active_operation_mode_id: ID = Field( + ..., + description='The operation mode that is presently active for this actuator.', + ) + operation_mode_factor: float = Field( + ..., + description='The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.', + ) + previous_operation_mode_id: Optional[ID] = Field( + None, + description='ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', + ) + transition_timestamp: Optional[AwareDatetime] = Field( + None, + description='Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', + ) + + +class DDBCInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.Instruction'] = 'DDBC.Instruction' + message_id: ID + id: ID = Field( + ..., + description='Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition', + ) + actuator_id: ID = Field( + ..., description='ID of the actuator this Instruction belongs to.' + ) + operation_mode_id: ID = Field(..., description='ID of the DDBC.OperationMode') + operation_mode_factor: float = Field( + ..., + description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', + ) + + +class DDBCAverageDemandRateForecast(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.AverageDemandRateForecast'] = ( + 'DDBC.AverageDemandRateForecast' + ) + message_id: ID + start_time: AwareDatetime = Field(..., description='Start time of the profile.') + elements: List[DDBCAverageDemandRateForecastElement] = Field( + ..., + description='Elements of the profile. Elements must be placed in chronological order.', + max_length=288, + min_length=1, + ) + + +class PowerValue(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + commodity_quantity: CommodityQuantity = Field( + ..., description='The power quantity the value refers to' + ) + value: float = Field( + ..., + description='Power value expressed in the unit associated with the CommodityQuantity', + ) + + +class PowerForecastValue(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + value_upper_limit: Optional[float] = Field( + None, + description='The upper boundary of the range with 100 % certainty the power value is in it', + ) + value_upper_95PPR: Optional[float] = Field( + None, + description='The upper boundary of the range with 95 % certainty the power value is in it', + ) + value_upper_68PPR: Optional[float] = Field( + None, + description='The upper boundary of the range with 68 % certainty the power value is in it', + ) + value_expected: float = Field(..., description='The expected power value.') + value_lower_68PPR: Optional[float] = Field( + None, + description='The lower boundary of the range with 68 % certainty the power value is in it', + ) + value_lower_95PPR: Optional[float] = Field( + None, + description='The lower boundary of the range with 95 % certainty the power value is in it', + ) + value_lower_limit: Optional[float] = Field( + None, + description='The lower boundary of the range with 100 % certainty the power value is in it', + ) + commodity_quantity: CommodityQuantity = Field( + ..., description='The power quantity the value refers to' + ) + + +class PowerRange(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + start_of_range: float = Field( + ..., description='Power value that defines the start of the range.' + ) + end_of_range: float = Field( + ..., description='Power value that defines the end of the range.' + ) + commodity_quantity: CommodityQuantity = Field( + ..., description='The power quantity the values refer to' + ) + + +class Role(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + role: RoleType = Field( + ..., description='Role type of the Resource Manager for the given commodity' + ) + commodity: Commodity = Field(..., description='Commodity the role refers to.') + + +class PowerForecastElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + duration: Duration = Field(..., description='Duration of the PowerForecastElement') + power_values: List[PowerForecastValue] = Field( + ..., + description='The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.', + max_length=10, + min_length=1, + ) + + +class PEBCAllowedLimitRange(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + commodity_quantity: CommodityQuantity = Field( + ..., description='Type of power quantity this PEBC.AllowedLimitRange applies to' + ) + limit_type: PEBCPowerEnvelopeLimitType = Field( + ..., + description='Indicates if this ranges applies to the upper limit or the lower limit', + ) + range_boundary: NumberRange = Field( + ..., + description='Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ', + ) + abnormal_condition_only: bool = Field( + ..., + description='Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition', + ) + + +class PEBCPowerEnvelope(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + commodity_quantity: CommodityQuantity = Field( + ..., description='Type of power quantity this PEBC.PowerEnvelope applies to' + ) + power_envelope_elements: List[PEBCPowerEnvelopeElement] = Field( + ..., + description='The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.', + max_length=288, + min_length=1, + ) + + +class PPBCPowerSequenceElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + duration: Duration = Field( + ..., description='Duration of the PPBC.PowerSequenceElement.' + ) + power_values: List[PowerForecastValue] = Field( + ..., + description='The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.', + max_length=10, + min_length=1, + ) + + +class PPBCPowerSequenceContainerStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + power_profile_id: ID = Field( + ..., + description='ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ', + ) + sequence_container_id: ID = Field( + ..., + description='ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.', + ) + selected_sequence_id: Optional[ID] = Field( + None, + description='ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.', + ) + progress: Optional[Duration] = Field( + None, + description='Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.', + ) + status: PPBCPowerSequenceStatus = Field( + ..., description='Status of the selected PPBC.PowerSequence' + ) + + +class OMBCOperationMode(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + ) + power_ranges: List[PowerRange] = Field( + ..., + description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + max_length=10, + min_length=1, + ) + running_costs: Optional[NumberRange] = Field( + None, + description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + ) + abnormal_condition_only: bool = Field( + ..., + description='Indicates if this OMBC.OperationMode may only be used during an abnormal condition.', + ) + + +class FRBCOperationModeElement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + fill_level_range: NumberRange = Field( + ..., + description='The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.', + ) + fill_rate: NumberRange = Field( + ..., + description='Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ', + ) + power_ranges: List[PowerRange] = Field( + ..., + description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + max_length=10, + min_length=1, + ) + running_costs: Optional[NumberRange] = Field( + None, + description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + ) + + +class DDBCOperationMode(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + Id: ID = Field( + ..., + description='ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + ) + power_ranges: List[PowerRange] = Field( + ..., + description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + max_length=10, + min_length=1, + ) + supply_range: NumberRange = Field( + ..., + description='The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.', + ) + running_costs: Optional[NumberRange] = Field( + None, + description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + ) + abnormal_condition_only: bool = Field( + ..., + description='Indicates if this DDBC.OperationMode may only be used during an abnormal condition.', + ) + + +class ResourceManagerDetails(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['ResourceManagerDetails'] = 'ResourceManagerDetails' + message_id: ID + resource_id: ID = Field( + ..., + description='Identifier of the Resource Manager. Must be unique within the scope of the CEM.', + ) + name: Optional[str] = Field(None, description='Human readable name given by user') + roles: List[Role] = Field( + ..., + description='Each Resource Manager provides one or more energy Roles', + max_length=3, + min_length=1, + ) + manufacturer: Optional[str] = Field(None, description='Name of Manufacturer') + model: Optional[str] = Field( + None, + description='Name of the model of the device (provided by the manufacturer)', + ) + serial_number: Optional[str] = Field( + None, description='Serial number of the device (provided by the manufacturer)' + ) + firmware_version: Optional[str] = Field( + None, + description='Version identifier of the firmware used in the device (provided by the manufacturer)', + ) + instruction_processing_delay: Duration = Field( + ..., + description='The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction', + ) + available_control_types: List[ControlType] = Field( + ..., + description='The control types supported by this Resource Manager.', + max_length=5, + min_length=1, + ) + currency: Optional[Currency] = Field( + None, + description='Currency to be used for all information regarding costs. Mandatory if cost information is published.', + ) + provides_forecast: bool = Field( + ..., + description='Indicates whether the ResourceManager is able to provide PowerForecasts', + ) + provides_power_measurement_types: List[CommodityQuantity] = Field( + ..., + description='Array of all CommodityQuantities that this Resource Manager can provide measurements for. ', + max_length=10, + min_length=1, + ) + + +class PowerMeasurement(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PowerMeasurement'] = 'PowerMeasurement' + message_id: ID + measurement_timestamp: AwareDatetime = Field( + ..., description='Timestamp when PowerValues were measured.' + ) + values: List[PowerValue] = Field( + ..., + description='Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).', + max_length=10, + min_length=1, + ) + + +class PowerForecast(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PowerForecast'] = 'PowerForecast' + message_id: ID + start_time: AwareDatetime = Field( + ..., description='Start time of time period that is covered by the profile.' + ) + elements: List[PowerForecastElement] = Field( + ..., + description='Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.', + max_length=288, + min_length=1, + ) + + +class PEBCPowerConstraints(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PEBC.PowerConstraints'] = 'PEBC.PowerConstraints' + message_id: ID + id: ID = Field( + ..., + description='Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + valid_from: AwareDatetime = Field( + ..., description='Moment this PEBC.PowerConstraints start to be valid' + ) + valid_until: Optional[AwareDatetime] = Field( + None, + description='Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.', + ) + consequence_type: PEBCPowerEnvelopeConsequenceType = Field( + ..., description='Type of consequence of limiting power' + ) + allowed_limit_ranges: List[PEBCAllowedLimitRange] = Field( + ..., + description='The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.', + max_length=100, + min_length=2, + ) + + +class PEBCInstruction(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PEBC.Instruction'] = 'PEBC.Instruction' + message_id: ID + id: ID = Field( + ..., + description='Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + execution_time: AwareDatetime = Field( + ..., + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + ) + abnormal_condition: bool = Field( + ..., + description='Indicates if this is an instruction during an abnormal condition.', + ) + power_constraints_id: ID = Field( + ..., + description='Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.', + ) + power_envelopes: List[PEBCPowerEnvelope] = Field( + ..., + description='The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.', + max_length=10, + min_length=1, + ) + + +class PPBCPowerProfileStatus(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.PowerProfileStatus'] = 'PPBC.PowerProfileStatus' + message_id: ID + sequence_container_status: List[PPBCPowerSequenceContainerStatus] = Field( + ..., + description='Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.', + max_length=1000, + min_length=1, + ) + + +class OMBCSystemDescription(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['OMBC.SystemDescription'] = 'OMBC.SystemDescription' + message_id: ID + valid_from: AwareDatetime = Field( + ..., + description='Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + ) + operation_modes: List[OMBCOperationMode] = Field( + ..., + description='OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.', + max_length=100, + min_length=1, + ) + transitions: List[Transition] = Field( + ..., + description='Possible transitions to switch from one OMBC.OperationMode to another.', + max_length=1000, + min_length=0, + ) + timers: List[Timer] = Field( + ..., + description='Timers that control when certain transitions can be made.', + max_length=1000, + min_length=0, + ) + + +class PPBCPowerSequence(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.', + ) + elements: List[PPBCPowerSequenceElement] = Field( + ..., + description='List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.', + max_length=288, + min_length=1, + ) + is_interruptible: bool = Field( + ..., + description='Indicates whether the option of pausing a sequence is available.', + ) + max_pause_before: Optional[Duration] = Field( + None, + description='The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one', + ) + abnormal_condition_only: bool = Field( + ..., + description='Indicates if this PPBC.PowerSequence may only be used during an abnormal condition', + ) + + +class FRBCOperationMode(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + ) + elements: List[FRBCOperationModeElement] = Field( + ..., + description='List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.', + max_length=100, + min_length=1, + ) + abnormal_condition_only: bool = Field( + ..., + description='Indicates if this FRBC.OperationMode may only be used during an abnormal condition', + ) + + +class DDBCActuatorDescription(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', + ) + supported_commodites: List[Commodity] = Field( + ..., + description='Commodities supported by the operation modes of this actuator. There shall be at least one commodity', + max_length=4, + min_length=1, + ) + operation_modes: List[DDBCOperationMode] = Field( + ..., + description='List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.', + max_length=100, + min_length=1, + ) + transitions: List[Transition] = Field( + ..., + description='List of Transitions between Operation Modes. Shall contain at least one Transition.', + max_length=1000, + min_length=0, + ) + timers: List[Timer] = Field( + ..., + description='List of Timers associated with Transitions for this Actuator. Can be empty.', + max_length=1000, + min_length=0, + ) + + +class DDBCSystemDescription(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['DDBC.SystemDescription'] = 'DDBC.SystemDescription' + message_id: ID + valid_from: AwareDatetime = Field( + ..., + description='Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + ) + actuators: List[DDBCActuatorDescription] = Field( + ..., + description='List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.', + max_length=10, + min_length=1, + ) + present_demand_rate: NumberRange = Field( + ..., description='Present demand rate that needs to be satisfied by the system' + ) + provides_average_demand_rate_forecast: bool = Field( + ..., + description='Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.', + ) + + +class PPBCPowerSequenceContainer(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.', + ) + power_sequences: List[PPBCPowerSequence] = Field( + ..., + description='List of alternative Sequences where one could be chosen by the CEM', + max_length=288, + min_length=1, + ) + + +class FRBCActuatorDescription(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + id: ID = Field( + ..., + description='ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + diagnostic_label: Optional[str] = Field( + None, + description='Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', + ) + supported_commodities: List[Commodity] = Field( + ..., + description='List of all supported Commodities.', + max_length=4, + min_length=1, + ) + operation_modes: List[FRBCOperationMode] = Field( + ..., + description='Provided FRBC.OperationModes associated with this actuator', + max_length=100, + min_length=1, + ) + transitions: List[Transition] = Field( + ..., + description='Possible transitions between FRBC.OperationModes associated with this actuator.', + max_length=1000, + min_length=0, + ) + timers: List[Timer] = Field( + ..., + description='List of Timers associated with this actuator', + max_length=1000, + min_length=0, + ) + + +class PPBCPowerProfileDefinition(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['PPBC.PowerProfileDefinition'] = 'PPBC.PowerProfileDefinition' + message_id: ID + id: ID = Field( + ..., + description='ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + ) + start_time: AwareDatetime = Field( + ..., + description='Indicates the first possible time the first PPBC.PowerSequence could start', + ) + end_time: AwareDatetime = Field( + ..., + description='Indicates when the last PPBC.PowerSequence shall be finished at the latest', + ) + power_sequences_containers: List[PPBCPowerSequenceContainer] = Field( + ..., + description='The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.', + max_length=1000, + min_length=1, + ) + + +class FRBCSystemDescription(BaseModel): + model_config = ConfigDict( + extra='forbid', + ) + message_type: Literal['FRBC.SystemDescription'] = 'FRBC.SystemDescription' + message_id: ID + valid_from: AwareDatetime = Field( + ..., + description='Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + ) + actuators: List[FRBCActuatorDescription] = Field( + ..., description='Details of all Actuators.', max_length=10, min_length=1 + ) + storage: FRBCStorageDescription = Field(..., description='Details of the storage.') diff --git a/build/lib/s2python/ppbc/__init__.py b/build/lib/s2python/ppbc/__init__.py new file mode 100644 index 0000000..28e4e0e --- /dev/null +++ b/build/lib/s2python/ppbc/__init__.py @@ -0,0 +1 @@ +from s2python.ppbc.ppbc_schedule_instruction import PPBCScheduleInstruction diff --git a/build/lib/s2python/ppbc/ppbc_schedule_instruction.py b/build/lib/s2python/ppbc/ppbc_schedule_instruction.py new file mode 100644 index 0000000..c794b78 --- /dev/null +++ b/build/lib/s2python/ppbc/ppbc_schedule_instruction.py @@ -0,0 +1,33 @@ +import uuid + +from s2python.generated.gen_s2 import ( + PPBCScheduleInstruction as GenPPBCScheduleInstruction, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class PPBCScheduleInstruction( + GenPPBCScheduleInstruction, S2Message["PPBCScheduleInstruction"] +): + model_config = GenPPBCScheduleInstruction.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["id"] # type: ignore[assignment] + + power_profile_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ + "power_profile_id" + ] # type: ignore[assignment] + + message_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["message_id"] # type: ignore[assignment] + + sequence_container_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ + "sequence_container_id" + ] # type: ignore[assignment] + + power_sequence_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ + "power_sequence_id" + ] # type: ignore[assignment] diff --git a/build/lib/s2python/py.typed b/build/lib/s2python/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/build/lib/s2python/reception_status_awaiter.py b/build/lib/s2python/reception_status_awaiter.py new file mode 100644 index 0000000..5c4bd42 --- /dev/null +++ b/build/lib/s2python/reception_status_awaiter.py @@ -0,0 +1,60 @@ +"""ReceptationStatusAwaiter class which notifies any coroutine waiting for a certain reception status message. + +Copied from +https://github.com/flexiblepower/s2-analyzer/blob/main/backend/s2_analyzer_backend/reception_status_awaiter.py under +Apache2 license on 31-08-2024. +""" + +import asyncio +import uuid +from typing import Dict + +from s2python.common import ReceptionStatus + + +class ReceptionStatusAwaiter: + received: Dict[uuid.UUID, ReceptionStatus] + awaiting: Dict[uuid.UUID, asyncio.Event] + + def __init__(self) -> None: + self.received = {} + self.awaiting = {} + + async def wait_for_reception_status( + self, message_id: uuid.UUID, timeout_reception_status: float + ) -> ReceptionStatus: + if message_id in self.received: + reception_status = self.received[message_id] + else: + if message_id in self.awaiting: + received_event = self.awaiting[message_id] + else: + received_event = asyncio.Event() + self.awaiting[message_id] = received_event + + await asyncio.wait_for(received_event.wait(), timeout_reception_status) + reception_status = self.received[message_id] + + if message_id in self.awaiting: + del self.awaiting[message_id] + + return reception_status + + async def receive_reception_status(self, reception_status: ReceptionStatus) -> None: + if not isinstance(reception_status, ReceptionStatus): + raise RuntimeError( + f"Expected a ReceptionStatus but received message {reception_status}" + ) + + if reception_status.subject_message_id in self.received: + raise RuntimeError( + f"ReceptationStatus for message_subject_id {reception_status.subject_message_id} has already " + f"been received!" + ) + + self.received[reception_status.subject_message_id] = reception_status + awaiting = self.awaiting.get(reception_status.subject_message_id) + + if awaiting: + awaiting.set() + del self.awaiting[reception_status.subject_message_id] diff --git a/build/lib/s2python/s2_connection.py b/build/lib/s2python/s2_connection.py new file mode 100644 index 0000000..188ecc7 --- /dev/null +++ b/build/lib/s2python/s2_connection.py @@ -0,0 +1,526 @@ +import asyncio +import json +import logging +import time +import threading +import uuid +from dataclasses import dataclass +from typing import Optional, List, Type, Dict, Callable, Awaitable, Union + +import websockets +from websockets.asyncio.client import ClientConnection as WSConnection, connect as ws_connect + +from s2python.common import ( + ReceptionStatusValues, + ReceptionStatus, + Handshake, + EnergyManagementRole, + Role, + HandshakeResponse, + ResourceManagerDetails, + Duration, + Currency, + SelectControlType, +) +from s2python.generated.gen_s2 import CommodityQuantity +from s2python.reception_status_awaiter import ReceptionStatusAwaiter +from s2python.s2_control_type import S2ControlType +from s2python.s2_parser import S2Parser +from s2python.s2_validation_error import S2ValidationError +from s2python.validate_values_mixin import S2Message +from s2python.version import S2_VERSION + +logger = logging.getLogger("s2python") + + +@dataclass +class AssetDetails: # pylint: disable=too-many-instance-attributes + resource_id: str + + provides_forecast: bool + provides_power_measurements: List[CommodityQuantity] + + instruction_processing_delay: Duration + roles: List[Role] + currency: Optional[Currency] = None + + name: Optional[str] = None + manufacturer: Optional[str] = None + model: Optional[str] = None + firmware_version: Optional[str] = None + serial_number: Optional[str] = None + + 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 + ], + currency=self.currency, + firmware_version=self.firmware_version, + instruction_processing_delay=self.instruction_processing_delay, + manufacturer=self.manufacturer, + message_id=uuid.uuid4(), + model=self.model, + name=self.name, + provides_forecast=self.provides_forecast, + provides_power_measurement_types=self.provides_power_measurements, + resource_id=self.resource_id, + roles=self.roles, + serial_number=self.serial_number, + ) + + +S2MessageHandler = Union[ + Callable[["S2Connection", S2Message, Callable[[], None]], None], + Callable[["S2Connection", S2Message, Awaitable[None]], Awaitable[None]], +] + + +class SendOkay: + status_is_send: threading.Event + connection: "S2Connection" + subject_message_id: uuid.UUID + + def __init__(self, connection: "S2Connection", subject_message_id: uuid.UUID): + self.status_is_send = threading.Event() + self.connection = connection + self.subject_message_id = subject_message_id + + async def run_async(self) -> None: + self.status_is_send.set() + + await self.connection.respond_with_reception_status( + subject_message_id=str(self.subject_message_id), + status=ReceptionStatusValues.OK, + diagnostic_label="Processed okay.", + ) + + def run_sync(self) -> None: + self.status_is_send.set() + + self.connection.respond_with_reception_status_sync( + subject_message_id=str(self.subject_message_id), + status=ReceptionStatusValues.OK, + diagnostic_label="Processed okay.", + ) + + async def ensure_send_async(self, type_msg: Type[S2Message]) -> None: + if not self.status_is_send.is_set(): + logger.warning( + "Handler for message %s %s did not call send_okay / function to send the ReceptionStatus. " + "Sending it now.", + type_msg, + self.subject_message_id, + ) + await self.run_async() + + def ensure_send_sync(self, type_msg: Type[S2Message]) -> None: + if not self.status_is_send.is_set(): + logger.warning( + "Handler for message %s %s did not call send_okay / function to send the ReceptionStatus. " + "Sending it now.", + type_msg, + self.subject_message_id, + ) + self.run_sync() + + +class MessageHandlers: + handlers: Dict[Type[S2Message], S2MessageHandler] + + def __init__(self) -> None: + self.handlers = {} + + async def handle_message(self, connection: "S2Connection", msg: S2Message) -> None: + """Handle the S2 message using the registered handler. + + :param connection: The S2 conncetion the `msg` is received from. + :param msg: The S2 message + """ + handler = self.handlers.get(type(msg)) + if handler is not None: + send_okay = SendOkay(connection, msg.message_id) # type: ignore[attr-defined] + + try: + if asyncio.iscoroutinefunction(handler): + await handler(connection, msg, send_okay.run_async()) # type: ignore[arg-type] + await send_okay.ensure_send_async(type(msg)) + else: + + def do_message() -> None: + handler(connection, msg, send_okay.run_sync) # type: ignore[arg-type] + send_okay.ensure_send_sync(type(msg)) + + eventloop = asyncio.get_event_loop() + await eventloop.run_in_executor(executor=None, func=do_message) + except Exception: + if not send_okay.status_is_send.is_set(): + await connection.respond_with_reception_status( + subject_message_id=str(msg.message_id), # type: ignore[attr-defined] + status=ReceptionStatusValues.PERMANENT_ERROR, + diagnostic_label=f"While processing message {msg.message_id} " # type: ignore[attr-defined] + f"an unrecoverable error occurred.", + ) + raise + else: + logger.warning( + "Received a message of type %s but no handler is registered. Ignoring the message.", + type(msg), + ) + + 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. + :param handler: The function (asynchronuous or normal) which should handle the S2 message. + """ + self.handlers[msg_type] = handler + + +class S2Connection: # pylint: disable=too-many-instance-attributes + url: str + reconnect: bool + reception_status_awaiter: ReceptionStatusAwaiter + ws: Optional[WSConnection] + s2_parser: S2Parser + control_types: List[S2ControlType] + role: EnergyManagementRole + asset_details: AssetDetails + + _thread: threading.Thread + + _handlers: MessageHandlers + _current_control_type: Optional[S2ControlType] + _received_messages: asyncio.Queue + + _eventloop: asyncio.AbstractEventLoop + _stop_event: asyncio.Event + _restart_connection_event: asyncio.Event + + def __init__( # pylint: disable=too-many-arguments + self, + url: str, + role: EnergyManagementRole, + control_types: List[S2ControlType], + asset_details: AssetDetails, + reconnect: bool = False, + ) -> None: + self.url = url + self.reconnect = reconnect + self.reception_status_awaiter = ReceptionStatusAwaiter() + self.ws = None + self.s2_parser = S2Parser() + + self._handlers = MessageHandlers() + self._current_control_type = None + + self._eventloop = asyncio.new_event_loop() + + self.control_types = control_types + self.role = role + self.asset_details = asset_details + + 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) + + def start_as_rm(self) -> None: + self._run_eventloop(self._run_as_rm()) + + def _run_eventloop(self, main_task: Awaitable[None]) -> None: + self._thread = threading.current_thread() + logger.debug("Starting eventloop") + try: + self._eventloop.run_until_complete(main_task) + except asyncio.CancelledError: + pass + logger.debug("S2 connection thread has stopped.") + + def stop(self) -> None: + """Stops the S2 connection. + + Note: Ensure this method is called from a different thread than the thread running the S2 connection. + Otherwise it will block waiting on the coroutine _do_stop to terminate successfully but it can't run + the coroutine. A `RuntimeError` will be raised to prevent the indefinite block. + """ + 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!" + ) + if self._eventloop.is_running(): + asyncio.run_coroutine_threadsafe(self._do_stop(), self._eventloop).result() + self._thread.join() + logger.info("Stopped the S2 connection.") + + async def _do_stop(self) -> None: + logger.info("Will stop the S2 connection.") + self._stop_event.set() + + async def _run_as_rm(self) -> None: + logger.debug("Connecting as S2 resource manager.") + + self._stop_event = asyncio.Event() + + first_run = True + + while (first_run or self.reconnect) and not self._stop_event.is_set(): + first_run = False + self._restart_connection_event = asyncio.Event() + await self._connect_and_run() + time.sleep(1) + + logger.debug("Finished S2 connection eventloop.") + + async def _connect_and_run(self) -> None: + self._received_messages = asyncio.Queue() + await self._connect_ws() + if self.ws: + + async def wait_till_stop() -> None: + await self._stop_event.wait() + + async def wait_till_connection_restart() -> None: + await self._restart_connection_event.wait() + + background_tasks = [ + self._eventloop.create_task(self._receive_messages()), + self._eventloop.create_task(wait_till_stop()), + self._eventloop.create_task(self._connect_as_rm()), + self._eventloop.create_task(wait_till_connection_restart()), + ] + + (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 + + for task in done: + try: + await task + except asyncio.CancelledError: + pass + except (websockets.ConnectionClosedError, websockets.ConnectionClosedOK): + logger.info("The other party closed the websocket connection.") + + for task in pending: + try: + task.cancel() + await task + except asyncio.CancelledError: + pass + + await self.ws.close() + await self.ws.wait_closed() + + async def _connect_ws(self) -> None: + try: + self.ws = await ws_connect(uri=self.url) + except (EOFError, OSError) as e: + logger.info("Could not connect due to: %s", str(e)) + + 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] + ) + ) + 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: + if not isinstance(message, Handshake): + logger.error( + "Handler for Handshake received a message of the wrong type: %s", type(message) + ) + return + + logger.debug( + "%s supports S2 protocol versions: %s", + message.role, + message.supported_protocol_versions, + ) + await send_okay + + async def handle_handshake_response_as_rm( + self, _: "S2Connection", message: S2Message, send_okay: Awaitable[None] + ) -> None: + if not isinstance(message, HandshakeResponse): + logger.error( + "Handler for HandshakeResponse received a message of the wrong type: %s", + type(message), + ) + return + + logger.debug("Received HandshakeResponse %s", message.to_json()) + + logger.debug("CEM selected to use version %s", message.selected_protocol_version) + await send_okay + logger.debug("Handshake complete. Sending first ResourceManagerDetails.") + + await self.send_msg_and_await_reception_status_async( + self.asset_details.to_resource_manager_details(self.control_types) + ) + + async def handle_select_control_type_as_rm( + self, _: "S2Connection", message: S2Message, send_okay: Awaitable[None] + ) -> None: + if not isinstance(message, SelectControlType): + logger.error( + "Handler for SelectControlType received a message of the wrong type: %s", + type(message), + ) + return + + await send_okay + + 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 + ) + + if self._current_control_type is not None: + 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) + self._current_control_type.register_handlers(self._handlers) + + async def _receive_messages(self) -> None: + """Receives all incoming messages in the form of a generator. + + Will also receive the ReceptionStatus messages but instead of yielding these messages, they are routed + 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." + ) + + logger.info("S2 connection has started to receive messages.") + + async for message in self.ws: + try: + s2_msg: S2Message = self.s2_parser.parse_as_any_message(message) + except json.JSONDecodeError: + await self._send_and_forget( + ReceptionStatus( + subject_message_id="00000000-0000-0000-0000-000000000000", + status=ReceptionStatusValues.INVALID_DATA, + diagnostic_label="Not valid json.", + ) + ) + except S2ValidationError as e: + json_msg = json.loads(message) + message_id = json_msg.get("message_id") + if message_id: + await self.respond_with_reception_status( + subject_message_id=message_id, + status=ReceptionStatusValues.INVALID_MESSAGE, + diagnostic_label=str(e), + ) + else: + await self.respond_with_reception_status( + subject_message_id="00000000-0000-0000-0000-000000000000", + status=ReceptionStatusValues.INVALID_DATA, + diagnostic_label="Message appears valid json but could not find a message_id field.", + ) + else: + logger.debug("Received message %s", s2_msg.to_json()) + + if isinstance(s2_msg, ReceptionStatus): + logger.debug( + "Message is a reception status for %s so registering in cache.", + s2_msg.subject_message_id, + ) + await self.reception_status_awaiter.receive_reception_status(s2_msg) + else: + await self._received_messages.put(s2_msg) + + 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." + ) + + json_msg = s2_msg.to_json() + logger.debug("Sending message %s", json_msg) + try: + await self.ws.send(json_msg) + except websockets.ConnectionClosedError as e: + logger.error("Unable to send message %s due to %s", s2_msg, str(e)) + self._restart_connection_event.set() + + async def respond_with_reception_status( + self, subject_message_id: str, status: ReceptionStatusValues, diagnostic_label: str + ) -> None: + 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, + status=status, + diagnostic_label=diagnostic_label, + ) + ) + + def respond_with_reception_status_sync( + self, subject_message_id: str, status: ReceptionStatusValues, diagnostic_label: str + ) -> None: + asyncio.run_coroutine_threadsafe( + 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 + ) -> ReceptionStatus: + await self._send_and_forget(s2_msg) + logger.debug( + "Waiting for ReceptionStatus for %s %s seconds", + s2_msg.message_id, # type: ignore[attr-defined] + timeout_reception_status, + ) + try: + reception_status = await self.reception_status_awaiter.wait_for_reception_status( + s2_msg.message_id, timeout_reception_status # type: ignore[attr-defined] + ) + except TimeoutError: + logger.error( + "Did not receive a reception status on time for %s", + s2_msg.message_id, # type: ignore[attr-defined] + ) + self._stop_event.set() + raise + + if reception_status.status != ReceptionStatusValues.OK and raise_on_error: + 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 + ) -> ReceptionStatus: + return asyncio.run_coroutine_threadsafe( + self.send_msg_and_await_reception_status_async( + s2_msg, timeout_reception_status, raise_on_error + ), + self._eventloop, + ).result() + + async def _handle_received_messages(self) -> None: + while True: + msg = await self._received_messages.get() + await self._handlers.handle_message(self, msg) diff --git a/build/lib/s2python/s2_control_type.py b/build/lib/s2python/s2_control_type.py new file mode 100644 index 0000000..547ead7 --- /dev/null +++ b/build/lib/s2python/s2_control_type.py @@ -0,0 +1,76 @@ +import abc +import typing + +from s2python.common import ControlType as ProtocolControlType +from s2python.frbc import FRBCInstruction +from s2python.ppbc import PPBCScheduleInstruction +from s2python.validate_values_mixin import S2Message + +if typing.TYPE_CHECKING: + from s2python.s2_connection import S2Connection, MessageHandlers + + +class S2ControlType(abc.ABC): + @abc.abstractmethod + def get_protocol_control_type(self) -> ProtocolControlType: ... + + @abc.abstractmethod + def register_handlers(self, handlers: "MessageHandlers") -> None: ... + + @abc.abstractmethod + def activate(self, conn: "S2Connection") -> None: ... + + @abc.abstractmethod + def deactivate(self, conn: "S2Connection") -> None: ... + + +class FRBCControlType(S2ControlType): + def get_protocol_control_type(self) -> ProtocolControlType: + return ProtocolControlType.FILL_RATE_BASED_CONTROL + + def register_handlers(self, handlers: "MessageHandlers") -> None: + handlers.register_handler(FRBCInstruction, 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: ... + + @abc.abstractmethod + def deactivate(self, conn: "S2Connection") -> None: ... + + +class PPBCControlType(S2ControlType): + def get_protocol_control_type(self) -> ProtocolControlType: + return ProtocolControlType.POWER_PROFILE_BASED_CONTROL + + def register_handlers(self, handlers: "MessageHandlers") -> None: + handlers.register_handler(PPBCScheduleInstruction, 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: ... + + @abc.abstractmethod + def deactivate(self, conn: "S2Connection") -> None: ... + + +class NoControlControlType(S2ControlType): + def get_protocol_control_type(self) -> ProtocolControlType: + return ProtocolControlType.NOT_CONTROLABLE + + def register_handlers(self, handlers: "MessageHandlers") -> None: + pass + + @abc.abstractmethod + def activate(self, conn: "S2Connection") -> None: ... + + @abc.abstractmethod + def deactivate(self, conn: "S2Connection") -> None: ... diff --git a/build/lib/s2python/s2_parser.py b/build/lib/s2python/s2_parser.py new file mode 100644 index 0000000..e1a5c43 --- /dev/null +++ b/build/lib/s2python/s2_parser.py @@ -0,0 +1,120 @@ +import json +import logging +from typing import Optional, TypeVar, Union, Type, Dict + +from s2python.common import ( + Handshake, + HandshakeResponse, + InstructionStatusUpdate, + PowerForecast, + PowerMeasurement, + ReceptionStatus, + ResourceManagerDetails, + RevokeObject, + SelectControlType, + SessionRequest, +) +from s2python.frbc import ( + FRBCActuatorStatus, + FRBCFillLevelTargetProfile, + FRBCInstruction, + FRBCLeakageBehaviour, + FRBCStorageStatus, + FRBCSystemDescription, + FRBCTimerStatus, + FRBCUsageForecast, +) +from s2python.ppbc import PPBCScheduleInstruction + +from s2python.validate_values_mixin import S2Message +from s2python.s2_validation_error import S2ValidationError + + +LOGGER = logging.getLogger(__name__) +S2MessageType = str + +M = TypeVar("M", bound=S2Message) + + +# May be generated with development_utilities/generate_s2_message_type_to_class.py +TYPE_TO_MESSAGE_CLASS: Dict[str, Type[S2Message]] = { + "FRBC.ActuatorStatus": FRBCActuatorStatus, + "FRBC.FillLevelTargetProfile": FRBCFillLevelTargetProfile, + "FRBC.Instruction": FRBCInstruction, + "FRBC.LeakageBehaviour": FRBCLeakageBehaviour, + "FRBC.StorageStatus": FRBCStorageStatus, + "FRBC.SystemDescription": FRBCSystemDescription, + "FRBC.TimerStatus": FRBCTimerStatus, + "FRBC.UsageForecast": FRBCUsageForecast, + "PPBC.ScheduleInstruction": PPBCScheduleInstruction, + "Handshake": Handshake, + "HandshakeResponse": HandshakeResponse, + "InstructionStatusUpdate": InstructionStatusUpdate, + "PowerForecast": PowerForecast, + "PowerMeasurement": PowerMeasurement, + "ReceptionStatus": ReceptionStatus, + "ResourceManagerDetails": ResourceManagerDetails, + "RevokeObject": RevokeObject, + "SelectControlType": SelectControlType, + "SessionRequest": SessionRequest, +} + + +class S2Parser: + @staticmethod + def _parse_json_if_required(unparsed_message: Union[dict, str, bytes]) -> dict: + if isinstance(unparsed_message, (str, bytes)): + return json.loads(unparsed_message) + return unparsed_message + + @staticmethod + def parse_as_any_message(unparsed_message: Union[dict, str, bytes]) -> S2Message: + """Parse the message as any S2 python message regardless of message type. + + :param unparsed_message: The message as a JSON-formatted string or as a json-parsed dictionary. + :raises: S2ValidationError, json.JSONDecodeError + :return: The parsed S2 message if no errors were found. + """ + message_json = S2Parser._parse_json_if_required(unparsed_message) + message_type = S2Parser.parse_message_type(message_json) + + if message_type not in TYPE_TO_MESSAGE_CLASS: + raise S2ValidationError( + None, + message_json, + f"Unable to parse {message_type} as an S2 message. Type unknown.", + None, + ) + + 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: + """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. + :param as_message: The type of message that is expected within the `message` + :raises: S2ValidationError, json.JSONDecodeError + :return: The parsed S2 message if no errors were found. + """ + message_json = S2Parser._parse_json_if_required(unparsed_message) + return as_message.from_dict(message_json) + + @staticmethod + def parse_message_type( + unparsed_message: Union[dict, str, bytes], + ) -> Optional[S2MessageType]: + """Parse only the message type from the unparsed message. + + This is useful to call before `parse_as_message` to retrieve the message type and allows for strictly-typed + parsing. + + :param unparsed_message: The message as a JSON-formatted string or as a JSON-parsed dictionary. + :raises: json.JSONDecodeError + :return: The parsed S2 message type if no errors were found. + """ + message_json = S2Parser._parse_json_if_required(unparsed_message) + + return message_json.get("message_type") diff --git a/build/lib/s2python/s2_validation_error.py b/build/lib/s2python/s2_validation_error.py new file mode 100644 index 0000000..8ab7664 --- /dev/null +++ b/build/lib/s2python/s2_validation_error.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from typing import Union, Type, Optional + +from pydantic import ValidationError +from pydantic.v1.error_wrappers import ValidationError as ValidationErrorV1 + + +@dataclass +class S2ValidationError(Exception): + class_: Optional[Type] + obj: object + msg: str + pydantic_validation_error: Union[ValidationErrorV1, ValidationError, TypeError, None] diff --git a/build/lib/s2python/utils.py b/build/lib/s2python/utils.py new file mode 100644 index 0000000..b4f78ed --- /dev/null +++ b/build/lib/s2python/utils.py @@ -0,0 +1,8 @@ +from typing import Generator, Tuple, List, TypeVar + +P = TypeVar("P") + + +def pairwise(arr: List[P]) -> Generator[Tuple[P, P], None, None]: + for i in range(max(len(arr) - 1, 0)): + yield arr[i], arr[i + 1] diff --git a/build/lib/s2python/validate_values_mixin.py b/build/lib/s2python/validate_values_mixin.py new file mode 100644 index 0000000..7d0d9d6 --- /dev/null +++ b/build/lib/s2python/validate_values_mixin.py @@ -0,0 +1,70 @@ +from typing import TypeVar, Generic, Type, Callable, Any, Union, AbstractSet, Mapping, List, Dict + +from pydantic import BaseModel, ValidationError # pylint: disable=no-name-in-module +from pydantic.v1.error_wrappers import display_errors # pylint: disable=no-name-in-module + +from s2python.s2_validation_error import S2ValidationError + +B_co = TypeVar("B_co", bound=BaseModel, covariant=True) + +IntStr = Union[int, str] +AbstractSetIntStr = AbstractSet[IntStr] +MappingIntStrAny = Mapping[IntStr, Any] + + +C = TypeVar("C", bound="BaseModel") + + +class S2Message(BaseModel, Generic[C]): + def to_json(self: C) -> str: + try: + return self.model_dump_json(by_alias=True, exclude_none=True) + except (ValidationError, TypeError) as e: + raise S2ValidationError( + type(self), self, "Pydantic raised a format validation error.", e + ) from e + + def to_dict(self: C) -> Dict: + return self.model_dump() + + @classmethod + def from_json(cls: Type[C], json_str: str) -> C: + gen_model: C = cls.model_validate_json(json_str) + return gen_model + + @classmethod + def from_dict(cls: Type[C], json_dict: dict) -> C: + gen_model: C = cls.model_validate(json_dict) + return gen_model + + +def convert_to_s2exception(f: Callable) -> Callable: + def inner(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: + try: + return f(*args, **kwargs) + except ValidationError as e: + if isinstance(args[0], BaseModel): + class_type = type(args[0]) + args = args[1:] + else: + class_type = None + + raise S2ValidationError(class_type, args, display_errors(e.errors()), e) from e # type: ignore[arg-type] + except TypeError as e: + raise S2ValidationError(None, args, str(e), e) from e + + inner.__doc__ = f.__doc__ + inner.__annotations__ = f.__annotations__ + + return inner + + +def catch_and_convert_exceptions(input_class: Type[S2Message[B_co]]) -> Type[S2Message[B_co]]: + input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign] + input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign] + input_class.model_validate_json = convert_to_s2exception( # type: ignore[method-assign] + input_class.model_validate_json + ) + input_class.model_validate = convert_to_s2exception(input_class.model_validate) # type: ignore[method-assign] + + return input_class diff --git a/build/lib/s2python/version.py b/build/lib/s2python/version.py new file mode 100644 index 0000000..3789fe8 --- /dev/null +++ b/build/lib/s2python/version.py @@ -0,0 +1,3 @@ +VERSION = "0.2.0" + +S2_VERSION = "0.0.2-beta" diff --git a/src/s2python/ppbc/__init__.py b/src/s2python/ppbc/__init__.py new file mode 100644 index 0000000..28e4e0e --- /dev/null +++ b/src/s2python/ppbc/__init__.py @@ -0,0 +1 @@ +from s2python.ppbc.ppbc_schedule_instruction import PPBCScheduleInstruction diff --git a/src/s2python/ppbc/ppbc_schedule_instruction.py b/src/s2python/ppbc/ppbc_schedule_instruction.py new file mode 100644 index 0000000..c794b78 --- /dev/null +++ b/src/s2python/ppbc/ppbc_schedule_instruction.py @@ -0,0 +1,33 @@ +import uuid + +from s2python.generated.gen_s2 import ( + PPBCScheduleInstruction as GenPPBCScheduleInstruction, +) +from s2python.validate_values_mixin import ( + catch_and_convert_exceptions, + S2Message, +) + + +@catch_and_convert_exceptions +class PPBCScheduleInstruction( + GenPPBCScheduleInstruction, S2Message["PPBCScheduleInstruction"] +): + model_config = GenPPBCScheduleInstruction.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["id"] # type: ignore[assignment] + + power_profile_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ + "power_profile_id" + ] # type: ignore[assignment] + + message_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["message_id"] # type: ignore[assignment] + + sequence_container_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ + "sequence_container_id" + ] # type: ignore[assignment] + + power_sequence_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ + "power_sequence_id" + ] # type: ignore[assignment] From abecea8e69ab2f7de38cee087b0d35a99af4c85e Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Fri, 13 Dec 2024 20:35:52 +0100 Subject: [PATCH 03/27] PPBC sub-modules created --- .gitignore | 1 + .../gen_unit_test_template.py | 25 +- .../ppbc/ppbc_end_interruption_instruction.py | 0 .../ppbc/ppbc_power_profile_definition.py | 0 .../ppbc/ppbc_power_profile_status.py | 0 src/s2python/ppbc/ppbc_power_sequence.py | 0 .../ppbc/ppbc_power_sequence_container.py | 0 .../ppbc_power_sequence_container_status.py | 0 .../ppbc/ppbc_power_sequence_element.py | 0 .../ppbc_start_interruption_instruction.py | 0 .../frbc/frbc_actuator_description_test.py | 325 ++++++------------ tests/unit/frbc_actuator_description_test.py | 118 +++++++ 12 files changed, 243 insertions(+), 226 deletions(-) create mode 100644 src/s2python/ppbc/ppbc_end_interruption_instruction.py create mode 100644 src/s2python/ppbc/ppbc_power_profile_definition.py create mode 100644 src/s2python/ppbc/ppbc_power_profile_status.py create mode 100644 src/s2python/ppbc/ppbc_power_sequence.py create mode 100644 src/s2python/ppbc/ppbc_power_sequence_container.py create mode 100644 src/s2python/ppbc/ppbc_power_sequence_container_status.py create mode 100644 src/s2python/ppbc/ppbc_power_sequence_element.py create mode 100644 src/s2python/ppbc/ppbc_start_interruption_instruction.py create mode 100644 tests/unit/frbc_actuator_description_test.py diff --git a/.gitignore b/.gitignore index 14a680a..8a082ed 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ venv *venv* .tox/ dist/ +build/ diff --git a/development_utilities/gen_unit_test_template.py b/development_utilities/gen_unit_test_template.py index 93e2b03..d246d88 100644 --- a/development_utilities/gen_unit_test_template.py +++ b/development_utilities/gen_unit_test_template.py @@ -17,6 +17,7 @@ import uuid import pydantic +from pydantic.types import AwareDatetime from s2python import frbc from s2python.common import Duration, PowerRange, NumberRange @@ -64,7 +65,7 @@ def get_list_arg(field_type): def is_enum(field_type): - return issubclass(field_type, Enum) + return inspect.isclass(field_type) and issubclass(field_type, Enum) def snake_case(camelcased: str) -> str: @@ -111,6 +112,17 @@ def generate_json_test_data_for_field(field_type: Type): value = bool(random.randint(0, 1)) elif field_type is float: value = random.random() * 9000.0 + elif field_type is AwareDatetime: + # Generate a timezone-aware datetime + value = datetime.datetime( + year=random.randint(2020, 2023), + month=random.randint(1, 12), + day=random.randint(1, 28), + hour=random.randint(0, 23), + minute=random.randint(0, 59), + second=random.randint(0, 59), + tzinfo=datetime.timezone(datetime.timedelta(hours=random.randint(-12, 14))), + ) elif field_type is datetime.datetime: value = datetime.datetime( year=random.randint(2020, 2023), @@ -167,10 +179,15 @@ def dump_test_data_as_constructor_field_for(test_data, field_type: Type) -> str: value = str(test_data) elif field_type is float: value = str(test_data) - elif field_type is datetime.datetime: + elif field_type is AwareDatetime or field_type is datetime.datetime: test_data: datetime.datetime offset: datetime.timedelta = test_data.tzinfo.utcoffset(None) - value = f"datetime(year={test_data.year}, month={test_data.month}, day={test_data.day}, hour={test_data.hour}, minute={test_data.minute}, second={test_data.second}, tzinfo=offset(offset=timedelta(seconds={offset.total_seconds()})))" + value = ( + f"datetime(" + f"year={test_data.year}, month={test_data.month}, day={test_data.day}, " + f"hour={test_data.hour}, minute={test_data.minute}, second={test_data.second}, " + f"tzinfo=offset(offset=timedelta(seconds={offset.total_seconds()})))" + ) elif field_type is uuid.UUID: value = f'uuid.UUID("{test_data}")' else: @@ -217,7 +234,7 @@ def dump_test_data_as_json_field_for(test_data, field_type: Type): value = test_data elif field_type is float: value = test_data - elif field_type is datetime.datetime: + elif field_type is AwareDatetime or field_type is datetime.datetime: test_data: datetime.datetime value = test_data.isoformat() elif field_type is uuid.UUID: diff --git a/src/s2python/ppbc/ppbc_end_interruption_instruction.py b/src/s2python/ppbc/ppbc_end_interruption_instruction.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_power_profile_definition.py b/src/s2python/ppbc/ppbc_power_profile_definition.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_power_profile_status.py b/src/s2python/ppbc/ppbc_power_profile_status.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_power_sequence.py b/src/s2python/ppbc/ppbc_power_sequence.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_power_sequence_container.py b/src/s2python/ppbc/ppbc_power_sequence_container.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_power_sequence_element.py b/src/s2python/ppbc/ppbc_power_sequence_element.py new file mode 100644 index 0000000..e69de29 diff --git a/src/s2python/ppbc/ppbc_start_interruption_instruction.py b/src/s2python/ppbc/ppbc_start_interruption_instruction.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py index 1b4f31d..7dafd8e 100644 --- a/tests/unit/frbc/frbc_actuator_description_test.py +++ b/tests/unit/frbc/frbc_actuator_description_test.py @@ -1,241 +1,122 @@ + +from datetime import timedelta, datetime, timezone as offset import json -import uuid -from datetime import timedelta from unittest import TestCase +import uuid -from s2python.common import ( - Transition, - Duration, - Timer, - NumberRange, - PowerRange, - CommodityQuantity, - Commodity, -) -from s2python.frbc import ( - FRBCActuatorDescription, - FRBCOperationMode, - FRBCOperationModeElement, -) +from s2python.common import * +from s2python.frbc import * class FRBCActuatorDescriptionTest(TestCase): - def test__from_json__happy_path(self): + def test__from_json__happy_path_full(self): # Arrange - json_str = """{ - "diagnostic_label": "some name of actuator", - "id": "3bdec96b-be3b-4ba9-afa0-c4a0632dded5", - "operation_modes": [{ - "abnormal_condition_only": false, - "diagnostic_label": "om1", - "id": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", - "elements": [{ "fill_level_range": {"start_of_range": 4.0, "end_of_range": 5.0}, - "fill_rate": {"start_of_range": 0.13, "end_of_range": 10342.569}, - "power_ranges": [{"start_of_range": 400, "end_of_range": 6000, "commodity_quantity": "HEAT.TEMPERATURE"}, - {"start_of_range": 500, "end_of_range": 7000, "commodity_quantity": "ELECTRIC.POWER.L1"}], - "running_costs": {"start_of_range": 4.3, "end_of_range": 4.6}}] - }], - "supported_commodities": ["HEAT", "ELECTRICITY"], - "timers": [{ - "diagnostic_label": "timer1", - "duration": 2300, - "id": "3bdec10b-be3b-4ba9-afa0-c4a0632ffed6" - }], - "transitions": [{ "id": "2bdec96b-be3b-4ba9-afa0-c4a0632cced3", - "from": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", - "to": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", - "start_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], - "blocking_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], - "transition_costs": 4.3, - "transition_duration": 1500, - "abnormal_condition_only": false}] - }""" + json_str = """ +{ + "id": "a58e9c5e-511b-436c-9c65-ad44c778c4b8", + "diagnostic_label": "some-test-string3006", + "supported_commodities": [ + "GAS" + ], + "operation_modes": [ + { + "id": "5d104a14-491f-4138-a757-ff0cb6fd5525", + "diagnostic_label": "some-test-string7637", + "elements": [ + { + "fill_level_range": { + "start_of_range": 24714.62330770477, + "end_of_range": 49561.87219293591 + }, + "fill_rate": { + "start_of_range": 22860.909596605932, + "end_of_range": 50712.187596827665 + }, + "power_ranges": [ + { + "start_of_range": 31917.547195435036, + "end_of_range": 56269.308985594, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 1119.8343828050738, + "end_of_range": 24787.439438905614 + } + } + ], + "abnormal_condition_only": false + } + ], + "transitions": [ + { + "id": "47f2c50d-17b4-4892-97f0-f3f81abb5c36", + "from_": "8b235e80-5c21-452f-8c58-72e887ab5aab", + "to": "9c585f61-e1b1-4e53-9fb4-5d60e141d51f", + "start_timers": [ + "f94f42ff-f836-4ba9-97f9-da3aa24941d6" + ], + "blocking_timers": [ + "ce322294-db0f-4351-a971-8c175c52714f" + ], + "transition_costs": 347.97008821629373, + "transition_duration": 39801, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "73933f13-7c3a-4f76-9975-99acf562498e", + "diagnostic_label": "some-test-string3849", + "duration": 23667 + } + ] +} + """ # Act - frbc_actuator_description: FRBCActuatorDescription = ( - FRBCActuatorDescription.from_json(json_str) - ) + frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) # Assert - expected_timer = Timer( - id=uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"), - diagnostic_label="timer1", - duration=Duration.from_timedelta(timedelta(seconds=2.3)), - ) - - # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. - # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to - # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 - expected_transition = Transition( - **{ - "id": uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3"), - "from": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), - "to": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), - "start_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], - "blocking_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], - "transition_costs": 4.3, - "transition_duration": Duration.from_milliseconds(1500), - "abnormal_condition_only": False, - } - ) - expected_operation_mode_element = FRBCOperationModeElement( - fill_level_range=NumberRange(start_of_range=4.0, end_of_range=5.0), - fill_rate=NumberRange(start_of_range=0.13, end_of_range=10342.569), - power_ranges=[ - PowerRange( - start_of_range=400, - end_of_range=6000, - commodity_quantity=CommodityQuantity.HEAT_TEMPERATURE, - ), - PowerRange( - start_of_range=500, - end_of_range=7000, - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - ), - ], - running_costs=NumberRange(start_of_range=4.3, end_of_range=4.6), - ) - expected_operation_mode = FRBCOperationMode( - abnormal_condition_only=False, - diagnostic_label="om1", - id=uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), - elements=[expected_operation_mode_element], - ) - - self.assertEqual( - frbc_actuator_description.diagnostic_label, "some name of actuator" - ) - self.assertEqual( - frbc_actuator_description.id, - uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632dded5"), - ) - self.assertEqual( - frbc_actuator_description.supported_commodities, - [Commodity.HEAT, Commodity.ELECTRICITY], - ) - self.assertEqual( - frbc_actuator_description.operation_modes, [expected_operation_mode] - ) - self.assertEqual(frbc_actuator_description.timers, [expected_timer]) - self.assertEqual(frbc_actuator_description.transitions, [expected_transition]) + self.assertEqual(frbc_actuator_description.id, uuid.UUID("a58e9c5e-511b-436c-9c65-ad44c778c4b8")) + self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string3006") + self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) + self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("5d104a14-491f-4138-a757-ff0cb6fd5525"), diagnostic_label="some-test-string7637", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=24714.62330770477, end_of_range=49561.87219293591), fill_rate=NumberRange(start_of_range=22860.909596605932, end_of_range=50712.187596827665), power_ranges=[PowerRange(start_of_range=31917.547195435036, end_of_range=56269.308985594, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=1119.8343828050738, end_of_range=24787.439438905614))], abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("47f2c50d-17b4-4892-97f0-f3f81abb5c36"), from_=uuid.UUID("8b235e80-5c21-452f-8c58-72e887ab5aab"), to=uuid.UUID("9c585f61-e1b1-4e53-9fb4-5d60e141d51f"), start_timers=[uuid.UUID("f94f42ff-f836-4ba9-97f9-da3aa24941d6")], blocking_timers=[uuid.UUID("ce322294-db0f-4351-a971-8c175c52714f")], transition_costs=347.97008821629373, transition_duration=Duration.from_timedelta(timedelta(milliseconds=39801)), abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("73933f13-7c3a-4f76-9975-99acf562498e"), diagnostic_label="some-test-string3849", duration=Duration.from_timedelta(timedelta(milliseconds=23667)))]) - def test__to_json__happy_path(self): + def test__to_json__happy_path_full(self): # Arrange - timer = Timer( - id=uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"), - diagnostic_label="timer1", - duration=Duration.from_timedelta(timedelta(seconds=2.3)), - ) - - # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. - # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to - # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 - transition = Transition( - **{ - "id": uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3"), - "from": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), - "to": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), - "start_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], - "blocking_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], - "transition_costs": 4.3, - "transition_duration": Duration.from_milliseconds(1500), - "abnormal_condition_only": False, - } - ) - operation_mode_element = FRBCOperationModeElement( - fill_level_range=NumberRange(start_of_range=4.0, end_of_range=5.0), - fill_rate=NumberRange(start_of_range=0.13, end_of_range=10342.569), - power_ranges=[ - PowerRange( - start_of_range=400, - end_of_range=6000, - commodity_quantity=CommodityQuantity.HEAT_TEMPERATURE, - ), - PowerRange( - start_of_range=500, - end_of_range=7000, - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - ), - ], - running_costs=NumberRange(start_of_range=4.3, end_of_range=4.6), - ) - operation_mode = FRBCOperationMode( - abnormal_condition_only=False, - diagnostic_label="om1", - id=uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), - elements=[operation_mode_element], - ) - - frbc_actuator_description = FRBCActuatorDescription( - diagnostic_label="some name of actuator", - id=uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632dded5"), - supported_commodities=[Commodity.HEAT, Commodity.ELECTRICITY], - operation_modes=[operation_mode], - timers=[timer], - transitions=[transition], - ) + frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("a58e9c5e-511b-436c-9c65-ad44c778c4b8"), diagnostic_label="some-test-string3006", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("5d104a14-491f-4138-a757-ff0cb6fd5525"), diagnostic_label="some-test-string7637", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=24714.62330770477, end_of_range=49561.87219293591), fill_rate=NumberRange(start_of_range=22860.909596605932, end_of_range=50712.187596827665), power_ranges=[PowerRange(start_of_range=31917.547195435036, end_of_range=56269.308985594, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=1119.8343828050738, end_of_range=24787.439438905614))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("47f2c50d-17b4-4892-97f0-f3f81abb5c36"), from_=uuid.UUID("8b235e80-5c21-452f-8c58-72e887ab5aab"), to=uuid.UUID("9c585f61-e1b1-4e53-9fb4-5d60e141d51f"), start_timers=[uuid.UUID("f94f42ff-f836-4ba9-97f9-da3aa24941d6")], blocking_timers=[uuid.UUID("ce322294-db0f-4351-a971-8c175c52714f")], transition_costs=347.97008821629373, transition_duration=Duration.from_timedelta(timedelta(milliseconds=39801)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("73933f13-7c3a-4f76-9975-99acf562498e"), diagnostic_label="some-test-string3849", duration=Duration.from_timedelta(timedelta(milliseconds=23667)))]) # Act json_str = frbc_actuator_description.to_json() # Assert - expected_json = { - "diagnostic_label": "some name of actuator", - "id": "3bdec96b-be3b-4ba9-afa0-c4a0632dded5", - "operation_modes": [ - { - "abnormal_condition_only": False, - "diagnostic_label": "om1", - "id": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", - "elements": [ - { - "fill_level_range": { - "start_of_range": 4.0, - "end_of_range": 5.0, - }, - "fill_rate": { - "start_of_range": 0.13, - "end_of_range": 10342.569, - }, - "power_ranges": [ - { - "start_of_range": 400, - "end_of_range": 6000, - "commodity_quantity": "HEAT.TEMPERATURE", - }, - { - "start_of_range": 500, - "end_of_range": 7000, - "commodity_quantity": "ELECTRIC.POWER.L1", - }, - ], - "running_costs": { - "start_of_range": 4.3, - "end_of_range": 4.6, - }, - } - ], - } - ], - "supported_commodities": ["HEAT", "ELECTRICITY"], - "timers": [ - { - "diagnostic_label": "timer1", - "duration": 2300, - "id": "3bdec10b-be3b-4ba9-afa0-c4a0632ffed6", - } - ], - "transitions": [ - { - "id": "2bdec96b-be3b-4ba9-afa0-c4a0632cced3", - "from": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", - "to": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", - "start_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], - "blocking_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], - "transition_costs": 4.3, - "transition_duration": 1500, - "abnormal_condition_only": False, - } - ], - } + expected_json = { 'diagnostic_label': 'some-test-string3006', + 'id': 'a58e9c5e-511b-436c-9c65-ad44c778c4b8', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string7637', + 'elements': [ { 'fill_level_range': { 'end_of_range': 49561.87219293591, + 'start_of_range': 24714.62330770477}, + 'fill_rate': { 'end_of_range': 50712.187596827665, + 'start_of_range': 22860.909596605932}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 56269.308985594, + 'start_of_range': 31917.547195435036}], + 'running_costs': { 'end_of_range': 24787.439438905614, + 'start_of_range': 1119.8343828050738}}], + 'id': '5d104a14-491f-4138-a757-ff0cb6fd5525'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string3849', + 'duration': 23667, + 'id': '73933f13-7c3a-4f76-9975-99acf562498e'}], + 'transitions': [ { 'abnormal_condition_only': False, + 'blocking_timers': [ 'ce322294-db0f-4351-a971-8c175c52714f'], + 'from_': '8b235e80-5c21-452f-8c58-72e887ab5aab', + 'id': '47f2c50d-17b4-4892-97f0-f3f81abb5c36', + 'start_timers': [ 'f94f42ff-f836-4ba9-97f9-da3aa24941d6'], + 'to': '9c585f61-e1b1-4e53-9fb4-5d60e141d51f', + 'transition_costs': 347.97008821629373, + 'transition_duration': 39801}]} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc_actuator_description_test.py b/tests/unit/frbc_actuator_description_test.py new file mode 100644 index 0000000..d8881b4 --- /dev/null +++ b/tests/unit/frbc_actuator_description_test.py @@ -0,0 +1,118 @@ + + + from s2python.frbc import * + from s2python. + + class FRBCActuatorDescriptionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ + { + "id": "f87e77fa-b984-4f9e-a1bf-a4782b600508", + "diagnostic_label": "some-test-string7553", + "supported_commodities": [ + "GAS" + ], + "operation_modes": [ + { + "id": "93a450ba-c94c-46e4-8bae-b768c5867075", + "diagnostic_label": "some-test-string64", + "elements": [ + { + "fill_level_range": { + "start_of_range": 6056.10613410257, + "end_of_range": 37655.606469064296 + }, + "fill_rate": { + "start_of_range": 24637.49716859664, + "end_of_range": 29714.5724875526 + }, + "power_ranges": [ + { + "start_of_range": 25430.49420136962, + "end_of_range": 65352.058496320744, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 5704.446007733921, + "end_of_range": 20299.76272470042 + } + } + ], + "abnormal_condition_only": false + } + ], + "transitions": [ + { + "id": "e5a850b3-cfa9-49c6-aec7-3658d59fdadc", + "from_": "5df9e140-7486-4cbe-b6cb-950e9010ef27", + "to": "0246915c-bcd1-4d6e-90bd-e97dbd2de803", + "start_timers": [ + "18c556e0-9a56-4aa3-9535-acc2440371ff" + ], + "blocking_timers": [ + "e0269765-8eb8-46b6-8ef1-4fa7df853b0c" + ], + "transition_costs": 5373.494383957436, + "transition_duration": 31883, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "89ff3d76-6e22-4436-a885-b7e868b5b246", + "diagnostic_label": "some-test-string6221", + "duration": 33492 + } + ] +} + """ + + # Act + frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) + + # Assert + self.assertEqual(frbc_actuator_description.id, uuid.UUID("f87e77fa-b984-4f9e-a1bf-a4782b600508")) + self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string7553") + self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) + self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("93a450ba-c94c-46e4-8bae-b768c5867075"), diagnostic_label="some-test-string64", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=6056.10613410257, end_of_range=37655.606469064296), fill_rate=NumberRange(start_of_range=24637.49716859664, end_of_range=29714.5724875526), power_ranges=[PowerRange(start_of_range=25430.49420136962, end_of_range=65352.058496320744, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=5704.446007733921, end_of_range=20299.76272470042))], abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("e5a850b3-cfa9-49c6-aec7-3658d59fdadc"), from_=uuid.UUID("5df9e140-7486-4cbe-b6cb-950e9010ef27"), to=uuid.UUID("0246915c-bcd1-4d6e-90bd-e97dbd2de803"), start_timers=[uuid.UUID("18c556e0-9a56-4aa3-9535-acc2440371ff")], blocking_timers=[uuid.UUID("e0269765-8eb8-46b6-8ef1-4fa7df853b0c")], transition_costs=5373.494383957436, transition_duration=Duration.from_timedelta(timedelta(milliseconds=31883)), abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("89ff3d76-6e22-4436-a885-b7e868b5b246"), diagnostic_label="some-test-string6221", duration=Duration.from_timedelta(timedelta(milliseconds=33492)))]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("f87e77fa-b984-4f9e-a1bf-a4782b600508"), diagnostic_label="some-test-string7553", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("93a450ba-c94c-46e4-8bae-b768c5867075"), diagnostic_label="some-test-string64", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=6056.10613410257, end_of_range=37655.606469064296), fill_rate=NumberRange(start_of_range=24637.49716859664, end_of_range=29714.5724875526), power_ranges=[PowerRange(start_of_range=25430.49420136962, end_of_range=65352.058496320744, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=5704.446007733921, end_of_range=20299.76272470042))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("e5a850b3-cfa9-49c6-aec7-3658d59fdadc"), from_=uuid.UUID("5df9e140-7486-4cbe-b6cb-950e9010ef27"), to=uuid.UUID("0246915c-bcd1-4d6e-90bd-e97dbd2de803"), start_timers=[uuid.UUID("18c556e0-9a56-4aa3-9535-acc2440371ff")], blocking_timers=[uuid.UUID("e0269765-8eb8-46b6-8ef1-4fa7df853b0c")], transition_costs=5373.494383957436, transition_duration=Duration.from_timedelta(timedelta(milliseconds=31883)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("89ff3d76-6e22-4436-a885-b7e868b5b246"), diagnostic_label="some-test-string6221", duration=Duration.from_timedelta(timedelta(milliseconds=33492)))]) + + # Act + json_str = frbc_actuator_description.to_json() + + # Assert + expected_json = { 'diagnostic_label': 'some-test-string7553', + 'id': 'f87e77fa-b984-4f9e-a1bf-a4782b600508', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string64', + 'elements': [ { 'fill_level_range': { 'end_of_range': 37655.606469064296, + 'start_of_range': 6056.10613410257}, + 'fill_rate': { 'end_of_range': 29714.5724875526, + 'start_of_range': 24637.49716859664}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 65352.058496320744, + 'start_of_range': 25430.49420136962}], + 'running_costs': { 'end_of_range': 20299.76272470042, + 'start_of_range': 5704.446007733921}}], + 'id': '93a450ba-c94c-46e4-8bae-b768c5867075'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string6221', + 'duration': 33492, + 'id': '89ff3d76-6e22-4436-a885-b7e868b5b246'}], + 'transitions': [ { 'abnormal_condition_only': False, + 'blocking_timers': [ 'e0269765-8eb8-46b6-8ef1-4fa7df853b0c'], + 'from_': '5df9e140-7486-4cbe-b6cb-950e9010ef27', + 'id': 'e5a850b3-cfa9-49c6-aec7-3658d59fdadc', + 'start_timers': [ '18c556e0-9a56-4aa3-9535-acc2440371ff'], + 'to': '0246915c-bcd1-4d6e-90bd-e97dbd2de803', + 'transition_costs': 5373.494383957436, + 'transition_duration': 31883}]} + self.assertEqual(json.loads(json_str), expected_json) + \ No newline at end of file From e33a4990ffd57c69d6282d1447ed2648ff0fbfc5 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 14 Dec 2024 00:16:10 +0100 Subject: [PATCH 04/27] PPBC message types --- examples/example_frbc_rm.py | 2 +- src/s2python/ppbc/__init__.py | 1 + .../ppbc/ppbc_end_interruption_instruction.py | 33 +++++ .../ppbc/ppbc_power_profile_definition.py | 26 ++++ .../ppbc/ppbc_power_profile_status.py | 27 ++++ src/s2python/ppbc/ppbc_power_sequence.py | 32 +++++ .../ppbc/ppbc_power_sequence_container.py | 27 ++++ .../ppbc_power_sequence_container_status.py | 31 +++++ .../ppbc/ppbc_power_sequence_element.py | 28 ++++ .../ppbc_start_interruption_instruction.py | 33 +++++ .../frbc/frbc_actuator_description_test.py | 122 ------------------ tests/unit/frbc_actuator_description_test.py | 118 ----------------- 12 files changed, 239 insertions(+), 241 deletions(-) delete mode 100644 tests/unit/frbc/frbc_actuator_description_test.py delete mode 100644 tests/unit/frbc_actuator_description_test.py diff --git a/examples/example_frbc_rm.py b/examples/example_frbc_rm.py index aea2337..8f77b82 100644 --- a/examples/example_frbc_rm.py +++ b/examples/example_frbc_rm.py @@ -146,7 +146,7 @@ def deactivate(self, conn: S2Connection) -> None: s2_conn = S2Connection( - url="ws://localhost:8001/backend/rm/s2python-frbc/cem/dummy_model/ws", + url="ws://localhost:8080/backend/rm/s2python-frbc/cem/dummy_model/ws", role=EnergyManagementRole.RM, control_types=[MyFRBCControlType(), MyNoControlControlType()], asset_details=AssetDetails( diff --git a/src/s2python/ppbc/__init__.py b/src/s2python/ppbc/__init__.py index 28e4e0e..8b3b676 100644 --- a/src/s2python/ppbc/__init__.py +++ b/src/s2python/ppbc/__init__.py @@ -1 +1,2 @@ from s2python.ppbc.ppbc_schedule_instruction import PPBCScheduleInstruction +from s2python.ppbc.ppbc_end_interruption_instruction import PPBCEndInterruptionInstruction diff --git a/src/s2python/ppbc/ppbc_end_interruption_instruction.py b/src/s2python/ppbc/ppbc_end_interruption_instruction.py index e69de29..1dfdee8 100644 --- a/src/s2python/ppbc/ppbc_end_interruption_instruction.py +++ b/src/s2python/ppbc/ppbc_end_interruption_instruction.py @@ -0,0 +1,33 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCEndInterruptionInstruction as GenPPBCEndInterruptionInstruction, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class PPBCEndInterruptionInstruction( + GenPPBCEndInterruptionInstruction, S2Message["PPBCEndInterruptionInstruction"] +): + model_config = GenPPBCEndInterruptionInstruction.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields["id"] + power_profile_id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields[ + "power_profile_id" + ] + sequence_container_id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields[ + "sequence_container_id" + ] + power_sequence_id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields[ + "power_sequence_id" + ] + abnormal_condition: bool = GenPPBCEndInterruptionInstruction.model_fields[ + "abnormal_condition" + ] diff --git a/src/s2python/ppbc/ppbc_power_profile_definition.py b/src/s2python/ppbc/ppbc_power_profile_definition.py index e69de29..9e4c87a 100644 --- a/src/s2python/ppbc/ppbc_power_profile_definition.py +++ b/src/s2python/ppbc/ppbc_power_profile_definition.py @@ -0,0 +1,26 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCPowerProfileDefinition as GenPPBCPowerProfileDefinition, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + +from s2python.ppbc.ppbc_power_sequence_container import PPBCPowerSequenceContainer + + +@catch_and_convert_exceptions +class PPBCPowerProfileDefinition( + GenPPBCPowerProfileDefinition, S2Message["PPBCPowerProfileDefinition"] +): + model_config = GenPPBCPowerProfileDefinition.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["id"] + power_sequences_containers: List[PPBCPowerSequenceContainer] = ( + GenPPBCPowerProfileDefinition.model_fields["power_sequences_containers"] + ) diff --git a/src/s2python/ppbc/ppbc_power_profile_status.py b/src/s2python/ppbc/ppbc_power_profile_status.py index e69de29..7dd5dca 100644 --- a/src/s2python/ppbc/ppbc_power_profile_status.py +++ b/src/s2python/ppbc/ppbc_power_profile_status.py @@ -0,0 +1,27 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCPowerProfileStatus as GenPPBCPowerProfileStatus, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + +from s2python.ppbc.ppbc_power_sequence_container_status import ( + PPBCPowerSequenceContainerStatus, +) + + +@catch_and_convert_exceptions +class PPBCPowerProfileStatus( + GenPPBCPowerProfileStatus, S2Message["PPBCPowerProfileStatus"] +): + model_config = GenPPBCPowerProfileStatus.model_config + model_config["validate_assignment"] = True + + sequence_container_status: List[PPBCPowerSequenceContainerStatus] = ( + GenPPBCPowerProfileStatus.model_fields["sequence_container_status"] + ) diff --git a/src/s2python/ppbc/ppbc_power_sequence.py b/src/s2python/ppbc/ppbc_power_sequence.py index e69de29..152170d 100644 --- a/src/s2python/ppbc/ppbc_power_sequence.py +++ b/src/s2python/ppbc/ppbc_power_sequence.py @@ -0,0 +1,32 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCPowerSequence as GenPPBCPowerSequence, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + +from s2python.ppbc.ppbc_power_sequence_element import PPBCPowerSequenceElement +from s2python.common import Duration + + +@catch_and_convert_exceptions +class PPBCPowerSequenceContainer( + GenPPBCPowerSequence, S2Message["PPBCPowerSequenceContainer"] +): + model_config = GenPPBCPowerSequence.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCPowerSequence.model_fields["id"] + elements: List[PPBCPowerSequenceElement] = GenPPBCPowerSequence.model_fields[ + "elements" + ] + is_interruptible: bool = GenPPBCPowerSequence.model_fields["is_interruptible"] + max_pause_before: Duration = GenPPBCPowerSequence.model_fields["max_pause_before"] + abnormal_condition_only: bool = GenPPBCPowerSequence.model_fields[ + "abnormal_condition_only" + ] diff --git a/src/s2python/ppbc/ppbc_power_sequence_container.py b/src/s2python/ppbc/ppbc_power_sequence_container.py index e69de29..44cb4d8 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container.py @@ -0,0 +1,27 @@ +from typing import List +import uuid + + +from s2python.generated.gen_s2 import ( + PPBCPowerSequenceContainer as GenPPBCPowerSequenceContainer, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + +from s2python.ppbc.ppbc_power_sequence import PPBCPowerSequence + + +@catch_and_convert_exceptions +class PPBCPowerSequenceContainer( + GenPPBCPowerSequenceContainer, S2Message["PPBCPowerSequenceContainer"] +): + model_config = GenPPBCPowerSequenceContainer.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCPowerSequenceContainer.model_fields["id"] + power_sequences: List[PPBCPowerSequence] = ( + GenPPBCPowerSequenceContainer.model_fields["power_sequences"] + ) diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index e69de29..bc213ff 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -0,0 +1,31 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCPowerSequenceContainerStatus as GenPPBCPowerSequenceContainerStatus, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) +from s2python.common import Duration + + +@catch_and_convert_exceptions +class PPBCPowerProfileDefinitionStatus( + GenPPBCPowerSequenceContainerStatus, S2Message["PPBCPowerProfileDefinitionStatus"] +): + model_config = GenPPBCPowerSequenceContainerStatus.model_config + model_config["validate_assignment"] = True + + power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields["id"] + sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ + "sequence_container_id" + ] + selected_sequence_id: uuid.UUID | None = ( + GenPPBCPowerSequenceContainerStatus.model_fields["selected_sequence_id"] + ) + progress: Duration | None = GenPPBCPowerSequenceContainerStatus.model_fields[ + "progress" + ] diff --git a/src/s2python/ppbc/ppbc_power_sequence_element.py b/src/s2python/ppbc/ppbc_power_sequence_element.py index e69de29..7763510 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_element.py +++ b/src/s2python/ppbc/ppbc_power_sequence_element.py @@ -0,0 +1,28 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCPowerSequenceElement as GenPPBCPowerSequenceElement, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + +from s2python.common import Duration, PowerForecastValue + + +@catch_and_convert_exceptions +class PPBCPowerSequenceElement( + GenPPBCPowerSequenceElement, S2Message["PPBCPowerSequenceElement"] +): + model_config = GenPPBCPowerSequenceElement.model_config + model_config["validate_assignment"] = True + + duration: Duration = GenPPBCPowerSequenceElement.model_fields["duration"] + power_values: List[PowerForecastValue] = GenPPBCPowerSequenceElement.model_fields[ + "power_values" + ] + + \ No newline at end of file diff --git a/src/s2python/ppbc/ppbc_start_interruption_instruction.py b/src/s2python/ppbc/ppbc_start_interruption_instruction.py index e69de29..6629daa 100644 --- a/src/s2python/ppbc/ppbc_start_interruption_instruction.py +++ b/src/s2python/ppbc/ppbc_start_interruption_instruction.py @@ -0,0 +1,33 @@ +from typing import List +import uuid + +from s2python.generated.gen_s2 import ( + PPBCStartInterruptionInstruction as GenPPBCStartInterruptionInstruction, +) + +from s2python.validate_values_mixin import ( + S2Message, + catch_and_convert_exceptions, +) + + +@catch_and_convert_exceptions +class PPBCStartInterruptionInstruction( + GenPPBCStartInterruptionInstruction, S2Message["PPBCStartInterruptionInstruction"] +): + model_config = GenPPBCStartInterruptionInstruction.model_config + model_config["validate_assignment"] = True + + id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields["id"] + power_profile_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ + "power_profile_id" + ] + sequence_container_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ + "sequence_container_id" + ] + power_sequence_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ + "power_sequence_id" + ] + abnormal_condition: bool = GenPPBCStartInterruptionInstruction.model_fields[ + "abnormal_condition" + ] diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py deleted file mode 100644 index 7dafd8e..0000000 --- a/tests/unit/frbc/frbc_actuator_description_test.py +++ /dev/null @@ -1,122 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCActuatorDescriptionTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "id": "a58e9c5e-511b-436c-9c65-ad44c778c4b8", - "diagnostic_label": "some-test-string3006", - "supported_commodities": [ - "GAS" - ], - "operation_modes": [ - { - "id": "5d104a14-491f-4138-a757-ff0cb6fd5525", - "diagnostic_label": "some-test-string7637", - "elements": [ - { - "fill_level_range": { - "start_of_range": 24714.62330770477, - "end_of_range": 49561.87219293591 - }, - "fill_rate": { - "start_of_range": 22860.909596605932, - "end_of_range": 50712.187596827665 - }, - "power_ranges": [ - { - "start_of_range": 31917.547195435036, - "end_of_range": 56269.308985594, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 1119.8343828050738, - "end_of_range": 24787.439438905614 - } - } - ], - "abnormal_condition_only": false - } - ], - "transitions": [ - { - "id": "47f2c50d-17b4-4892-97f0-f3f81abb5c36", - "from_": "8b235e80-5c21-452f-8c58-72e887ab5aab", - "to": "9c585f61-e1b1-4e53-9fb4-5d60e141d51f", - "start_timers": [ - "f94f42ff-f836-4ba9-97f9-da3aa24941d6" - ], - "blocking_timers": [ - "ce322294-db0f-4351-a971-8c175c52714f" - ], - "transition_costs": 347.97008821629373, - "transition_duration": 39801, - "abnormal_condition_only": false - } - ], - "timers": [ - { - "id": "73933f13-7c3a-4f76-9975-99acf562498e", - "diagnostic_label": "some-test-string3849", - "duration": 23667 - } - ] -} - """ - - # Act - frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) - - # Assert - self.assertEqual(frbc_actuator_description.id, uuid.UUID("a58e9c5e-511b-436c-9c65-ad44c778c4b8")) - self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string3006") - self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) - self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("5d104a14-491f-4138-a757-ff0cb6fd5525"), diagnostic_label="some-test-string7637", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=24714.62330770477, end_of_range=49561.87219293591), fill_rate=NumberRange(start_of_range=22860.909596605932, end_of_range=50712.187596827665), power_ranges=[PowerRange(start_of_range=31917.547195435036, end_of_range=56269.308985594, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=1119.8343828050738, end_of_range=24787.439438905614))], abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("47f2c50d-17b4-4892-97f0-f3f81abb5c36"), from_=uuid.UUID("8b235e80-5c21-452f-8c58-72e887ab5aab"), to=uuid.UUID("9c585f61-e1b1-4e53-9fb4-5d60e141d51f"), start_timers=[uuid.UUID("f94f42ff-f836-4ba9-97f9-da3aa24941d6")], blocking_timers=[uuid.UUID("ce322294-db0f-4351-a971-8c175c52714f")], transition_costs=347.97008821629373, transition_duration=Duration.from_timedelta(timedelta(milliseconds=39801)), abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("73933f13-7c3a-4f76-9975-99acf562498e"), diagnostic_label="some-test-string3849", duration=Duration.from_timedelta(timedelta(milliseconds=23667)))]) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("a58e9c5e-511b-436c-9c65-ad44c778c4b8"), diagnostic_label="some-test-string3006", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("5d104a14-491f-4138-a757-ff0cb6fd5525"), diagnostic_label="some-test-string7637", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=24714.62330770477, end_of_range=49561.87219293591), fill_rate=NumberRange(start_of_range=22860.909596605932, end_of_range=50712.187596827665), power_ranges=[PowerRange(start_of_range=31917.547195435036, end_of_range=56269.308985594, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=1119.8343828050738, end_of_range=24787.439438905614))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("47f2c50d-17b4-4892-97f0-f3f81abb5c36"), from_=uuid.UUID("8b235e80-5c21-452f-8c58-72e887ab5aab"), to=uuid.UUID("9c585f61-e1b1-4e53-9fb4-5d60e141d51f"), start_timers=[uuid.UUID("f94f42ff-f836-4ba9-97f9-da3aa24941d6")], blocking_timers=[uuid.UUID("ce322294-db0f-4351-a971-8c175c52714f")], transition_costs=347.97008821629373, transition_duration=Duration.from_timedelta(timedelta(milliseconds=39801)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("73933f13-7c3a-4f76-9975-99acf562498e"), diagnostic_label="some-test-string3849", duration=Duration.from_timedelta(timedelta(milliseconds=23667)))]) - - # Act - json_str = frbc_actuator_description.to_json() - - # Assert - expected_json = { 'diagnostic_label': 'some-test-string3006', - 'id': 'a58e9c5e-511b-436c-9c65-ad44c778c4b8', - 'operation_modes': [ { 'abnormal_condition_only': False, - 'diagnostic_label': 'some-test-string7637', - 'elements': [ { 'fill_level_range': { 'end_of_range': 49561.87219293591, - 'start_of_range': 24714.62330770477}, - 'fill_rate': { 'end_of_range': 50712.187596827665, - 'start_of_range': 22860.909596605932}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 56269.308985594, - 'start_of_range': 31917.547195435036}], - 'running_costs': { 'end_of_range': 24787.439438905614, - 'start_of_range': 1119.8343828050738}}], - 'id': '5d104a14-491f-4138-a757-ff0cb6fd5525'}], - 'supported_commodities': ['GAS'], - 'timers': [ { 'diagnostic_label': 'some-test-string3849', - 'duration': 23667, - 'id': '73933f13-7c3a-4f76-9975-99acf562498e'}], - 'transitions': [ { 'abnormal_condition_only': False, - 'blocking_timers': [ 'ce322294-db0f-4351-a971-8c175c52714f'], - 'from_': '8b235e80-5c21-452f-8c58-72e887ab5aab', - 'id': '47f2c50d-17b4-4892-97f0-f3f81abb5c36', - 'start_timers': [ 'f94f42ff-f836-4ba9-97f9-da3aa24941d6'], - 'to': '9c585f61-e1b1-4e53-9fb4-5d60e141d51f', - 'transition_costs': 347.97008821629373, - 'transition_duration': 39801}]} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc_actuator_description_test.py b/tests/unit/frbc_actuator_description_test.py deleted file mode 100644 index d8881b4..0000000 --- a/tests/unit/frbc_actuator_description_test.py +++ /dev/null @@ -1,118 +0,0 @@ - - - from s2python.frbc import * - from s2python. - - class FRBCActuatorDescriptionTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ - { - "id": "f87e77fa-b984-4f9e-a1bf-a4782b600508", - "diagnostic_label": "some-test-string7553", - "supported_commodities": [ - "GAS" - ], - "operation_modes": [ - { - "id": "93a450ba-c94c-46e4-8bae-b768c5867075", - "diagnostic_label": "some-test-string64", - "elements": [ - { - "fill_level_range": { - "start_of_range": 6056.10613410257, - "end_of_range": 37655.606469064296 - }, - "fill_rate": { - "start_of_range": 24637.49716859664, - "end_of_range": 29714.5724875526 - }, - "power_ranges": [ - { - "start_of_range": 25430.49420136962, - "end_of_range": 65352.058496320744, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 5704.446007733921, - "end_of_range": 20299.76272470042 - } - } - ], - "abnormal_condition_only": false - } - ], - "transitions": [ - { - "id": "e5a850b3-cfa9-49c6-aec7-3658d59fdadc", - "from_": "5df9e140-7486-4cbe-b6cb-950e9010ef27", - "to": "0246915c-bcd1-4d6e-90bd-e97dbd2de803", - "start_timers": [ - "18c556e0-9a56-4aa3-9535-acc2440371ff" - ], - "blocking_timers": [ - "e0269765-8eb8-46b6-8ef1-4fa7df853b0c" - ], - "transition_costs": 5373.494383957436, - "transition_duration": 31883, - "abnormal_condition_only": false - } - ], - "timers": [ - { - "id": "89ff3d76-6e22-4436-a885-b7e868b5b246", - "diagnostic_label": "some-test-string6221", - "duration": 33492 - } - ] -} - """ - - # Act - frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) - - # Assert - self.assertEqual(frbc_actuator_description.id, uuid.UUID("f87e77fa-b984-4f9e-a1bf-a4782b600508")) - self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string7553") - self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) - self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("93a450ba-c94c-46e4-8bae-b768c5867075"), diagnostic_label="some-test-string64", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=6056.10613410257, end_of_range=37655.606469064296), fill_rate=NumberRange(start_of_range=24637.49716859664, end_of_range=29714.5724875526), power_ranges=[PowerRange(start_of_range=25430.49420136962, end_of_range=65352.058496320744, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=5704.446007733921, end_of_range=20299.76272470042))], abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("e5a850b3-cfa9-49c6-aec7-3658d59fdadc"), from_=uuid.UUID("5df9e140-7486-4cbe-b6cb-950e9010ef27"), to=uuid.UUID("0246915c-bcd1-4d6e-90bd-e97dbd2de803"), start_timers=[uuid.UUID("18c556e0-9a56-4aa3-9535-acc2440371ff")], blocking_timers=[uuid.UUID("e0269765-8eb8-46b6-8ef1-4fa7df853b0c")], transition_costs=5373.494383957436, transition_duration=Duration.from_timedelta(timedelta(milliseconds=31883)), abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("89ff3d76-6e22-4436-a885-b7e868b5b246"), diagnostic_label="some-test-string6221", duration=Duration.from_timedelta(timedelta(milliseconds=33492)))]) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("f87e77fa-b984-4f9e-a1bf-a4782b600508"), diagnostic_label="some-test-string7553", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("93a450ba-c94c-46e4-8bae-b768c5867075"), diagnostic_label="some-test-string64", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=6056.10613410257, end_of_range=37655.606469064296), fill_rate=NumberRange(start_of_range=24637.49716859664, end_of_range=29714.5724875526), power_ranges=[PowerRange(start_of_range=25430.49420136962, end_of_range=65352.058496320744, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=5704.446007733921, end_of_range=20299.76272470042))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("e5a850b3-cfa9-49c6-aec7-3658d59fdadc"), from_=uuid.UUID("5df9e140-7486-4cbe-b6cb-950e9010ef27"), to=uuid.UUID("0246915c-bcd1-4d6e-90bd-e97dbd2de803"), start_timers=[uuid.UUID("18c556e0-9a56-4aa3-9535-acc2440371ff")], blocking_timers=[uuid.UUID("e0269765-8eb8-46b6-8ef1-4fa7df853b0c")], transition_costs=5373.494383957436, transition_duration=Duration.from_timedelta(timedelta(milliseconds=31883)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("89ff3d76-6e22-4436-a885-b7e868b5b246"), diagnostic_label="some-test-string6221", duration=Duration.from_timedelta(timedelta(milliseconds=33492)))]) - - # Act - json_str = frbc_actuator_description.to_json() - - # Assert - expected_json = { 'diagnostic_label': 'some-test-string7553', - 'id': 'f87e77fa-b984-4f9e-a1bf-a4782b600508', - 'operation_modes': [ { 'abnormal_condition_only': False, - 'diagnostic_label': 'some-test-string64', - 'elements': [ { 'fill_level_range': { 'end_of_range': 37655.606469064296, - 'start_of_range': 6056.10613410257}, - 'fill_rate': { 'end_of_range': 29714.5724875526, - 'start_of_range': 24637.49716859664}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 65352.058496320744, - 'start_of_range': 25430.49420136962}], - 'running_costs': { 'end_of_range': 20299.76272470042, - 'start_of_range': 5704.446007733921}}], - 'id': '93a450ba-c94c-46e4-8bae-b768c5867075'}], - 'supported_commodities': ['GAS'], - 'timers': [ { 'diagnostic_label': 'some-test-string6221', - 'duration': 33492, - 'id': '89ff3d76-6e22-4436-a885-b7e868b5b246'}], - 'transitions': [ { 'abnormal_condition_only': False, - 'blocking_timers': [ 'e0269765-8eb8-46b6-8ef1-4fa7df853b0c'], - 'from_': '5df9e140-7486-4cbe-b6cb-950e9010ef27', - 'id': 'e5a850b3-cfa9-49c6-aec7-3658d59fdadc', - 'start_timers': [ '18c556e0-9a56-4aa3-9535-acc2440371ff'], - 'to': '0246915c-bcd1-4d6e-90bd-e97dbd2de803', - 'transition_costs': 5373.494383957436, - 'transition_duration': 31883}]} - self.assertEqual(json.loads(json_str), expected_json) - \ No newline at end of file From 161a16b3675cc294b551dcdc6277136b3d7d7567 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Thu, 19 Dec 2024 00:13:42 +0200 Subject: [PATCH 05/27] PPBC fixed issues with wrong class names and the gen_unit tests --- .../gen_unit_test_template.py | 7 + src/s2python/generated/gen_s2.py | 1012 ++++++++--------- src/s2python/ppbc/__init__.py | 12 +- .../ppbc/ppbc_power_profile_definition.py | 1 + src/s2python/ppbc/ppbc_power_sequence.py | 4 +- .../ppbc_power_sequence_container_status.py | 6 +- .../ppbc/ppbc_power_sequence_element.py | 2 - .../frbc/frbc_actuator_description_test.py | 122 ++ tests/unit/frbc/frbc_actuator_status_test.py | 84 +- ..._fill_level_target_profile_element_test.py | 45 +- .../frbc_fill_level_target_profile_test.py | 91 +- tests/unit/frbc/frbc_instruction_test.py | 88 +- .../frbc_leakage_behaviour_element_test.py | 42 +- .../unit/frbc/frbc_leakage_behaviour_test.py | 91 +- .../frbc/frbc_operation_mode_element_test.py | 105 +- tests/unit/frbc/frbc_operation_mode_test.py | 125 +- .../frbc/frbc_storage_description_test.py | 67 +- tests/unit/frbc/frbc_storage_status_test.py | 28 +- .../unit/frbc/frbc_system_description_test.py | 380 ++----- tests/unit/frbc/frbc_timer_status_test.py | 67 +- .../frbc/frbc_usage_forecast_element_test.py | 79 +- tests/unit/frbc/frbc_usage_forecast_test.py | 115 +- 22 files changed, 1007 insertions(+), 1566 deletions(-) create mode 100644 tests/unit/frbc/frbc_actuator_description_test.py diff --git a/development_utilities/gen_unit_test_template.py b/development_utilities/gen_unit_test_template.py index d246d88..4e45260 100644 --- a/development_utilities/gen_unit_test_template.py +++ b/development_utilities/gen_unit_test_template.py @@ -13,7 +13,9 @@ TypeVar, Callable, Sequence, + Literal, ) +from typing_extensions import _LiteralGenericAlias import uuid import pydantic @@ -190,7 +192,10 @@ def dump_test_data_as_constructor_field_for(test_data, field_type: Type) -> str: ) elif field_type is uuid.UUID: value = f'uuid.UUID("{test_data}")' + elif type(field_type).__name__ == "_LiteralGenericAlias": + value = field_type.__args__[0] else: + breakpoint() raise RuntimeError( f"Please implement dump test data for field type {field_type}" ) @@ -239,6 +244,8 @@ def dump_test_data_as_json_field_for(test_data, field_type: Type): value = test_data.isoformat() elif field_type is uuid.UUID: value = str(test_data) + elif type(field_type).__name__ == "_LiteralGenericAlias": + value = test_data else: raise RuntimeError( f"Please implement dump test data to json for field type {field_type}" diff --git a/src/s2python/generated/gen_s2.py b/src/s2python/generated/gen_s2.py index c7febd6..f665886 100644 --- a/src/s2python/generated/gen_s2.py +++ b/src/s2python/generated/gen_s2.py @@ -20,805 +20,805 @@ class Duration(RootModel[conint(ge=0)]): - root: conint(ge=0) = Field(..., description='Duration in milliseconds') + root: conint(ge=0) = Field(..., description="Duration in milliseconds") -class ID(RootModel[constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}')]): - root: constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') +class ID(RootModel[constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}")]): + root: constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}") = Field(..., description="UUID") class Currency(Enum): - AED = 'AED' - ANG = 'ANG' - AUD = 'AUD' - CHE = 'CHE' - CHF = 'CHF' - CHW = 'CHW' - EUR = 'EUR' - GBP = 'GBP' - LBP = 'LBP' - LKR = 'LKR' - LRD = 'LRD' - LSL = 'LSL' - LYD = 'LYD' - MAD = 'MAD' - MDL = 'MDL' - MGA = 'MGA' - MKD = 'MKD' - MMK = 'MMK' - MNT = 'MNT' - MOP = 'MOP' - MRO = 'MRO' - MUR = 'MUR' - MVR = 'MVR' - MWK = 'MWK' - MXN = 'MXN' - MXV = 'MXV' - MYR = 'MYR' - MZN = 'MZN' - NAD = 'NAD' - NGN = 'NGN' - NIO = 'NIO' - NOK = 'NOK' - NPR = 'NPR' - NZD = 'NZD' - OMR = 'OMR' - PAB = 'PAB' - PEN = 'PEN' - PGK = 'PGK' - PHP = 'PHP' - PKR = 'PKR' - PLN = 'PLN' - PYG = 'PYG' - QAR = 'QAR' - RON = 'RON' - RSD = 'RSD' - RUB = 'RUB' - RWF = 'RWF' - SAR = 'SAR' - SBD = 'SBD' - SCR = 'SCR' - SDG = 'SDG' - SEK = 'SEK' - SGD = 'SGD' - SHP = 'SHP' - SLL = 'SLL' - SOS = 'SOS' - SRD = 'SRD' - SSP = 'SSP' - STD = 'STD' - SYP = 'SYP' - SZL = 'SZL' - THB = 'THB' - TJS = 'TJS' - TMT = 'TMT' - TND = 'TND' - TOP = 'TOP' - TRY = 'TRY' - TTD = 'TTD' - TWD = 'TWD' - TZS = 'TZS' - UAH = 'UAH' - UGX = 'UGX' - USD = 'USD' - USN = 'USN' - UYI = 'UYI' - UYU = 'UYU' - UZS = 'UZS' - VEF = 'VEF' - VND = 'VND' - VUV = 'VUV' - WST = 'WST' - XAG = 'XAG' - XAU = 'XAU' - XBA = 'XBA' - XBB = 'XBB' - XBC = 'XBC' - XBD = 'XBD' - XCD = 'XCD' - XOF = 'XOF' - XPD = 'XPD' - XPF = 'XPF' - XPT = 'XPT' - XSU = 'XSU' - XTS = 'XTS' - XUA = 'XUA' - XXX = 'XXX' - YER = 'YER' - ZAR = 'ZAR' - ZMW = 'ZMW' - ZWL = 'ZWL' + AED = "AED" + ANG = "ANG" + AUD = "AUD" + CHE = "CHE" + CHF = "CHF" + CHW = "CHW" + EUR = "EUR" + GBP = "GBP" + LBP = "LBP" + LKR = "LKR" + LRD = "LRD" + LSL = "LSL" + LYD = "LYD" + MAD = "MAD" + MDL = "MDL" + MGA = "MGA" + MKD = "MKD" + MMK = "MMK" + MNT = "MNT" + MOP = "MOP" + MRO = "MRO" + MUR = "MUR" + MVR = "MVR" + MWK = "MWK" + MXN = "MXN" + MXV = "MXV" + MYR = "MYR" + MZN = "MZN" + NAD = "NAD" + NGN = "NGN" + NIO = "NIO" + NOK = "NOK" + NPR = "NPR" + NZD = "NZD" + OMR = "OMR" + PAB = "PAB" + PEN = "PEN" + PGK = "PGK" + PHP = "PHP" + PKR = "PKR" + PLN = "PLN" + PYG = "PYG" + QAR = "QAR" + RON = "RON" + RSD = "RSD" + RUB = "RUB" + RWF = "RWF" + SAR = "SAR" + SBD = "SBD" + SCR = "SCR" + SDG = "SDG" + SEK = "SEK" + SGD = "SGD" + SHP = "SHP" + SLL = "SLL" + SOS = "SOS" + SRD = "SRD" + SSP = "SSP" + STD = "STD" + SYP = "SYP" + SZL = "SZL" + THB = "THB" + TJS = "TJS" + TMT = "TMT" + TND = "TND" + TOP = "TOP" + TRY = "TRY" + TTD = "TTD" + TWD = "TWD" + TZS = "TZS" + UAH = "UAH" + UGX = "UGX" + USD = "USD" + USN = "USN" + UYI = "UYI" + UYU = "UYU" + UZS = "UZS" + VEF = "VEF" + VND = "VND" + VUV = "VUV" + WST = "WST" + XAG = "XAG" + XAU = "XAU" + XBA = "XBA" + XBB = "XBB" + XBC = "XBC" + XBD = "XBD" + XCD = "XCD" + XOF = "XOF" + XPD = "XPD" + XPF = "XPF" + XPT = "XPT" + XSU = "XSU" + XTS = "XTS" + XUA = "XUA" + XXX = "XXX" + YER = "YER" + ZAR = "ZAR" + ZMW = "ZMW" + ZWL = "ZWL" class SessionRequestType(Enum): - RECONNECT = 'RECONNECT' - TERMINATE = 'TERMINATE' + RECONNECT = "RECONNECT" + TERMINATE = "TERMINATE" class RevokableObjects(Enum): - PEBC_PowerConstraints = 'PEBC.PowerConstraints' - PEBC_EnergyConstraint = 'PEBC.EnergyConstraint' - PEBC_Instruction = 'PEBC.Instruction' - PPBC_PowerProfileDefinition = 'PPBC.PowerProfileDefinition' - PPBC_ScheduleInstruction = 'PPBC.ScheduleInstruction' - PPBC_StartInterruptionInstruction = 'PPBC.StartInterruptionInstruction' - PPBC_EndInterruptionInstruction = 'PPBC.EndInterruptionInstruction' - OMBC_SystemDescription = 'OMBC.SystemDescription' - OMBC_Instruction = 'OMBC.Instruction' - FRBC_SystemDescription = 'FRBC.SystemDescription' - FRBC_Instruction = 'FRBC.Instruction' - DDBC_SystemDescription = 'DDBC.SystemDescription' - DDBC_Instruction = 'DDBC.Instruction' + PEBC_PowerConstraints = "PEBC.PowerConstraints" + PEBC_EnergyConstraint = "PEBC.EnergyConstraint" + PEBC_Instruction = "PEBC.Instruction" + PPBC_PowerProfileDefinition = "PPBC.PowerProfileDefinition" + PPBC_ScheduleInstruction = "PPBC.ScheduleInstruction" + PPBC_StartInterruptionInstruction = "PPBC.StartInterruptionInstruction" + PPBC_EndInterruptionInstruction = "PPBC.EndInterruptionInstruction" + OMBC_SystemDescription = "OMBC.SystemDescription" + OMBC_Instruction = "OMBC.Instruction" + FRBC_SystemDescription = "FRBC.SystemDescription" + FRBC_Instruction = "FRBC.Instruction" + DDBC_SystemDescription = "DDBC.SystemDescription" + DDBC_Instruction = "DDBC.Instruction" class EnergyManagementRole(Enum): - CEM = 'CEM' - RM = 'RM' + CEM = "CEM" + RM = "RM" class ReceptionStatusValues(Enum): - INVALID_DATA = 'INVALID_DATA' - INVALID_MESSAGE = 'INVALID_MESSAGE' - INVALID_CONTENT = 'INVALID_CONTENT' - TEMPORARY_ERROR = 'TEMPORARY_ERROR' - PERMANENT_ERROR = 'PERMANENT_ERROR' - OK = 'OK' + INVALID_DATA = "INVALID_DATA" + INVALID_MESSAGE = "INVALID_MESSAGE" + INVALID_CONTENT = "INVALID_CONTENT" + TEMPORARY_ERROR = "TEMPORARY_ERROR" + PERMANENT_ERROR = "PERMANENT_ERROR" + OK = "OK" class NumberRange(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) start_of_range: float = Field( - ..., description='Number that defines the start of the range' + ..., description="Number that defines the start of the range" ) end_of_range: float = Field( - ..., description='Number that defines the end of the range' + ..., description="Number that defines the end of the range" ) class Transition(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', + description="ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.", ) from_: ID = Field( ..., - alias='from', - description='ID of the OperationMode (exact type differs per ControlType) that should be switched from.', + alias="from", + description="ID of the OperationMode (exact type differs per ControlType) that should be switched from.", ) to: ID = Field( ..., - description='ID of the OperationMode (exact type differs per ControlType) that will be switched to.', + description="ID of the OperationMode (exact type differs per ControlType) that will be switched to.", ) start_timers: List[ID] = Field( ..., - description='List of IDs of Timers that will be (re)started when this transition is initiated', + description="List of IDs of Timers that will be (re)started when this transition is initiated", max_length=1000, min_length=0, ) blocking_timers: List[ID] = Field( ..., - description='List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished', + description="List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished", max_length=1000, min_length=0, ) transition_costs: Optional[float] = Field( None, - description='Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.', + description="Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.", ) transition_duration: Optional[Duration] = Field( None, - description='Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.', + description="Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this Transition may only be used during an abnormal condition (see Clause )', + description="Indicates if this Transition may only be used during an abnormal condition (see Clause )", ) class Timer(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', + description="ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.", ) duration: Duration = Field( ..., - description='The time it takes for the Timer to finish after it has been started', + description="The time it takes for the Timer to finish after it has been started", ) class PEBCPowerEnvelopeElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='The duration of the element') + duration: Duration = Field(..., description="The duration of the element") upper_limit: float = Field( ..., - description='Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.', + description="Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.", ) lower_limit: float = Field( ..., - description='Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.', + description="Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.", ) class FRBCStorageDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.", ) fill_level_label: Optional[str] = Field( None, - description='Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.", ) provides_leakage_behaviour: bool = Field( ..., - description='Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.', + description="Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.", ) provides_fill_level_target_profile: bool = Field( ..., - description='Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.', + description="Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.", ) provides_usage_forecast: bool = Field( ..., - description='Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.', + description="Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.", ) fill_level_range: NumberRange = Field( ..., - description='The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ', + description="The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ", ) class FRBCLeakageBehaviourElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) fill_level_range: NumberRange = Field( ..., - description='The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.', + description="The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.", ) leakage_rate: float = Field( ..., - description='Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.', + description="Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.", ) class FRBCUsageForecastElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) duration: Duration = Field( - ..., description='Indicator for how long the given usage_rate is valid.' + ..., description="Indicator for how long the given usage_rate is valid." ) usage_rate_upper_limit: Optional[float] = Field( None, - description='The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_upper_95PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_upper_68PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_expected: float = Field( ..., - description='The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.', + description="The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_lower_68PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_lower_95PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_lower_limit: Optional[float] = Field( None, - description='The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) class FRBCFillLevelTargetProfileElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='The duration of the element.') + duration: Duration = Field(..., description="The duration of the element.") fill_level_range: NumberRange = Field( ..., - description='The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.', + description="The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.", ) class DDBCAverageDemandRateForecastElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='Duration of the element') + duration: Duration = Field(..., description="Duration of the element") demand_rate_upper_limit: Optional[float] = Field( None, - description='The upper limit of the range with a 100 % probability that the demand rate is within that range', + description="The upper limit of the range with a 100 % probability that the demand rate is within that range", ) demand_rate_upper_95PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 95 % probability that the demand rate is within that range', + description="The upper limit of the range with a 95 % probability that the demand rate is within that range", ) demand_rate_upper_68PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 68 % probability that the demand rate is within that range', + description="The upper limit of the range with a 68 % probability that the demand rate is within that range", ) demand_rate_expected: float = Field( ..., - description='The most likely value for the demand rate; the expected increase or decrease of the fill_level per second', + description="The most likely value for the demand rate; the expected increase or decrease of the fill_level per second", ) demand_rate_lower_68PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 68 % probability that the demand rate is within that range', + description="The lower limit of the range with a 68 % probability that the demand rate is within that range", ) demand_rate_lower_95PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 95 % probability that the demand rate is within that range', + description="The lower limit of the range with a 95 % probability that the demand rate is within that range", ) demand_rate_lower_limit: Optional[float] = Field( None, - description='The lower limit of the range with a 100 % probability that the demand rate is within that range', + description="The lower limit of the range with a 100 % probability that the demand rate is within that range", ) class RoleType(Enum): - ENERGY_PRODUCER = 'ENERGY_PRODUCER' - ENERGY_CONSUMER = 'ENERGY_CONSUMER' - ENERGY_STORAGE = 'ENERGY_STORAGE' + ENERGY_PRODUCER = "ENERGY_PRODUCER" + ENERGY_CONSUMER = "ENERGY_CONSUMER" + ENERGY_STORAGE = "ENERGY_STORAGE" class Commodity(Enum): - GAS = 'GAS' - HEAT = 'HEAT' - ELECTRICITY = 'ELECTRICITY' - OIL = 'OIL' + GAS = "GAS" + HEAT = "HEAT" + ELECTRICITY = "ELECTRICITY" + OIL = "OIL" class CommodityQuantity(Enum): - ELECTRIC_POWER_L1 = 'ELECTRIC.POWER.L1' - ELECTRIC_POWER_L2 = 'ELECTRIC.POWER.L2' - ELECTRIC_POWER_L3 = 'ELECTRIC.POWER.L3' - ELECTRIC_POWER_3_PHASE_SYMMETRIC = 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' - NATURAL_GAS_FLOW_RATE = 'NATURAL_GAS.FLOW_RATE' - HYDROGEN_FLOW_RATE = 'HYDROGEN.FLOW_RATE' - HEAT_TEMPERATURE = 'HEAT.TEMPERATURE' - HEAT_FLOW_RATE = 'HEAT.FLOW_RATE' - HEAT_THERMAL_POWER = 'HEAT.THERMAL_POWER' - OIL_FLOW_RATE = 'OIL.FLOW_RATE' + ELECTRIC_POWER_L1 = "ELECTRIC.POWER.L1" + ELECTRIC_POWER_L2 = "ELECTRIC.POWER.L2" + ELECTRIC_POWER_L3 = "ELECTRIC.POWER.L3" + ELECTRIC_POWER_3_PHASE_SYMMETRIC = "ELECTRIC.POWER.3_PHASE_SYMMETRIC" + NATURAL_GAS_FLOW_RATE = "NATURAL_GAS.FLOW_RATE" + HYDROGEN_FLOW_RATE = "HYDROGEN.FLOW_RATE" + HEAT_TEMPERATURE = "HEAT.TEMPERATURE" + HEAT_FLOW_RATE = "HEAT.FLOW_RATE" + HEAT_THERMAL_POWER = "HEAT.THERMAL_POWER" + OIL_FLOW_RATE = "OIL.FLOW_RATE" class InstructionStatus(Enum): - NEW = 'NEW' - ACCEPTED = 'ACCEPTED' - REJECTED = 'REJECTED' - REVOKED = 'REVOKED' - STARTED = 'STARTED' - SUCCEEDED = 'SUCCEEDED' - ABORTED = 'ABORTED' + NEW = "NEW" + ACCEPTED = "ACCEPTED" + REJECTED = "REJECTED" + REVOKED = "REVOKED" + STARTED = "STARTED" + SUCCEEDED = "SUCCEEDED" + ABORTED = "ABORTED" class ControlType(Enum): - POWER_ENVELOPE_BASED_CONTROL = 'POWER_ENVELOPE_BASED_CONTROL' - POWER_PROFILE_BASED_CONTROL = 'POWER_PROFILE_BASED_CONTROL' - OPERATION_MODE_BASED_CONTROL = 'OPERATION_MODE_BASED_CONTROL' - FILL_RATE_BASED_CONTROL = 'FILL_RATE_BASED_CONTROL' - DEMAND_DRIVEN_BASED_CONTROL = 'DEMAND_DRIVEN_BASED_CONTROL' - NOT_CONTROLABLE = 'NOT_CONTROLABLE' - NO_SELECTION = 'NO_SELECTION' + POWER_ENVELOPE_BASED_CONTROL = "POWER_ENVELOPE_BASED_CONTROL" + POWER_PROFILE_BASED_CONTROL = "POWER_PROFILE_BASED_CONTROL" + OPERATION_MODE_BASED_CONTROL = "OPERATION_MODE_BASED_CONTROL" + FILL_RATE_BASED_CONTROL = "FILL_RATE_BASED_CONTROL" + DEMAND_DRIVEN_BASED_CONTROL = "DEMAND_DRIVEN_BASED_CONTROL" + NOT_CONTROLABLE = "NOT_CONTROLABLE" + NO_SELECTION = "NO_SELECTION" class PEBCPowerEnvelopeLimitType(Enum): - UPPER_LIMIT = 'UPPER_LIMIT' - LOWER_LIMIT = 'LOWER_LIMIT' + UPPER_LIMIT = "UPPER_LIMIT" + LOWER_LIMIT = "LOWER_LIMIT" class PEBCPowerEnvelopeConsequenceType(Enum): - VANISH = 'VANISH' - DEFER = 'DEFER' + VANISH = "VANISH" + DEFER = "DEFER" class PPBCPowerSequenceStatus(Enum): - NOT_SCHEDULED = 'NOT_SCHEDULED' - SCHEDULED = 'SCHEDULED' - EXECUTING = 'EXECUTING' - INTERRUPTED = 'INTERRUPTED' - FINISHED = 'FINISHED' - ABORTED = 'ABORTED' + NOT_SCHEDULED = "NOT_SCHEDULED" + SCHEDULED = "SCHEDULED" + EXECUTING = "EXECUTING" + INTERRUPTED = "INTERRUPTED" + FINISHED = "FINISHED" + ABORTED = "ABORTED" class OMBCTimerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.TimerStatus'] = 'OMBC.TimerStatus' + message_type: Literal["OMBC.TimerStatus"] = "OMBC.TimerStatus" message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') + timer_id: ID = Field(..., description="The ID of the timer this message refers to") finished_at: AwareDatetime = Field( ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", ) class FRBCTimerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.TimerStatus'] = 'FRBC.TimerStatus' + message_type: Literal["FRBC.TimerStatus"] = "FRBC.TimerStatus" message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') + timer_id: ID = Field(..., description="The ID of the timer this message refers to") actuator_id: ID = Field( - ..., description='The ID of the actuator the timer belongs to' + ..., description="The ID of the actuator the timer belongs to" ) finished_at: AwareDatetime = Field( ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", ) class DDBCTimerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.TimerStatus'] = 'DDBC.TimerStatus' + message_type: Literal["DDBC.TimerStatus"] = "DDBC.TimerStatus" message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') + timer_id: ID = Field(..., description="The ID of the timer this message refers to") actuator_id: ID = Field( - ..., description='The ID of the actuator the timer belongs to' + ..., description="The ID of the actuator the timer belongs to" ) finished_at: AwareDatetime = Field( ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", ) class SelectControlType(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['SelectControlType'] = 'SelectControlType' + message_type: Literal["SelectControlType"] = "SelectControlType" message_id: ID control_type: ControlType = Field( ..., - description='The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails', + description="The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails", ) class SessionRequest(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['SessionRequest'] = 'SessionRequest' + message_type: Literal["SessionRequest"] = "SessionRequest" message_id: ID - request: SessionRequestType = Field(..., description='The type of request') + request: SessionRequestType = Field(..., description="The type of request") diagnostic_label: Optional[str] = Field( None, - description='Optional field for a human readible descirption for debugging purposes', + description="Optional field for a human readible descirption for debugging purposes", ) class RevokeObject(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['RevokeObject'] = 'RevokeObject' + message_type: Literal["RevokeObject"] = "RevokeObject" message_id: ID object_type: RevokableObjects = Field( - ..., description='The type of object that needs to be revoked' + ..., description="The type of object that needs to be revoked" ) - object_id: ID = Field(..., description='The ID of object that needs to be revoked') + object_id: ID = Field(..., description="The ID of object that needs to be revoked") class Handshake(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['Handshake'] = 'Handshake' + message_type: Literal["Handshake"] = "Handshake" message_id: ID role: EnergyManagementRole = Field( - ..., description='The role of the sender of this message' + ..., description="The role of the sender of this message" ) supported_protocol_versions: Optional[List[str]] = Field( None, - description='Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.', + description="Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.", min_length=1, ) class HandshakeResponse(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['HandshakeResponse'] = 'HandshakeResponse' + message_type: Literal["HandshakeResponse"] = "HandshakeResponse" message_id: ID selected_protocol_version: str = Field( - ..., description='The protocol version the CEM selected for this session' + ..., description="The protocol version the CEM selected for this session" ) class ReceptionStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['ReceptionStatus'] = 'ReceptionStatus' + message_type: Literal["ReceptionStatus"] = "ReceptionStatus" subject_message_id: ID = Field( - ..., description='The message this ReceptionStatus refers to' + ..., description="The message this ReceptionStatus refers to" ) status: ReceptionStatusValues = Field( - ..., description='Enumeration of status values' + ..., description="Enumeration of status values" ) diagnostic_label: Optional[str] = Field( None, - description='Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.', + description="Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.", ) class InstructionStatusUpdate(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['InstructionStatusUpdate'] = 'InstructionStatusUpdate' + message_type: Literal["InstructionStatusUpdate"] = "InstructionStatusUpdate" message_id: ID instruction_id: ID = Field( - ..., description='ID of this instruction (as provided by the CEM) ' + ..., description="ID of this instruction (as provided by the CEM) " ) status_type: InstructionStatus = Field( - ..., description='Present status of this instruction.' + ..., description="Present status of this instruction." ) timestamp: AwareDatetime = Field( - ..., description='Timestamp when status_type has changed the last time.' + ..., description="Timestamp when status_type has changed the last time." ) class PEBCEnergyConstraint(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PEBC.EnergyConstraint'] = 'PEBC.EnergyConstraint' + message_type: Literal["PEBC.EnergyConstraint"] = "PEBC.EnergyConstraint" message_id: ID id: ID = Field( ..., - description='Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) valid_from: AwareDatetime = Field( ..., - description='Moment this PEBC.EnergyConstraints information starts to be valid', + description="Moment this PEBC.EnergyConstraints information starts to be valid", ) valid_until: AwareDatetime = Field( ..., - description='Moment until this PEBC.EnergyConstraints information is valid.', + description="Moment until this PEBC.EnergyConstraints information is valid.", ) upper_average_power: float = Field( ..., - description='Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', + description="Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.", ) lower_average_power: float = Field( ..., - description='Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', + description="Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.", ) commodity_quantity: CommodityQuantity = Field( ..., - description='Type of power quantity which applies to upper_average_power and lower_average_power', + description="Type of power quantity which applies to upper_average_power and lower_average_power", ) class PPBCScheduleInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.ScheduleInstruction'] = 'PPBC.ScheduleInstruction' + message_type: Literal["PPBC.ScheduleInstruction"] = "PPBC.ScheduleInstruction" message_id: ID id: ID = Field( ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', + description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', + description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.", ) power_sequence_id: ID = Field( ..., - description='ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.', + description="ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class PPBCStartInterruptionInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.StartInterruptionInstruction'] = ( - 'PPBC.StartInterruptionInstruction' + message_type: Literal["PPBC.StartInterruptionInstruction"] = ( + "PPBC.StartInterruptionInstruction" ) message_id: ID id: ID = Field( ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.', + description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.', + description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.", ) power_sequence_id: ID = Field( - ..., description='ID of the PPBC.PowerSequence that the CEM wants to interrupt.' + ..., description="ID of the PPBC.PowerSequence that the CEM wants to interrupt." ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class PPBCEndInterruptionInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.EndInterruptionInstruction'] = ( - 'PPBC.EndInterruptionInstruction' + message_type: Literal["PPBC.EndInterruptionInstruction"] = ( + "PPBC.EndInterruptionInstruction" ) message_id: ID id: ID = Field( ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.', + description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.', + description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.", ) power_sequence_id: ID = Field( ..., - description='ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.', + description="ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class OMBCStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.Status'] = 'OMBC.Status' + message_type: Literal["OMBC.Status"] = "OMBC.Status" message_id: ID active_operation_mode_id: ID = Field( - ..., description='ID of the active OMBC.OperationMode.' + ..., description="ID of the active OMBC.OperationMode." ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', + description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.", ) previous_operation_mode_id: Optional[ID] = Field( None, - description='ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', + description="ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.", ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description='Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', + description="Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.", ) class OMBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.Instruction'] = 'OMBC.Instruction' + message_type: Literal["OMBC.Instruction"] = "OMBC.Instruction" message_id: ID id: ID = Field( ..., - description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) operation_mode_id: ID = Field( - ..., description='ID of the OMBC.OperationMode that should be activated' + ..., description="ID of the OMBC.OperationMode that should be activated" ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', + description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class FRBCActuatorStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.ActuatorStatus'] = 'FRBC.ActuatorStatus' + message_type: Literal["FRBC.ActuatorStatus"] = "FRBC.ActuatorStatus" message_id: ID actuator_id: ID = Field( - ..., description='ID of the actuator this messages refers to' + ..., description="ID of the actuator this messages refers to" ) active_operation_mode_id: ID = Field( - ..., description='ID of the FRBC.OperationMode that is presently active.' + ..., description="ID of the FRBC.OperationMode that is presently active." ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.', + description="The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.", ) previous_operation_mode_id: Optional[ID] = Field( None, - description='ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', + description="ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.", ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description='Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', + description="Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.", ) class FRBCStorageStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.StorageStatus'] = 'FRBC.StorageStatus' + message_type: Literal["FRBC.StorageStatus"] = "FRBC.StorageStatus" message_id: ID present_fill_level: float = Field( - ..., description='Present fill level of the Storage' + ..., description="Present fill level of the Storage" ) class FRBCLeakageBehaviour(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.LeakageBehaviour'] = 'FRBC.LeakageBehaviour' + message_type: Literal["FRBC.LeakageBehaviour"] = "FRBC.LeakageBehaviour" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.", ) elements: List[FRBCLeakageBehaviourElement] = Field( ..., - description='List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.', + description="List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.", max_length=288, min_length=1, ) @@ -826,46 +826,46 @@ class FRBCLeakageBehaviour(BaseModel): class FRBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.Instruction'] = 'FRBC.Instruction' + message_type: Literal["FRBC.Instruction"] = "FRBC.Instruction" message_id: ID id: ID = Field( ..., - description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) actuator_id: ID = Field( - ..., description='ID of the actuator this instruction belongs to.' + ..., description="ID of the actuator this instruction belongs to." ) operation_mode: ID = Field( - ..., description='ID of the FRBC.OperationMode that should be activated.' + ..., description="ID of the FRBC.OperationMode that should be activated." ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', + description="The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition.', + description="Indicates if this is an instruction during an abnormal condition.", ) class FRBCUsageForecast(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.UsageForecast'] = 'FRBC.UsageForecast' + message_type: Literal["FRBC.UsageForecast"] = "FRBC.UsageForecast" message_id: ID start_time: AwareDatetime = Field( - ..., description='Time at which the FRBC.UsageForecast starts.' + ..., description="Time at which the FRBC.UsageForecast starts." ) elements: List[FRBCUsageForecastElement] = Field( ..., - description='Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.', + description="Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -873,16 +873,16 @@ class FRBCUsageForecast(BaseModel): class FRBCFillLevelTargetProfile(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.FillLevelTargetProfile'] = 'FRBC.FillLevelTargetProfile' + message_type: Literal["FRBC.FillLevelTargetProfile"] = "FRBC.FillLevelTargetProfile" message_id: ID start_time: AwareDatetime = Field( - ..., description='Time at which the FRBC.FillLevelTargetProfile starts.' + ..., description="Time at which the FRBC.FillLevelTargetProfile starts." ) elements: List[FRBCFillLevelTargetProfileElement] = Field( ..., - description='List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.', + description="List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -890,71 +890,71 @@ class FRBCFillLevelTargetProfile(BaseModel): class DDBCActuatorStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.ActuatorStatus'] = 'DDBC.ActuatorStatus' + message_type: Literal["DDBC.ActuatorStatus"] = "DDBC.ActuatorStatus" message_id: ID actuator_id: ID = Field( - ..., description='ID of the actuator this messages refers to' + ..., description="ID of the actuator this messages refers to" ) active_operation_mode_id: ID = Field( ..., - description='The operation mode that is presently active for this actuator.', + description="The operation mode that is presently active for this actuator.", ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.', + description="The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.", ) previous_operation_mode_id: Optional[ID] = Field( None, - description='ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', + description="ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.", ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description='Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', + description="Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.", ) class DDBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.Instruction'] = 'DDBC.Instruction' + message_type: Literal["DDBC.Instruction"] = "DDBC.Instruction" message_id: ID id: ID = Field( ..., - description='Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) actuator_id: ID = Field( - ..., description='ID of the actuator this Instruction belongs to.' + ..., description="ID of the actuator this Instruction belongs to." ) - operation_mode_id: ID = Field(..., description='ID of the DDBC.OperationMode') + operation_mode_id: ID = Field(..., description="ID of the DDBC.OperationMode") operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', + description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.", ) class DDBCAverageDemandRateForecast(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.AverageDemandRateForecast'] = ( - 'DDBC.AverageDemandRateForecast' + message_type: Literal["DDBC.AverageDemandRateForecast"] = ( + "DDBC.AverageDemandRateForecast" ) message_id: ID - start_time: AwareDatetime = Field(..., description='Start time of the profile.') + start_time: AwareDatetime = Field(..., description="Start time of the profile.") elements: List[DDBCAverageDemandRateForecastElement] = Field( ..., - description='Elements of the profile. Elements must be placed in chronological order.', + description="Elements of the profile. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -962,84 +962,84 @@ class DDBCAverageDemandRateForecast(BaseModel): class PowerValue(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the value refers to' + ..., description="The power quantity the value refers to" ) value: float = Field( ..., - description='Power value expressed in the unit associated with the CommodityQuantity', + description="Power value expressed in the unit associated with the CommodityQuantity", ) class PowerForecastValue(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) value_upper_limit: Optional[float] = Field( None, - description='The upper boundary of the range with 100 % certainty the power value is in it', + description="The upper boundary of the range with 100 % certainty the power value is in it", ) value_upper_95PPR: Optional[float] = Field( None, - description='The upper boundary of the range with 95 % certainty the power value is in it', + description="The upper boundary of the range with 95 % certainty the power value is in it", ) value_upper_68PPR: Optional[float] = Field( None, - description='The upper boundary of the range with 68 % certainty the power value is in it', + description="The upper boundary of the range with 68 % certainty the power value is in it", ) - value_expected: float = Field(..., description='The expected power value.') + value_expected: float = Field(..., description="The expected power value.") value_lower_68PPR: Optional[float] = Field( None, - description='The lower boundary of the range with 68 % certainty the power value is in it', + description="The lower boundary of the range with 68 % certainty the power value is in it", ) value_lower_95PPR: Optional[float] = Field( None, - description='The lower boundary of the range with 95 % certainty the power value is in it', + description="The lower boundary of the range with 95 % certainty the power value is in it", ) value_lower_limit: Optional[float] = Field( None, - description='The lower boundary of the range with 100 % certainty the power value is in it', + description="The lower boundary of the range with 100 % certainty the power value is in it", ) commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the value refers to' + ..., description="The power quantity the value refers to" ) class PowerRange(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) start_of_range: float = Field( - ..., description='Power value that defines the start of the range.' + ..., description="Power value that defines the start of the range." ) end_of_range: float = Field( - ..., description='Power value that defines the end of the range.' + ..., description="Power value that defines the end of the range." ) commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the values refer to' + ..., description="The power quantity the values refer to" ) class Role(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) role: RoleType = Field( - ..., description='Role type of the Resource Manager for the given commodity' + ..., description="Role type of the Resource Manager for the given commodity" ) - commodity: Commodity = Field(..., description='Commodity the role refers to.') + commodity: Commodity = Field(..., description="Commodity the role refers to.") class PowerForecastElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='Duration of the PowerForecastElement') + duration: Duration = Field(..., description="Duration of the PowerForecastElement") power_values: List[PowerForecastValue] = Field( ..., - description='The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.', + description="The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.", max_length=10, min_length=1, ) @@ -1047,39 +1047,39 @@ class PowerForecastElement(BaseModel): class PEBCAllowedLimitRange(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) commodity_quantity: CommodityQuantity = Field( - ..., description='Type of power quantity this PEBC.AllowedLimitRange applies to' + ..., description="Type of power quantity this PEBC.AllowedLimitRange applies to" ) limit_type: PEBCPowerEnvelopeLimitType = Field( ..., - description='Indicates if this ranges applies to the upper limit or the lower limit', + description="Indicates if this ranges applies to the upper limit or the lower limit", ) range_boundary: NumberRange = Field( ..., - description='Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ', + description="Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition', + description="Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition", ) class PEBCPowerEnvelope(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) commodity_quantity: CommodityQuantity = Field( - ..., description='Type of power quantity this PEBC.PowerEnvelope applies to' + ..., description="Type of power quantity this PEBC.PowerEnvelope applies to" ) power_envelope_elements: List[PEBCPowerEnvelopeElement] = Field( ..., - description='The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.', + description="The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -1087,14 +1087,14 @@ class PEBCPowerEnvelope(BaseModel): class PPBCPowerSequenceElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) duration: Duration = Field( - ..., description='Duration of the PPBC.PowerSequenceElement.' + ..., description="Duration of the PPBC.PowerSequenceElement." ) power_values: List[PowerForecastValue] = Field( ..., - description='The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.', + description="The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.", max_length=10, min_length=1, ) @@ -1102,163 +1102,163 @@ class PPBCPowerSequenceElement(BaseModel): class PPBCPowerSequenceContainerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ', + description="ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.', + description="ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.", ) selected_sequence_id: Optional[ID] = Field( None, - description='ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.', + description="ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.", ) progress: Optional[Duration] = Field( None, - description='Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.', + description="Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.", ) status: PPBCPowerSequenceStatus = Field( - ..., description='Status of the selected PPBC.PowerSequence' + ..., description="Status of the selected PPBC.PowerSequence" ) class OMBCOperationMode(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", ) power_ranges: List[PowerRange] = Field( ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", max_length=10, min_length=1, ) running_costs: Optional[NumberRange] = Field( None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this OMBC.OperationMode may only be used during an abnormal condition.', + description="Indicates if this OMBC.OperationMode may only be used during an abnormal condition.", ) class FRBCOperationModeElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) fill_level_range: NumberRange = Field( ..., - description='The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.', + description="The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.", ) fill_rate: NumberRange = Field( ..., - description='Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ', + description="Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ", ) power_ranges: List[PowerRange] = Field( ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", max_length=10, min_length=1, ) running_costs: Optional[NumberRange] = Field( None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", ) class DDBCOperationMode(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) Id: ID = Field( ..., - description='ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.', + description="ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", ) power_ranges: List[PowerRange] = Field( ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", max_length=10, min_length=1, ) supply_range: NumberRange = Field( ..., - description='The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.', + description="The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.", ) running_costs: Optional[NumberRange] = Field( None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this DDBC.OperationMode may only be used during an abnormal condition.', + description="Indicates if this DDBC.OperationMode may only be used during an abnormal condition.", ) class ResourceManagerDetails(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['ResourceManagerDetails'] = 'ResourceManagerDetails' + message_type: Literal["ResourceManagerDetails"] = "ResourceManagerDetails" message_id: ID resource_id: ID = Field( ..., - description='Identifier of the Resource Manager. Must be unique within the scope of the CEM.', + description="Identifier of the Resource Manager. Must be unique within the scope of the CEM.", ) - name: Optional[str] = Field(None, description='Human readable name given by user') + name: Optional[str] = Field(None, description="Human readable name given by user") roles: List[Role] = Field( ..., - description='Each Resource Manager provides one or more energy Roles', + description="Each Resource Manager provides one or more energy Roles", max_length=3, min_length=1, ) - manufacturer: Optional[str] = Field(None, description='Name of Manufacturer') + manufacturer: Optional[str] = Field(None, description="Name of Manufacturer") model: Optional[str] = Field( None, - description='Name of the model of the device (provided by the manufacturer)', + description="Name of the model of the device (provided by the manufacturer)", ) serial_number: Optional[str] = Field( - None, description='Serial number of the device (provided by the manufacturer)' + None, description="Serial number of the device (provided by the manufacturer)" ) firmware_version: Optional[str] = Field( None, - description='Version identifier of the firmware used in the device (provided by the manufacturer)', + description="Version identifier of the firmware used in the device (provided by the manufacturer)", ) instruction_processing_delay: Duration = Field( ..., - description='The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction', + description="The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction", ) available_control_types: List[ControlType] = Field( ..., - description='The control types supported by this Resource Manager.', + description="The control types supported by this Resource Manager.", max_length=5, min_length=1, ) currency: Optional[Currency] = Field( None, - description='Currency to be used for all information regarding costs. Mandatory if cost information is published.', + description="Currency to be used for all information regarding costs. Mandatory if cost information is published.", ) provides_forecast: bool = Field( ..., - description='Indicates whether the ResourceManager is able to provide PowerForecasts', + description="Indicates whether the ResourceManager is able to provide PowerForecasts", ) provides_power_measurement_types: List[CommodityQuantity] = Field( ..., - description='Array of all CommodityQuantities that this Resource Manager can provide measurements for. ', + description="Array of all CommodityQuantities that this Resource Manager can provide measurements for. ", max_length=10, min_length=1, ) @@ -1266,16 +1266,16 @@ class ResourceManagerDetails(BaseModel): class PowerMeasurement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PowerMeasurement'] = 'PowerMeasurement' + message_type: Literal["PowerMeasurement"] = "PowerMeasurement" message_id: ID measurement_timestamp: AwareDatetime = Field( - ..., description='Timestamp when PowerValues were measured.' + ..., description="Timestamp when PowerValues were measured." ) values: List[PowerValue] = Field( ..., - description='Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).', + description="Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).", max_length=10, min_length=1, ) @@ -1283,16 +1283,16 @@ class PowerMeasurement(BaseModel): class PowerForecast(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PowerForecast'] = 'PowerForecast' + message_type: Literal["PowerForecast"] = "PowerForecast" message_id: ID start_time: AwareDatetime = Field( - ..., description='Start time of time period that is covered by the profile.' + ..., description="Start time of time period that is covered by the profile." ) elements: List[PowerForecastElement] = Field( ..., - description='Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.', + description="Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -1300,27 +1300,27 @@ class PowerForecast(BaseModel): class PEBCPowerConstraints(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PEBC.PowerConstraints'] = 'PEBC.PowerConstraints' + message_type: Literal["PEBC.PowerConstraints"] = "PEBC.PowerConstraints" message_id: ID id: ID = Field( ..., - description='Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) valid_from: AwareDatetime = Field( - ..., description='Moment this PEBC.PowerConstraints start to be valid' + ..., description="Moment this PEBC.PowerConstraints start to be valid" ) valid_until: Optional[AwareDatetime] = Field( None, - description='Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.', + description="Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.", ) consequence_type: PEBCPowerEnvelopeConsequenceType = Field( - ..., description='Type of consequence of limiting power' + ..., description="Type of consequence of limiting power" ) allowed_limit_ranges: List[PEBCAllowedLimitRange] = Field( ..., - description='The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.', + description="The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.", max_length=100, min_length=2, ) @@ -1328,29 +1328,29 @@ class PEBCPowerConstraints(BaseModel): class PEBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PEBC.Instruction'] = 'PEBC.Instruction' + message_type: Literal["PEBC.Instruction"] = "PEBC.Instruction" message_id: ID id: ID = Field( ..., - description='Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition.', + description="Indicates if this is an instruction during an abnormal condition.", ) power_constraints_id: ID = Field( ..., - description='Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.', + description="Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.", ) power_envelopes: List[PEBCPowerEnvelope] = Field( ..., - description='The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.', + description="The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.", max_length=10, min_length=1, ) @@ -1358,13 +1358,13 @@ class PEBCInstruction(BaseModel): class PPBCPowerProfileStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.PowerProfileStatus'] = 'PPBC.PowerProfileStatus' + message_type: Literal["PPBC.PowerProfileStatus"] = "PPBC.PowerProfileStatus" message_id: ID sequence_container_status: List[PPBCPowerSequenceContainerStatus] = Field( ..., - description='Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.', + description="Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.", max_length=1000, min_length=1, ) @@ -1372,29 +1372,29 @@ class PPBCPowerProfileStatus(BaseModel): class OMBCSystemDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.SystemDescription'] = 'OMBC.SystemDescription' + message_type: Literal["OMBC.SystemDescription"] = "OMBC.SystemDescription" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", ) operation_modes: List[OMBCOperationMode] = Field( ..., - description='OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.', + description="OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.", max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description='Possible transitions to switch from one OMBC.OperationMode to another.', + description="Possible transitions to switch from one OMBC.OperationMode to another.", max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description='Timers that control when certain transitions can be made.', + description="Timers that control when certain transitions can be made.", max_length=1000, min_length=0, ) @@ -1402,89 +1402,89 @@ class OMBCSystemDescription(BaseModel): class PPBCPowerSequence(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.', + description="ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.", ) elements: List[PPBCPowerSequenceElement] = Field( ..., - description='List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.', + description="List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) is_interruptible: bool = Field( ..., - description='Indicates whether the option of pausing a sequence is available.', + description="Indicates whether the option of pausing a sequence is available.", ) max_pause_before: Optional[Duration] = Field( None, - description='The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one', + description="The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this PPBC.PowerSequence may only be used during an abnormal condition', + description="Indicates if this PPBC.PowerSequence may only be used during an abnormal condition", ) class FRBCOperationMode(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.', + description="ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", ) elements: List[FRBCOperationModeElement] = Field( ..., - description='List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.', + description="List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.", max_length=100, min_length=1, ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this FRBC.OperationMode may only be used during an abnormal condition', + description="Indicates if this FRBC.OperationMode may only be used during an abnormal condition", ) class DDBCActuatorDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.", ) supported_commodites: List[Commodity] = Field( ..., - description='Commodities supported by the operation modes of this actuator. There shall be at least one commodity', + description="Commodities supported by the operation modes of this actuator. There shall be at least one commodity", max_length=4, min_length=1, ) operation_modes: List[DDBCOperationMode] = Field( ..., - description='List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.', + description="List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.", max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description='List of Transitions between Operation Modes. Shall contain at least one Transition.', + description="List of Transitions between Operation Modes. Shall contain at least one Transition.", max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description='List of Timers associated with Transitions for this Actuator. Can be empty.', + description="List of Timers associated with Transitions for this Actuator. Can be empty.", max_length=1000, min_length=0, ) @@ -1492,40 +1492,40 @@ class DDBCActuatorDescription(BaseModel): class DDBCSystemDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.SystemDescription'] = 'DDBC.SystemDescription' + message_type: Literal["DDBC.SystemDescription"] = "DDBC.SystemDescription" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", ) actuators: List[DDBCActuatorDescription] = Field( ..., - description='List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.', + description="List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.", max_length=10, min_length=1, ) present_demand_rate: NumberRange = Field( - ..., description='Present demand rate that needs to be satisfied by the system' + ..., description="Present demand rate that needs to be satisfied by the system" ) provides_average_demand_rate_forecast: bool = Field( ..., - description='Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.', + description="Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.", ) class PPBCPowerSequenceContainer(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.', + description="ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.", ) power_sequences: List[PPBCPowerSequence] = Field( ..., - description='List of alternative Sequences where one could be chosen by the CEM', + description="List of alternative Sequences where one could be chosen by the CEM", max_length=288, min_length=1, ) @@ -1533,37 +1533,37 @@ class PPBCPowerSequenceContainer(BaseModel): class FRBCActuatorDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.", ) supported_commodities: List[Commodity] = Field( ..., - description='List of all supported Commodities.', + description="List of all supported Commodities.", max_length=4, min_length=1, ) operation_modes: List[FRBCOperationMode] = Field( ..., - description='Provided FRBC.OperationModes associated with this actuator', + description="Provided FRBC.OperationModes associated with this actuator", max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description='Possible transitions between FRBC.OperationModes associated with this actuator.', + description="Possible transitions between FRBC.OperationModes associated with this actuator.", max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description='List of Timers associated with this actuator', + description="List of Timers associated with this actuator", max_length=1000, min_length=0, ) @@ -1571,25 +1571,25 @@ class FRBCActuatorDescription(BaseModel): class PPBCPowerProfileDefinition(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.PowerProfileDefinition'] = 'PPBC.PowerProfileDefinition' + message_type: Literal["PPBC.PowerProfileDefinition"] = "PPBC.PowerProfileDefinition" message_id: ID id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) start_time: AwareDatetime = Field( ..., - description='Indicates the first possible time the first PPBC.PowerSequence could start', + description="Indicates the first possible time the first PPBC.PowerSequence could start", ) end_time: AwareDatetime = Field( ..., - description='Indicates when the last PPBC.PowerSequence shall be finished at the latest', + description="Indicates when the last PPBC.PowerSequence shall be finished at the latest", ) power_sequences_containers: List[PPBCPowerSequenceContainer] = Field( ..., - description='The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.', + description="The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.", max_length=1000, min_length=1, ) @@ -1597,15 +1597,15 @@ class PPBCPowerProfileDefinition(BaseModel): class FRBCSystemDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.SystemDescription'] = 'FRBC.SystemDescription' + message_type: Literal["FRBC.SystemDescription"] = "FRBC.SystemDescription" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", ) actuators: List[FRBCActuatorDescription] = Field( - ..., description='Details of all Actuators.', max_length=10, min_length=1 + ..., description="Details of all Actuators.", max_length=10, min_length=1 ) - storage: FRBCStorageDescription = Field(..., description='Details of the storage.') + storage: FRBCStorageDescription = Field(..., description="Details of the storage.") diff --git a/src/s2python/ppbc/__init__.py b/src/s2python/ppbc/__init__.py index 8b3b676..1e0b4d3 100644 --- a/src/s2python/ppbc/__init__.py +++ b/src/s2python/ppbc/__init__.py @@ -1,2 +1,12 @@ from s2python.ppbc.ppbc_schedule_instruction import PPBCScheduleInstruction -from s2python.ppbc.ppbc_end_interruption_instruction import PPBCEndInterruptionInstruction +from s2python.ppbc.ppbc_end_interruption_instruction import ( + PPBCEndInterruptionInstruction, +) +from s2python.ppbc.ppbc_power_profile_definition import PPBCPowerProfileDefinition +from s2python.ppbc.ppbc_power_sequence_container import PPBCPowerSequenceContainer +from s2python.ppbc.ppbc_power_sequence import PPBCPowerSequence +from s2python.ppbc.ppbc_power_profile_status import PPBCPowerProfileStatus +from s2python.ppbc.ppbc_power_sequence_container_status import ( + PPBCPowerSequenceContainerStatus, +) +from s2python.ppbc.ppbc_power_sequence_element import PPBCPowerSequenceElement diff --git a/src/s2python/ppbc/ppbc_power_profile_definition.py b/src/s2python/ppbc/ppbc_power_profile_definition.py index 9e4c87a..b57ccc0 100644 --- a/src/s2python/ppbc/ppbc_power_profile_definition.py +++ b/src/s2python/ppbc/ppbc_power_profile_definition.py @@ -20,6 +20,7 @@ class PPBCPowerProfileDefinition( model_config = GenPPBCPowerProfileDefinition.model_config model_config["validate_assignment"] = True + message_id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["message_id"] id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["id"] power_sequences_containers: List[PPBCPowerSequenceContainer] = ( GenPPBCPowerProfileDefinition.model_fields["power_sequences_containers"] diff --git a/src/s2python/ppbc/ppbc_power_sequence.py b/src/s2python/ppbc/ppbc_power_sequence.py index 152170d..2c22cd9 100644 --- a/src/s2python/ppbc/ppbc_power_sequence.py +++ b/src/s2python/ppbc/ppbc_power_sequence.py @@ -15,9 +15,7 @@ @catch_and_convert_exceptions -class PPBCPowerSequenceContainer( - GenPPBCPowerSequence, S2Message["PPBCPowerSequenceContainer"] -): +class PPBCPowerSequence(GenPPBCPowerSequence, S2Message["PPBCPowerSequence"]): model_config = GenPPBCPowerSequence.model_config model_config["validate_assignment"] = True diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index bc213ff..2d0a50e 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -13,13 +13,15 @@ @catch_and_convert_exceptions -class PPBCPowerProfileDefinitionStatus( +class PPBCPowerSequenceContainerStatus( GenPPBCPowerSequenceContainerStatus, S2Message["PPBCPowerProfileDefinitionStatus"] ): model_config = GenPPBCPowerSequenceContainerStatus.model_config model_config["validate_assignment"] = True - power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields["id"] + power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ + "power_profile_id" + ] sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ "sequence_container_id" ] diff --git a/src/s2python/ppbc/ppbc_power_sequence_element.py b/src/s2python/ppbc/ppbc_power_sequence_element.py index 7763510..e239453 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_element.py +++ b/src/s2python/ppbc/ppbc_power_sequence_element.py @@ -24,5 +24,3 @@ class PPBCPowerSequenceElement( power_values: List[PowerForecastValue] = GenPPBCPowerSequenceElement.model_fields[ "power_values" ] - - \ No newline at end of file diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py new file mode 100644 index 0000000..46d3e11 --- /dev/null +++ b/tests/unit/frbc/frbc_actuator_description_test.py @@ -0,0 +1,122 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCActuatorDescriptionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "id": "5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9", + "diagnostic_label": "some-test-string8906", + "supported_commodities": [ + "GAS" + ], + "operation_modes": [ + { + "id": "512001f9-e720-40ce-88e2-06694bb2ca6d", + "diagnostic_label": "some-test-string1472", + "elements": [ + { + "fill_level_range": { + "start_of_range": 27494.741307134802, + "end_of_range": 58877.84844659244 + }, + "fill_rate": { + "start_of_range": 25438.73202815934, + "end_of_range": 32365.85082869052 + }, + "power_ranges": [ + { + "start_of_range": 810.7301251031068, + "end_of_range": 36176.5333835611, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 37284.94705878388, + "end_of_range": 61334.00977608085 + } + } + ], + "abnormal_condition_only": false + } + ], + "transitions": [ + { + "id": "5427f2cc-7d7d-4d30-bdc8-5f6594a86297", + "from_": "de2771e5-bac3-4385-8ee1-ad1500b2905f", + "to": "a169553d-cac4-4b29-9cce-9a9a17b5aebd", + "start_timers": [ + "26538354-e139-448d-9cb1-19da008bbd43" + ], + "blocking_timers": [ + "947ad518-3305-4be4-b847-a4b01a0bfd01" + ], + "transition_costs": 3119.39267710221, + "transition_duration": 21417, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "166fdf46-2695-412e-a014-dbd95dee1eb2", + "diagnostic_label": "some-test-string9309", + "duration": 16076 + } + ] +} + """ + + # Act + frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) + + # Assert + self.assertEqual(frbc_actuator_description.id, uuid.UUID("5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9")) + self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string8906") + self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) + self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("512001f9-e720-40ce-88e2-06694bb2ca6d"), diagnostic_label="some-test-string1472", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=27494.741307134802, end_of_range=58877.84844659244), fill_rate=NumberRange(start_of_range=25438.73202815934, end_of_range=32365.85082869052), power_ranges=[PowerRange(start_of_range=810.7301251031068, end_of_range=36176.5333835611, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=37284.94705878388, end_of_range=61334.00977608085))], abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("5427f2cc-7d7d-4d30-bdc8-5f6594a86297"), from_=uuid.UUID("de2771e5-bac3-4385-8ee1-ad1500b2905f"), to=uuid.UUID("a169553d-cac4-4b29-9cce-9a9a17b5aebd"), start_timers=[uuid.UUID("26538354-e139-448d-9cb1-19da008bbd43")], blocking_timers=[uuid.UUID("947ad518-3305-4be4-b847-a4b01a0bfd01")], transition_costs=3119.39267710221, transition_duration=Duration.from_timedelta(timedelta(milliseconds=21417)), abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("166fdf46-2695-412e-a014-dbd95dee1eb2"), diagnostic_label="some-test-string9309", duration=Duration.from_timedelta(timedelta(milliseconds=16076)))]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9"), diagnostic_label="some-test-string8906", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("512001f9-e720-40ce-88e2-06694bb2ca6d"), diagnostic_label="some-test-string1472", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=27494.741307134802, end_of_range=58877.84844659244), fill_rate=NumberRange(start_of_range=25438.73202815934, end_of_range=32365.85082869052), power_ranges=[PowerRange(start_of_range=810.7301251031068, end_of_range=36176.5333835611, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=37284.94705878388, end_of_range=61334.00977608085))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5427f2cc-7d7d-4d30-bdc8-5f6594a86297"), from_=uuid.UUID("de2771e5-bac3-4385-8ee1-ad1500b2905f"), to=uuid.UUID("a169553d-cac4-4b29-9cce-9a9a17b5aebd"), start_timers=[uuid.UUID("26538354-e139-448d-9cb1-19da008bbd43")], blocking_timers=[uuid.UUID("947ad518-3305-4be4-b847-a4b01a0bfd01")], transition_costs=3119.39267710221, transition_duration=Duration.from_timedelta(timedelta(milliseconds=21417)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("166fdf46-2695-412e-a014-dbd95dee1eb2"), diagnostic_label="some-test-string9309", duration=Duration.from_timedelta(timedelta(milliseconds=16076)))]) + + # Act + json_str = frbc_actuator_description.to_json() + + # Assert + expected_json = { 'diagnostic_label': 'some-test-string8906', + 'id': '5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string1472', + 'elements': [ { 'fill_level_range': { 'end_of_range': 58877.84844659244, + 'start_of_range': 27494.741307134802}, + 'fill_rate': { 'end_of_range': 32365.85082869052, + 'start_of_range': 25438.73202815934}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 36176.5333835611, + 'start_of_range': 810.7301251031068}], + 'running_costs': { 'end_of_range': 61334.00977608085, + 'start_of_range': 37284.94705878388}}], + 'id': '512001f9-e720-40ce-88e2-06694bb2ca6d'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string9309', + 'duration': 16076, + 'id': '166fdf46-2695-412e-a014-dbd95dee1eb2'}], + 'transitions': [ { 'abnormal_condition_only': False, + 'blocking_timers': [ '947ad518-3305-4be4-b847-a4b01a0bfd01'], + 'from_': 'de2771e5-bac3-4385-8ee1-ad1500b2905f', + 'id': '5427f2cc-7d7d-4d30-bdc8-5f6594a86297', + 'start_timers': [ '26538354-e139-448d-9cb1-19da008bbd43'], + 'to': 'a169553d-cac4-4b29-9cce-9a9a17b5aebd', + 'transition_costs': 3119.39267710221, + 'transition_duration': 21417}]} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py index b3bb5c8..819a923 100644 --- a/tests/unit/frbc/frbc_actuator_status_test.py +++ b/tests/unit/frbc/frbc_actuator_status_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,13 +13,13 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "active_operation_mode_id": "395dcbc5-5c7f-415e-8727-e48fc53761bc", - "actuator_id": "1cee425e-861b-417a-8208-bb6d53aafb00", - "message_id": "07f3d559-63c5-4369-a9e0-deed4195f651", "message_type": "FRBC.ActuatorStatus", - "operation_mode_factor": 6919.960475850124, - "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", - "transition_timestamp": "2020-01-02T07:56:46Z" + "message_id": "5d000419-7f2c-451a-b803-ebcc72b7d0bd", + "actuator_id": "9752ddc9-b04c-4902-a92c-3f181eafd270", + "active_operation_mode_id": "8d00a902-e202-4c6a-876a-4182bb83c8ac", + "operation_mode_factor": 39.51079942572766, + "previous_operation_mode_id": "767ff15a-e406-4ad3-8d74-0f470f9b0589", + "transition_timestamp": "2022-12-10T08:41:46+07:00" } """ @@ -26,68 +27,27 @@ def test__from_json__happy_path_full(self): frbc_actuator_status = FRBCActuatorStatus.from_json(json_str) # Assert - self.assertEqual( - frbc_actuator_status.active_operation_mode_id, - uuid.UUID("395dcbc5-5c7f-415e-8727-e48fc53761bc"), - ) - self.assertEqual( - frbc_actuator_status.actuator_id, - uuid.UUID("1cee425e-861b-417a-8208-bb6d53aafb00"), - ) - self.assertEqual( - frbc_actuator_status.message_id, - uuid.UUID("07f3d559-63c5-4369-a9e0-deed4195f651"), - ) - self.assertEqual(frbc_actuator_status.message_type, "FRBC.ActuatorStatus") - self.assertEqual(frbc_actuator_status.operation_mode_factor, 6919.960475850124) - self.assertEqual( - frbc_actuator_status.previous_operation_mode_id, - uuid.UUID("2ed8f7de-cbaa-4cab-9d25-6792317aa284"), - ) - self.assertEqual( - frbc_actuator_status.transition_timestamp, - datetime( - year=2020, - month=1, - day=2, - hour=7, - minute=56, - second=46, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_actuator_status.message_type, FRBC.ActuatorStatus) + self.assertEqual(frbc_actuator_status.message_id, uuid.UUID("5d000419-7f2c-451a-b803-ebcc72b7d0bd")) + self.assertEqual(frbc_actuator_status.actuator_id, uuid.UUID("9752ddc9-b04c-4902-a92c-3f181eafd270")) + self.assertEqual(frbc_actuator_status.active_operation_mode_id, uuid.UUID("8d00a902-e202-4c6a-876a-4182bb83c8ac")) + self.assertEqual(frbc_actuator_status.operation_mode_factor, 39.51079942572766) + self.assertEqual(frbc_actuator_status.previous_operation_mode_id, uuid.UUID("767ff15a-e406-4ad3-8d74-0f470f9b0589")) + self.assertEqual(frbc_actuator_status.transition_timestamp, datetime(year=2022, month=12, day=10, hour=8, minute=41, second=46, tzinfo=offset(offset=timedelta(seconds=25200.0)))) def test__to_json__happy_path_full(self): # Arrange - frbc_actuator_status = FRBCActuatorStatus( - active_operation_mode_id=uuid.UUID("395dcbc5-5c7f-415e-8727-e48fc53761bc"), - actuator_id=uuid.UUID("1cee425e-861b-417a-8208-bb6d53aafb00"), - 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"), - transition_timestamp=datetime( - year=2020, - month=1, - day=2, - hour=7, - minute=56, - second=46, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_actuator_status = FRBCActuatorStatus(message_type=FRBC.ActuatorStatus, message_id=uuid.UUID("5d000419-7f2c-451a-b803-ebcc72b7d0bd"), actuator_id=uuid.UUID("9752ddc9-b04c-4902-a92c-3f181eafd270"), active_operation_mode_id=uuid.UUID("8d00a902-e202-4c6a-876a-4182bb83c8ac"), operation_mode_factor=39.51079942572766, previous_operation_mode_id=uuid.UUID("767ff15a-e406-4ad3-8d74-0f470f9b0589"), transition_timestamp=datetime(year=2022, month=12, day=10, hour=8, minute=41, second=46, tzinfo=offset(offset=timedelta(seconds=25200.0)))) # Act json_str = frbc_actuator_status.to_json() # Assert - expected_json = { - "active_operation_mode_id": "395dcbc5-5c7f-415e-8727-e48fc53761bc", - "actuator_id": "1cee425e-861b-417a-8208-bb6d53aafb00", - "message_id": "07f3d559-63c5-4369-a9e0-deed4195f651", - "message_type": "FRBC.ActuatorStatus", - "operation_mode_factor": 6919.960475850124, - "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", - "transition_timestamp": "2020-01-02T07:56:46Z", - } + expected_json = { 'active_operation_mode_id': '8d00a902-e202-4c6a-876a-4182bb83c8ac', + 'actuator_id': '9752ddc9-b04c-4902-a92c-3f181eafd270', + 'message_id': '5d000419-7f2c-451a-b803-ebcc72b7d0bd', + 'message_type': 'FRBC.ActuatorStatus', + 'operation_mode_factor': 39.51079942572766, + 'previous_operation_mode_id': '767ff15a-e406-4ad3-8d74-0f470f9b0589', + 'transition_timestamp': '2022-12-10T08:41:46+07:00'} self.assertEqual(json.loads(json_str), expected_json) 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..5f72002 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 @@ -1,10 +1,11 @@ -from datetime import timedelta + +from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase +import uuid from s2python.common import * from s2python.frbc import * -from s2python.s2_validation_error import S2ValidationError class FRBCFillLevelTargetProfileElementTest(TestCase): @@ -12,50 +13,30 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "duration": 12950, + "duration": 16041, "fill_level_range": { - "end_of_range": 8176, - "start_of_range": 6207 + "start_of_range": 38789.06538190935, + "end_of_range": 45889.620464907246 } } """ # Act - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json( - json_str - ) + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json(json_str) # Assert - self.assertEqual( - frbc_fill_level_target_profile_element.duration, - Duration.from_timedelta(timedelta(milliseconds=12950)), - ) - self.assertEqual( - frbc_fill_level_target_profile_element.fill_level_range, - NumberRange(end_of_range=8176.0, start_of_range=6207.0), - ) + self.assertEqual(frbc_fill_level_target_profile_element.duration, Duration.from_timedelta(timedelta(milliseconds=16041))) + self.assertEqual(frbc_fill_level_target_profile_element.fill_level_range, NumberRange(start_of_range=38789.06538190935, end_of_range=45889.620464907246)) def test__to_json__happy_path_full(self): # Arrange - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=12950)), - fill_level_range=NumberRange(end_of_range=8176, start_of_range=6207), - ) + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=16041)), fill_level_range=NumberRange(start_of_range=38789.06538190935, end_of_range=45889.620464907246)) # Act json_str = frbc_fill_level_target_profile_element.to_json() # Assert - expected_json = { - "duration": 12950, - "fill_level_range": {"end_of_range": 8176, "start_of_range": 6207}, - } + expected_json = { 'duration': 16041, + 'fill_level_range': { 'end_of_range': 45889.620464907246, + 'start_of_range': 38789.06538190935}} self.assertEqual(json.loads(json_str), expected_json) - - def test__init__fill_level_range_end_is_smaller_than_start(self): - # Arrange / Act / Assert - with self.assertRaises(S2ValidationError): - FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=12950)), - fill_level_range=NumberRange(end_of_range=6000, start_of_range=8176), - ) 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..5142704 100644 --- a/tests/unit/frbc/frbc_fill_level_target_profile_test.py +++ b/tests/unit/frbc/frbc_fill_level_target_profile_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,18 +13,18 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.FillLevelTargetProfile", + "message_id": "698ea780-c918-4ea8-aef2-885a5c0228ad", + "start_time": "2020-05-16T18:38:12+08:00", "elements": [ { - "duration": 4704, + "duration": 30067, "fill_level_range": { - "end_of_range": 10800.98606857073545, - "start_of_range": 6891.19014440217 + "start_of_range": 18107.25728114559, + "end_of_range": 51078.86550304321 } } - ], - "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", - "message_type": "FRBC.FillLevelTargetProfile", - "start_time": "2021-04-17T00:19:20Z" + ] } """ @@ -31,77 +32,23 @@ def test__from_json__happy_path_full(self): frbc_fill_level_target_profile = FRBCFillLevelTargetProfile.from_json(json_str) # Assert - self.assertEqual( - frbc_fill_level_target_profile.elements, - [ - FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=4704)), - fill_level_range=NumberRange( - end_of_range=10800.98606857073545, - start_of_range=6891.19014440217, - ), - ) - ], - ) - self.assertEqual( - 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.start_time, - datetime( - year=2021, - month=4, - day=17, - hour=0, - minute=19, - second=20, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_fill_level_target_profile.message_type, FRBC.FillLevelTargetProfile) + self.assertEqual(frbc_fill_level_target_profile.message_id, uuid.UUID("698ea780-c918-4ea8-aef2-885a5c0228ad")) + self.assertEqual(frbc_fill_level_target_profile.start_time, datetime(year=2020, month=5, day=16, hour=18, minute=38, second=12, tzinfo=offset(offset=timedelta(seconds=28800.0)))) + self.assertEqual(frbc_fill_level_target_profile.elements, [FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=30067)), fill_level_range=NumberRange(start_of_range=18107.25728114559, end_of_range=51078.86550304321))]) def test__to_json__happy_path_full(self): # Arrange - frbc_fill_level_target_profile = FRBCFillLevelTargetProfile( - elements=[ - FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=4704)), - fill_level_range=NumberRange( - end_of_range=10800.98606857073545, - start_of_range=6891.19014440217, - ), - ) - ], - message_id=uuid.UUID("04a6c8af-ca8d-420c-9c11-e96a70fe82b1"), - message_type="FRBC.FillLevelTargetProfile", - start_time=datetime( - year=2021, - month=4, - day=17, - hour=0, - minute=19, - second=20, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_fill_level_target_profile = FRBCFillLevelTargetProfile(message_type=FRBC.FillLevelTargetProfile, message_id=uuid.UUID("698ea780-c918-4ea8-aef2-885a5c0228ad"), start_time=datetime(year=2020, month=5, day=16, hour=18, minute=38, second=12, tzinfo=offset(offset=timedelta(seconds=28800.0))), elements=[FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=30067)), fill_level_range=NumberRange(start_of_range=18107.25728114559, end_of_range=51078.86550304321))]) # Act json_str = frbc_fill_level_target_profile.to_json() # Assert - expected_json = { - "elements": [ - { - "duration": 4704, - "fill_level_range": { - "end_of_range": 10800.98606857073545, - "start_of_range": 6891.19014440217, - }, - } - ], - "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", - "message_type": "FRBC.FillLevelTargetProfile", - "start_time": "2021-04-17T00:19:20Z", - } + expected_json = { 'elements': [ { 'duration': 30067, + 'fill_level_range': { 'end_of_range': 51078.86550304321, + 'start_of_range': 18107.25728114559}}], + 'message_id': '698ea780-c918-4ea8-aef2-885a5c0228ad', + 'message_type': 'FRBC.FillLevelTargetProfile', + 'start_time': '2020-05-16T18:38:12+08:00'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_instruction_test.py b/tests/unit/frbc/frbc_instruction_test.py index 901a711..9f552e6 100644 --- a/tests/unit/frbc/frbc_instruction_test.py +++ b/tests/unit/frbc/frbc_instruction_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,14 +13,14 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "abnormal_condition": true, - "actuator_id": "db7855dd-05c4-4ba8-81e2-d10001c5bc3f", - "execution_time": "2023-04-11T16:46:33+01:00", - "id": "9ffd68cd-b0e2-44a6-aded-4dce6c18247e", - "message_id": "bcb3e1da-e797-4951-86be-5e5d9136c63f", "message_type": "FRBC.Instruction", - "operation_mode": "e7bf29a7-4ebc-49c1-a1fb-20725f450c91", - "operation_mode_factor": 2303.58902271682 + "message_id": "185b511f-725d-42c8-a1db-523cda5c45a9", + "id": "8db97bd0-258e-4a8b-92be-40e10b1c7a2c", + "actuator_id": "cb06550c-e8a2-418d-8885-24a530dde4c4", + "operation_mode": "4a687962-da90-4379-9190-5d1d68f9dd18", + "operation_mode_factor": 4456.156981790672, + "execution_time": "2020-11-11T07:59:54-11:00", + "abnormal_condition": true } """ @@ -27,70 +28,29 @@ def test__from_json__happy_path_full(self): frbc_instruction = FRBCInstruction.from_json(json_str) # Assert + self.assertEqual(frbc_instruction.message_type, FRBC.Instruction) + self.assertEqual(frbc_instruction.message_id, uuid.UUID("185b511f-725d-42c8-a1db-523cda5c45a9")) + self.assertEqual(frbc_instruction.id, uuid.UUID("8db97bd0-258e-4a8b-92be-40e10b1c7a2c")) + self.assertEqual(frbc_instruction.actuator_id, uuid.UUID("cb06550c-e8a2-418d-8885-24a530dde4c4")) + self.assertEqual(frbc_instruction.operation_mode, uuid.UUID("4a687962-da90-4379-9190-5d1d68f9dd18")) + self.assertEqual(frbc_instruction.operation_mode_factor, 4456.156981790672) + self.assertEqual(frbc_instruction.execution_time, datetime(year=2020, month=11, day=11, hour=7, minute=59, second=54, tzinfo=offset(offset=timedelta(seconds=-39600.0)))) self.assertEqual(frbc_instruction.abnormal_condition, True) - self.assertEqual( - frbc_instruction.actuator_id, - uuid.UUID("db7855dd-05c4-4ba8-81e2-d10001c5bc3f"), - ) - self.assertEqual( - frbc_instruction.execution_time, - datetime( - year=2023, - month=4, - day=11, - hour=16, - minute=46, - second=33, - tzinfo=offset(offset=timedelta(seconds=3600.0)), - ), - ) - self.assertEqual( - frbc_instruction.id, uuid.UUID("9ffd68cd-b0e2-44a6-aded-4dce6c18247e") - ) - self.assertEqual( - frbc_instruction.message_id, - uuid.UUID("bcb3e1da-e797-4951-86be-5e5d9136c63f"), - ) - self.assertEqual(frbc_instruction.message_type, "FRBC.Instruction") - self.assertEqual( - frbc_instruction.operation_mode, - uuid.UUID("e7bf29a7-4ebc-49c1-a1fb-20725f450c91"), - ) - self.assertEqual(frbc_instruction.operation_mode_factor, 2303.58902271682) def test__to_json__happy_path_full(self): # Arrange - frbc_instruction = FRBCInstruction( - abnormal_condition=True, - actuator_id=uuid.UUID("db7855dd-05c4-4ba8-81e2-d10001c5bc3f"), - execution_time=datetime( - year=2023, - month=4, - day=11, - hour=16, - minute=46, - second=33, - tzinfo=offset(offset=timedelta(seconds=3600.0)), - ), - id=uuid.UUID("9ffd68cd-b0e2-44a6-aded-4dce6c18247e"), - message_id=uuid.UUID("bcb3e1da-e797-4951-86be-5e5d9136c63f"), - message_type="FRBC.Instruction", - operation_mode=uuid.UUID("e7bf29a7-4ebc-49c1-a1fb-20725f450c91"), - operation_mode_factor=2303.58902271682, - ) + frbc_instruction = FRBCInstruction(message_type=FRBC.Instruction, message_id=uuid.UUID("185b511f-725d-42c8-a1db-523cda5c45a9"), id=uuid.UUID("8db97bd0-258e-4a8b-92be-40e10b1c7a2c"), actuator_id=uuid.UUID("cb06550c-e8a2-418d-8885-24a530dde4c4"), operation_mode=uuid.UUID("4a687962-da90-4379-9190-5d1d68f9dd18"), operation_mode_factor=4456.156981790672, execution_time=datetime(year=2020, month=11, day=11, hour=7, minute=59, second=54, tzinfo=offset(offset=timedelta(seconds=-39600.0))), abnormal_condition=True) # Act json_str = frbc_instruction.to_json() # Assert - expected_json = { - "abnormal_condition": True, - "actuator_id": "db7855dd-05c4-4ba8-81e2-d10001c5bc3f", - "execution_time": "2023-04-11T16:46:33+01:00", - "id": "9ffd68cd-b0e2-44a6-aded-4dce6c18247e", - "message_id": "bcb3e1da-e797-4951-86be-5e5d9136c63f", - "message_type": "FRBC.Instruction", - "operation_mode": "e7bf29a7-4ebc-49c1-a1fb-20725f450c91", - "operation_mode_factor": 2303.58902271682, - } + expected_json = { 'abnormal_condition': True, + 'actuator_id': 'cb06550c-e8a2-418d-8885-24a530dde4c4', + 'execution_time': '2020-11-11T07:59:54-11:00', + 'id': '8db97bd0-258e-4a8b-92be-40e10b1c7a2c', + 'message_id': '185b511f-725d-42c8-a1db-523cda5c45a9', + 'message_type': 'FRBC.Instruction', + 'operation_mode': '4a687962-da90-4379-9190-5d1d68f9dd18', + 'operation_mode_factor': 4456.156981790672} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py index 79f480a..e900d5d 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -5,7 +6,6 @@ from s2python.common import * from s2python.frbc import * -from s2python.s2_validation_error import S2ValidationError class FRBCLeakageBehaviourElementTest(TestCase): @@ -14,10 +14,10 @@ def test__from_json__happy_path_full(self): json_str = """ { "fill_level_range": { - "end_of_range": 40192.498918818455, - "start_of_range": 29234.82582981918 + "start_of_range": 28885.23485069778, + "end_of_range": 40074.88428029624 }, - "leakage_rate": 1170.4041485129987 + "leakage_rate": 6677.574789126709 } """ @@ -25,40 +25,18 @@ def test__from_json__happy_path_full(self): frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement.from_json(json_str) # Assert - self.assertEqual( - frbc_leakage_behaviour_element.fill_level_range, - 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.fill_level_range, NumberRange(start_of_range=28885.23485069778, end_of_range=40074.88428029624)) + self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 6677.574789126709) def test__to_json__happy_path_full(self): # Arrange - frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=40192.498918818455, start_of_range=29234.82582981918 - ), - leakage_rate=1170.4041485129987, - ) + frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=28885.23485069778, end_of_range=40074.88428029624), leakage_rate=6677.574789126709) # Act json_str = frbc_leakage_behaviour_element.to_json() # Assert - expected_json = { - "fill_level_range": { - "end_of_range": 40192.498918818455, - "start_of_range": 29234.82582981918, - }, - "leakage_rate": 1170.4041485129987, - } + expected_json = { 'fill_level_range': { 'end_of_range': 40074.88428029624, + 'start_of_range': 28885.23485069778}, + 'leakage_rate': 6677.574789126709} self.assertEqual(json.loads(json_str), expected_json) - - def test__init__fill_level_range_end_is_smaller_than_start(self): - # Arrange / Act / Assert - with self.assertRaises(S2ValidationError): - FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=29234.82582981918, start_of_range=40192.498918818455 - ), - leakage_rate=1170.4041485129987, - ) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_test.py b/tests/unit/frbc/frbc_leakage_behaviour_test.py index ad290d1..ac7155c 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,18 +13,18 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.LeakageBehaviour", + "message_id": "f681fa18-589b-4081-a85b-cdb608795248", + "valid_from": "2023-09-26T17:14:46+13:00", "elements": [ { "fill_level_range": { - "end_of_range": 31155.931914859895, - "start_of_range": 5727.722922773178 + "start_of_range": 5349.224337716178, + "end_of_range": 26642.603531976765 }, - "leakage_rate": 1225.9695121338086 + "leakage_rate": 3062.489155126407 } - ], - "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", - "message_type": "FRBC.LeakageBehaviour", - "valid_from": "2022-05-26T15:02:32Z" + ] } """ @@ -31,77 +32,23 @@ def test__from_json__happy_path_full(self): frbc_leakage_behaviour = FRBCLeakageBehaviour.from_json(json_str) # Assert - self.assertEqual( - frbc_leakage_behaviour.elements, - [ - FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=31155.931914859895, - start_of_range=5727.722922773178, - ), - leakage_rate=1225.9695121338086, - ) - ], - ) - self.assertEqual( - frbc_leakage_behaviour.message_id, - uuid.UUID("b3e9604a-1127-4ecc-9f9e-336047fde285"), - ) - self.assertEqual(frbc_leakage_behaviour.message_type, "FRBC.LeakageBehaviour") - self.assertEqual( - frbc_leakage_behaviour.valid_from, - datetime( - year=2022, - month=5, - day=26, - hour=15, - minute=2, - second=32, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_leakage_behaviour.message_type, FRBC.LeakageBehaviour) + self.assertEqual(frbc_leakage_behaviour.message_id, uuid.UUID("f681fa18-589b-4081-a85b-cdb608795248")) + self.assertEqual(frbc_leakage_behaviour.valid_from, datetime(year=2023, month=9, day=26, hour=17, minute=14, second=46, tzinfo=offset(offset=timedelta(seconds=46800.0)))) + self.assertEqual(frbc_leakage_behaviour.elements, [FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=5349.224337716178, end_of_range=26642.603531976765), leakage_rate=3062.489155126407)]) def test__to_json__happy_path_full(self): # Arrange - frbc_leakage_behaviour = FRBCLeakageBehaviour( - elements=[ - FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=31155.931914859895, - start_of_range=5727.722922773178, - ), - leakage_rate=1225.9695121338086, - ) - ], - message_id=uuid.UUID("b3e9604a-1127-4ecc-9f9e-336047fde285"), - message_type="FRBC.LeakageBehaviour", - valid_from=datetime( - year=2022, - month=5, - day=26, - hour=15, - minute=2, - second=32, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_leakage_behaviour = FRBCLeakageBehaviour(message_type=FRBC.LeakageBehaviour, message_id=uuid.UUID("f681fa18-589b-4081-a85b-cdb608795248"), valid_from=datetime(year=2023, month=9, day=26, hour=17, minute=14, second=46, tzinfo=offset(offset=timedelta(seconds=46800.0))), elements=[FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=5349.224337716178, end_of_range=26642.603531976765), leakage_rate=3062.489155126407)]) # Act json_str = frbc_leakage_behaviour.to_json() # Assert - expected_json = { - "elements": [ - { - "fill_level_range": { - "end_of_range": 31155.931914859895, - "start_of_range": 5727.722922773178, - }, - "leakage_rate": 1225.9695121338086, - } - ], - "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", - "message_type": "FRBC.LeakageBehaviour", - "valid_from": "2022-05-26T15:02:32Z", - } + expected_json = { 'elements': [ { 'fill_level_range': { 'end_of_range': 26642.603531976765, + 'start_of_range': 5349.224337716178}, + 'leakage_rate': 3062.489155126407}], + 'message_id': 'f681fa18-589b-4081-a85b-cdb608795248', + 'message_type': 'FRBC.LeakageBehaviour', + 'valid_from': '2023-09-26T17:14:46+13:00'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_element_test.py b/tests/unit/frbc/frbc_operation_mode_element_test.py index d2f3455..2c8bcba 100644 --- a/tests/unit/frbc/frbc_operation_mode_element_test.py +++ b/tests/unit/frbc/frbc_operation_mode_element_test.py @@ -1,11 +1,11 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase import uuid -from s2python.common import NumberRange, PowerRange -from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement -from s2python.generated.gen_s2 import CommodityQuantity +from s2python.common import * +from s2python.frbc import * class FRBCOperationModeElementTest(TestCase): @@ -14,23 +14,23 @@ def test__from_json__happy_path_full(self): json_str = """ { "fill_level_range": { - "end_of_range": 51798.05122344172, - "start_of_range": 12901.48976850875 + "start_of_range": 2839.6809937410153, + "end_of_range": 23657.59074127252 }, "fill_rate": { - "end_of_range": 35734.54630113551, - "start_of_range": 10740.443924585083 + "start_of_range": 31115.515599679075, + "end_of_range": 34674.7451119136 }, "power_ranges": [ { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 69093.48993128976, - "start_of_range": 34859.59303603876 + "start_of_range": 28918.644493729425, + "end_of_range": 62283.65396785374, + "commodity_quantity": "ELECTRIC.POWER.L1" } ], "running_costs": { - "end_of_range": 47869.03540464825, - "start_of_range": 19009.60894672492 + "start_of_range": 27450.2722984119, + "end_of_range": 63683.41689038279 } } """ @@ -39,79 +39,26 @@ def test__from_json__happy_path_full(self): frbc_operation_mode_element = FRBCOperationModeElement.from_json(json_str) # Assert - self.assertEqual( - frbc_operation_mode_element.fill_level_range, - NumberRange( - end_of_range=51798.05122344172, start_of_range=12901.48976850875 - ), - ) - self.assertEqual( - frbc_operation_mode_element.fill_rate, - NumberRange( - end_of_range=35734.54630113551, start_of_range=10740.443924585083 - ), - ) - self.assertEqual( - frbc_operation_mode_element.power_ranges, - [ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=69093.48993128976, - start_of_range=34859.59303603876, - ) - ], - ) - self.assertEqual( - frbc_operation_mode_element.running_costs, - NumberRange( - end_of_range=47869.03540464825, start_of_range=19009.60894672492 - ), - ) + self.assertEqual(frbc_operation_mode_element.fill_level_range, NumberRange(start_of_range=2839.6809937410153, end_of_range=23657.59074127252)) + self.assertEqual(frbc_operation_mode_element.fill_rate, NumberRange(start_of_range=31115.515599679075, end_of_range=34674.7451119136)) + self.assertEqual(frbc_operation_mode_element.power_ranges, [PowerRange(start_of_range=28918.644493729425, end_of_range=62283.65396785374, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)]) + self.assertEqual(frbc_operation_mode_element.running_costs, NumberRange(start_of_range=27450.2722984119, end_of_range=63683.41689038279)) def test__to_json__happy_path_full(self): # Arrange - frbc_operation_mode_element = FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=51798.05122344172, start_of_range=12901.48976850875 - ), - fill_rate=NumberRange( - end_of_range=35734.54630113551, start_of_range=10740.443924585083 - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=69093.48993128976, - start_of_range=34859.59303603876, - ) - ], - running_costs=NumberRange( - end_of_range=47869.03540464825, start_of_range=19009.60894672492 - ), - ) + frbc_operation_mode_element = FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=2839.6809937410153, end_of_range=23657.59074127252), fill_rate=NumberRange(start_of_range=31115.515599679075, end_of_range=34674.7451119136), power_ranges=[PowerRange(start_of_range=28918.644493729425, end_of_range=62283.65396785374, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=27450.2722984119, end_of_range=63683.41689038279)) # Act json_str = frbc_operation_mode_element.to_json() # Assert - expected_json = { - "fill_level_range": { - "end_of_range": 51798.05122344172, - "start_of_range": 12901.48976850875, - }, - "fill_rate": { - "end_of_range": 35734.54630113551, - "start_of_range": 10740.443924585083, - }, - "power_ranges": [ - { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 69093.48993128976, - "start_of_range": 34859.59303603876, - } - ], - "running_costs": { - "end_of_range": 47869.03540464825, - "start_of_range": 19009.60894672492, - }, - } + expected_json = { 'fill_level_range': { 'end_of_range': 23657.59074127252, + 'start_of_range': 2839.6809937410153}, + 'fill_rate': { 'end_of_range': 34674.7451119136, + 'start_of_range': 31115.515599679075}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 62283.65396785374, + 'start_of_range': 28918.644493729425}], + 'running_costs': { 'end_of_range': 63683.41689038279, + 'start_of_range': 27450.2722984119}} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_test.py b/tests/unit/frbc/frbc_operation_mode_test.py index 0d97f27..446de12 100644 --- a/tests/unit/frbc/frbc_operation_mode_test.py +++ b/tests/unit/frbc/frbc_operation_mode_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,32 +13,32 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "abnormal_condition_only": true, - "diagnostic_label": "some-test-string7557", + "id": "44ea8c08-6aca-4b93-8434-ede68200dc69", + "diagnostic_label": "some-test-string6411", "elements": [ { "fill_level_range": { - "end_of_range": 34304.92092046668, - "start_of_range": 17579.18236077446 + "start_of_range": 13185.562172385307, + "end_of_range": 28351.769654896747 }, "fill_rate": { - "end_of_range": 41719.931165871916, - "start_of_range": 10542.600445486576 + "start_of_range": 25266.999524961477, + "end_of_range": 34750.38438764264 }, "power_ranges": [ { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 44983.5145552435, - "start_of_range": 29337.138579372047 + "start_of_range": 14986.569871211224, + "end_of_range": 24935.417325009203, + "commodity_quantity": "ELECTRIC.POWER.L1" } ], "running_costs": { - "end_of_range": 62835.00070350196, - "start_of_range": 33318.34845926906 + "start_of_range": 18871.059938463823, + "end_of_range": 30857.989097156864 } } ], - "id": "b1255236-475c-4dc7-a728-afb620a99ec8" + "abnormal_condition_only": false } """ @@ -45,95 +46,29 @@ def test__from_json__happy_path_full(self): frbc_operation_mode = FRBCOperationMode.from_json(json_str) # Assert - self.assertEqual(frbc_operation_mode.abnormal_condition_only, True) - self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string7557") - self.assertEqual( - frbc_operation_mode.elements, - [ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=34304.92092046668, start_of_range=17579.18236077446 - ), - fill_rate=NumberRange( - end_of_range=41719.931165871916, - start_of_range=10542.600445486576, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=44983.5145552435, - start_of_range=29337.138579372047, - ) - ], - running_costs=NumberRange( - end_of_range=62835.00070350196, start_of_range=33318.34845926906 - ), - ) - ], - ) - self.assertEqual( - frbc_operation_mode.id, uuid.UUID("b1255236-475c-4dc7-a728-afb620a99ec8") - ) + self.assertEqual(frbc_operation_mode.id, uuid.UUID("44ea8c08-6aca-4b93-8434-ede68200dc69")) + self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string6411") + self.assertEqual(frbc_operation_mode.elements, [FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=13185.562172385307, end_of_range=28351.769654896747), fill_rate=NumberRange(start_of_range=25266.999524961477, end_of_range=34750.38438764264), power_ranges=[PowerRange(start_of_range=14986.569871211224, end_of_range=24935.417325009203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=18871.059938463823, end_of_range=30857.989097156864))]) + self.assertEqual(frbc_operation_mode.abnormal_condition_only, False) def test__to_json__happy_path_full(self): # Arrange - frbc_operation_mode = FRBCOperationMode( - abnormal_condition_only=True, - diagnostic_label="some-test-string7557", - elements=[ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=34304.92092046668, start_of_range=17579.18236077446 - ), - fill_rate=NumberRange( - end_of_range=41719.931165871916, - start_of_range=10542.600445486576, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=44983.5145552435, - start_of_range=29337.138579372047, - ) - ], - running_costs=NumberRange( - end_of_range=62835.00070350196, start_of_range=33318.34845926906 - ), - ) - ], - id=uuid.UUID("b1255236-475c-4dc7-a728-afb620a99ec8"), - ) + frbc_operation_mode = FRBCOperationMode(id=uuid.UUID("44ea8c08-6aca-4b93-8434-ede68200dc69"), diagnostic_label="some-test-string6411", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=13185.562172385307, end_of_range=28351.769654896747), fill_rate=NumberRange(start_of_range=25266.999524961477, end_of_range=34750.38438764264), power_ranges=[PowerRange(start_of_range=14986.569871211224, end_of_range=24935.417325009203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=18871.059938463823, end_of_range=30857.989097156864))], abnormal_condition_only=False) # Act json_str = frbc_operation_mode.to_json() # Assert - expected_json = { - "abnormal_condition_only": True, - "diagnostic_label": "some-test-string7557", - "elements": [ - { - "fill_level_range": { - "end_of_range": 34304.92092046668, - "start_of_range": 17579.18236077446, - }, - "fill_rate": { - "end_of_range": 41719.931165871916, - "start_of_range": 10542.600445486576, - }, - "power_ranges": [ - { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 44983.5145552435, - "start_of_range": 29337.138579372047, - } - ], - "running_costs": { - "end_of_range": 62835.00070350196, - "start_of_range": 33318.34845926906, - }, - } - ], - "id": "b1255236-475c-4dc7-a728-afb620a99ec8", - } + expected_json = { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string6411', + 'elements': [ { 'fill_level_range': { 'end_of_range': 28351.769654896747, + 'start_of_range': 13185.562172385307}, + 'fill_rate': { 'end_of_range': 34750.38438764264, + 'start_of_range': 25266.999524961477}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 24935.417325009203, + 'start_of_range': 14986.569871211224}], + 'running_costs': { 'end_of_range': 30857.989097156864, + 'start_of_range': 18871.059938463823}}], + 'id': '44ea8c08-6aca-4b93-8434-ede68200dc69'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_description_test.py b/tests/unit/frbc/frbc_storage_description_test.py index a1e8e2e..948202c 100644 --- a/tests/unit/frbc/frbc_storage_description_test.py +++ b/tests/unit/frbc/frbc_storage_description_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,15 +13,15 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "diagnostic_label": "some-test-string3063", - "fill_level_label": "some-test-string2323", - "fill_level_range": { - "end_of_range": 14555.806367871957, - "start_of_range": 10409.397377840089 - }, + "diagnostic_label": "some-test-string6024", + "fill_level_label": "some-test-string4194", + "provides_leakage_behaviour": true, "provides_fill_level_target_profile": true, - "provides_leakage_behaviour": false, - "provides_usage_forecast": false + "provides_usage_forecast": false, + "fill_level_range": { + "start_of_range": 4284.806107128117, + "end_of_range": 15952.434979774753 + } } """ @@ -28,50 +29,26 @@ def test__from_json__happy_path_full(self): frbc_storage_description = FRBCStorageDescription.from_json(json_str) # Assert - self.assertEqual( - frbc_storage_description.diagnostic_label, "some-test-string3063" - ) - self.assertEqual( - frbc_storage_description.fill_level_label, "some-test-string2323" - ) - self.assertEqual( - frbc_storage_description.fill_level_range, - NumberRange( - end_of_range=14555.806367871957, start_of_range=10409.397377840089 - ), - ) - self.assertEqual( - frbc_storage_description.provides_fill_level_target_profile, True - ) - self.assertEqual(frbc_storage_description.provides_leakage_behaviour, False) + self.assertEqual(frbc_storage_description.diagnostic_label, "some-test-string6024") + self.assertEqual(frbc_storage_description.fill_level_label, "some-test-string4194") + self.assertEqual(frbc_storage_description.provides_leakage_behaviour, True) + self.assertEqual(frbc_storage_description.provides_fill_level_target_profile, True) self.assertEqual(frbc_storage_description.provides_usage_forecast, False) + self.assertEqual(frbc_storage_description.fill_level_range, NumberRange(start_of_range=4284.806107128117, end_of_range=15952.434979774753)) def test__to_json__happy_path_full(self): # Arrange - frbc_storage_description = FRBCStorageDescription( - diagnostic_label="some-test-string3063", - fill_level_label="some-test-string2323", - fill_level_range=NumberRange( - end_of_range=14555.806367871957, start_of_range=10409.397377840089 - ), - provides_fill_level_target_profile=True, - provides_leakage_behaviour=False, - provides_usage_forecast=False, - ) + frbc_storage_description = FRBCStorageDescription(diagnostic_label="some-test-string6024", fill_level_label="some-test-string4194", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=False, fill_level_range=NumberRange(start_of_range=4284.806107128117, end_of_range=15952.434979774753)) # Act json_str = frbc_storage_description.to_json() # Assert - expected_json = { - "diagnostic_label": "some-test-string3063", - "fill_level_label": "some-test-string2323", - "fill_level_range": { - "end_of_range": 14555.806367871957, - "start_of_range": 10409.397377840089, - }, - "provides_fill_level_target_profile": True, - "provides_leakage_behaviour": False, - "provides_usage_forecast": False, - } + expected_json = { 'diagnostic_label': 'some-test-string6024', + 'fill_level_label': 'some-test-string4194', + 'fill_level_range': { 'end_of_range': 15952.434979774753, + 'start_of_range': 4284.806107128117}, + 'provides_fill_level_target_profile': True, + 'provides_leakage_behaviour': True, + 'provides_usage_forecast': False} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_status_test.py b/tests/unit/frbc/frbc_storage_status_test.py index c2b99ab..2d819cf 100644 --- a/tests/unit/frbc/frbc_storage_status_test.py +++ b/tests/unit/frbc/frbc_storage_status_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,9 +13,9 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "message_id": "6bad8186-9ebf-4647-ac45-1c6856511a2f", "message_type": "FRBC.StorageStatus", - "present_fill_level": 2443.939298819414 + "message_id": "84266c85-e666-49b3-a4e5-a1feff77df6e", + "present_fill_level": 8890.677190972274 } """ @@ -22,28 +23,19 @@ def test__from_json__happy_path_full(self): frbc_storage_status = FRBCStorageStatus.from_json(json_str) # Assert - self.assertEqual( - frbc_storage_status.message_id, - uuid.UUID("6bad8186-9ebf-4647-ac45-1c6856511a2f"), - ) - self.assertEqual(frbc_storage_status.message_type, "FRBC.StorageStatus") - self.assertEqual(frbc_storage_status.present_fill_level, 2443.939298819414) + self.assertEqual(frbc_storage_status.message_type, FRBC.StorageStatus) + self.assertEqual(frbc_storage_status.message_id, uuid.UUID("84266c85-e666-49b3-a4e5-a1feff77df6e")) + self.assertEqual(frbc_storage_status.present_fill_level, 8890.677190972274) def test__to_json__happy_path_full(self): # Arrange - frbc_storage_status = FRBCStorageStatus( - message_id=uuid.UUID("6bad8186-9ebf-4647-ac45-1c6856511a2f"), - message_type="FRBC.StorageStatus", - present_fill_level=2443.939298819414, - ) + frbc_storage_status = FRBCStorageStatus(message_type=FRBC.StorageStatus, message_id=uuid.UUID("84266c85-e666-49b3-a4e5-a1feff77df6e"), present_fill_level=8890.677190972274) # Act json_str = frbc_storage_status.to_json() # Assert - expected_json = { - "message_id": "6bad8186-9ebf-4647-ac45-1c6856511a2f", - "message_type": "FRBC.StorageStatus", - "present_fill_level": 2443.939298819414, - } + expected_json = { 'message_id': '84266c85-e666-49b3-a4e5-a1feff77df6e', + 'message_type': 'FRBC.StorageStatus', + 'present_fill_level': 8890.677190972274} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py index 9950ea7..f943a1a 100644 --- a/tests/unit/frbc/frbc_system_description_test.py +++ b/tests/unit/frbc/frbc_system_description_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,82 +13,82 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.SystemDescription", + "message_id": "873112de-a6d8-4123-a523-fc908e9a0310", + "valid_from": "2021-07-20T04:50:11+12:00", "actuators": [ { - "diagnostic_label": "some-test-string2728", - "id": "a1061148-f19e-4b1b-8fe3-b506583ce61e", + "id": "9d5243b4-f92c-42cd-9d6b-00e3e5953dd4", + "diagnostic_label": "some-test-string2832", + "supported_commodities": [ + "GAS" + ], "operation_modes": [ { - "abnormal_condition_only": false, - "diagnostic_label": "some-test-string2930", + "id": "4526bc26-cdd8-4cd6-ae35-dfb2a6751375", + "diagnostic_label": "some-test-string2393", "elements": [ { "fill_level_range": { - "end_of_range": 36932.65171036228, - "start_of_range": 12649.272766336762 + "start_of_range": 22530.41813108864, + "end_of_range": 50141.518189827795 }, "fill_rate": { - "end_of_range": 34553.16163528188, - "start_of_range": 14377.963894945604 + "start_of_range": 10937.461391570358, + "end_of_range": 22111.530603779658 }, "power_ranges": [ { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 46924.65023353163, - "start_of_range": 11888.235871902496 + "start_of_range": 3372.255894277375, + "end_of_range": 11155.631994233203, + "commodity_quantity": "ELECTRIC.POWER.L1" } ], "running_costs": { - "end_of_range": 42897.60731684277, - "start_of_range": 33997.56376994998 + "start_of_range": 31076.328103540887, + "end_of_range": 38380.84063645549 } } ], - "id": "2795136c-eb30-4f8a-bdaa-61feba1e71b6" - } - ], - "supported_commodities": [ - "ELECTRICITY" - ], - "timers": [ - { - "diagnostic_label": "some-test-string4315", - "duration": 14099, - "id": "e1ff9e58-935b-4765-92e3-5e7679f73eb6" + "abnormal_condition_only": false } ], "transitions": [ { - "abnormal_condition_only": true, - "blocking_timers": [ - "e1ff9e58-935b-4765-92e3-5e7679f73eb6" - ], - "from": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "id": "c32cc1d3-4722-41e3-a8de-55307c723611", + "id": "5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e", + "from_": "cac664cf-bec3-49df-aeb4-d5dca13f3f0b", + "to": "61605548-7d68-4ceb-bbe2-b4d79d385b11", "start_timers": [ - "e1ff9e58-935b-4765-92e3-5e7679f73eb6" + "d2436620-dce4-43fe-a59d-a7bc3045a7cd" ], - "to": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "transition_costs": 1018.4228054114793, - "transition_duration": 11988 + "blocking_timers": [ + "2f690536-73d5-4441-90b4-a28fa60e962f" + ], + "transition_costs": 2339.5028721662775, + "transition_duration": 35219, + "abnormal_condition_only": true + } + ], + "timers": [ + { + "id": "0c8a46de-b2bf-4adc-9052-0d2aa5dade66", + "diagnostic_label": "some-test-string8913", + "duration": 20103 } ] } ], - "message_id": "97256813-de70-4640-a992-9ae0b2d8e4d1", - "message_type": "FRBC.SystemDescription", "storage": { - "diagnostic_label": "some-test-string8418", - "fill_level_label": "some-test-string9512", - "fill_level_range": { - "end_of_range": 20876.752745956997, - "start_of_range": 18324.0229135081 - }, - "provides_fill_level_target_profile": false, + "diagnostic_label": "some-test-string816", + "fill_level_label": "some-test-string5484", "provides_leakage_behaviour": true, - "provides_usage_forecast": false - }, - "valid_from": "2020-10-07T06:30:55Z" + "provides_fill_level_target_profile": true, + "provides_usage_forecast": true, + "fill_level_range": { + "start_of_range": 16537.091716121224, + "end_of_range": 19907.512143139258 + } + } } """ @@ -95,257 +96,54 @@ def test__from_json__happy_path_full(self): frbc_system_description = FRBCSystemDescription.from_json(json_str) # Assert - # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. - # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to - # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 - transition = Transition( - **{ - "id": uuid.UUID("c32cc1d3-4722-41e3-a8de-55307c723611"), - "from": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "to": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "start_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "blocking_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "transition_costs": 1018.4228054114793, - "transition_duration": Duration.from_milliseconds(11988), - "abnormal_condition_only": True, - } - ) - - self.assertEqual( - frbc_system_description.actuators, - [ - FRBCActuatorDescription( - diagnostic_label="some-test-string2728", - id=uuid.UUID("a1061148-f19e-4b1b-8fe3-b506583ce61e"), - operation_modes=[ - FRBCOperationMode( - abnormal_condition_only=False, - diagnostic_label="some-test-string2930", - elements=[ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=36932.65171036228, - start_of_range=12649.272766336762, - ), - fill_rate=NumberRange( - end_of_range=34553.16163528188, - start_of_range=14377.963894945604, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=46924.65023353163, - start_of_range=11888.235871902496, - ) - ], - running_costs=NumberRange( - end_of_range=42897.60731684277, - start_of_range=33997.56376994998, - ), - ) - ], - id=uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - ) - ], - supported_commodities=[Commodity.ELECTRICITY], - timers=[ - Timer( - diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta(timedelta(milliseconds=14099)), - id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), - ) - ], - transitions=[transition], - ) - ], - ) - self.assertEqual( - frbc_system_description.message_id, - uuid.UUID("97256813-de70-4640-a992-9ae0b2d8e4d1"), - ) - self.assertEqual(frbc_system_description.message_type, "FRBC.SystemDescription") - self.assertEqual( - frbc_system_description.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), - provides_fill_level_target_profile=False, - provides_leakage_behaviour=True, - provides_usage_forecast=False, - ), - ) - self.assertEqual( - frbc_system_description.valid_from, - datetime( - year=2020, - month=10, - day=7, - hour=6, - minute=30, - second=55, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_system_description.message_type, FRBC.SystemDescription) + self.assertEqual(frbc_system_description.message_id, uuid.UUID("873112de-a6d8-4123-a523-fc908e9a0310")) + self.assertEqual(frbc_system_description.valid_from, datetime(year=2021, month=7, day=20, hour=4, minute=50, second=11, tzinfo=offset(offset=timedelta(seconds=43200.0)))) + self.assertEqual(frbc_system_description.actuators, [FRBCActuatorDescription(id=uuid.UUID("9d5243b4-f92c-42cd-9d6b-00e3e5953dd4"), diagnostic_label="some-test-string2832", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("4526bc26-cdd8-4cd6-ae35-dfb2a6751375"), diagnostic_label="some-test-string2393", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=22530.41813108864, end_of_range=50141.518189827795), fill_rate=NumberRange(start_of_range=10937.461391570358, end_of_range=22111.530603779658), power_ranges=[PowerRange(start_of_range=3372.255894277375, end_of_range=11155.631994233203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=31076.328103540887, end_of_range=38380.84063645549))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e"), from_=uuid.UUID("cac664cf-bec3-49df-aeb4-d5dca13f3f0b"), to=uuid.UUID("61605548-7d68-4ceb-bbe2-b4d79d385b11"), start_timers=[uuid.UUID("d2436620-dce4-43fe-a59d-a7bc3045a7cd")], blocking_timers=[uuid.UUID("2f690536-73d5-4441-90b4-a28fa60e962f")], transition_costs=2339.5028721662775, transition_duration=Duration.from_timedelta(timedelta(milliseconds=35219)), abnormal_condition_only=True)], timers=[Timer(id=uuid.UUID("0c8a46de-b2bf-4adc-9052-0d2aa5dade66"), diagnostic_label="some-test-string8913", duration=Duration.from_timedelta(timedelta(milliseconds=20103)))])]) + self.assertEqual(frbc_system_description.storage, FRBCStorageDescription(diagnostic_label="some-test-string816", fill_level_label="some-test-string5484", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=16537.091716121224, end_of_range=19907.512143139258))) def test__to_json__happy_path_full(self): # Arrange - # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. - # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to - # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 - transition = Transition( - **{ - "id": uuid.UUID("c32cc1d3-4722-41e3-a8de-55307c723611"), - "from": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "to": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "start_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "blocking_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "transition_costs": 1018.4228054114793, - "transition_duration": Duration.from_milliseconds(11988), - "abnormal_condition_only": True, - } - ) - frbc_system_description = FRBCSystemDescription( - actuators=[ - FRBCActuatorDescription( - diagnostic_label="some-test-string2728", - id=uuid.UUID("a1061148-f19e-4b1b-8fe3-b506583ce61e"), - operation_modes=[ - FRBCOperationMode( - abnormal_condition_only=False, - diagnostic_label="some-test-string2930", - elements=[ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=36932.65171036228, - start_of_range=12649.272766336762, - ), - fill_rate=NumberRange( - end_of_range=34553.16163528188, - start_of_range=14377.963894945604, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=46924.65023353163, - start_of_range=11888.235871902496, - ) - ], - running_costs=NumberRange( - end_of_range=42897.60731684277, - start_of_range=33997.56376994998, - ), - ) - ], - id=uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - ) - ], - supported_commodities=[Commodity.ELECTRICITY], - timers=[ - Timer( - diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta(timedelta(milliseconds=14099)), - id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), - ) - ], - transitions=[transition], - ) - ], - message_id=uuid.UUID("97256813-de70-4640-a992-9ae0b2d8e4d1"), - message_type="FRBC.SystemDescription", - 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), - provides_fill_level_target_profile=False, - provides_leakage_behaviour=True, - provides_usage_forecast=False, - ), - valid_from=datetime( - year=2020, - month=10, - day=7, - hour=6, - minute=30, - second=55, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_system_description = FRBCSystemDescription(message_type=FRBC.SystemDescription, message_id=uuid.UUID("873112de-a6d8-4123-a523-fc908e9a0310"), valid_from=datetime(year=2021, month=7, day=20, hour=4, minute=50, second=11, tzinfo=offset(offset=timedelta(seconds=43200.0))), actuators=[FRBCActuatorDescription(id=uuid.UUID("9d5243b4-f92c-42cd-9d6b-00e3e5953dd4"), diagnostic_label="some-test-string2832", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("4526bc26-cdd8-4cd6-ae35-dfb2a6751375"), diagnostic_label="some-test-string2393", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=22530.41813108864, end_of_range=50141.518189827795), fill_rate=NumberRange(start_of_range=10937.461391570358, end_of_range=22111.530603779658), power_ranges=[PowerRange(start_of_range=3372.255894277375, end_of_range=11155.631994233203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=31076.328103540887, end_of_range=38380.84063645549))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e"), from_=uuid.UUID("cac664cf-bec3-49df-aeb4-d5dca13f3f0b"), to=uuid.UUID("61605548-7d68-4ceb-bbe2-b4d79d385b11"), start_timers=[uuid.UUID("d2436620-dce4-43fe-a59d-a7bc3045a7cd")], blocking_timers=[uuid.UUID("2f690536-73d5-4441-90b4-a28fa60e962f")], transition_costs=2339.5028721662775, transition_duration=Duration.from_timedelta(timedelta(milliseconds=35219)), abnormal_condition_only=True)], timers=[Timer(id=uuid.UUID("0c8a46de-b2bf-4adc-9052-0d2aa5dade66"), diagnostic_label="some-test-string8913", duration=Duration.from_timedelta(timedelta(milliseconds=20103)))])], storage=FRBCStorageDescription(diagnostic_label="some-test-string816", fill_level_label="some-test-string5484", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=16537.091716121224, end_of_range=19907.512143139258))) # Act json_str = frbc_system_description.to_json() # Assert - expected_json = { - "actuators": [ - { - "diagnostic_label": "some-test-string2728", - "id": "a1061148-f19e-4b1b-8fe3-b506583ce61e", - "operation_modes": [ - { - "abnormal_condition_only": False, - "diagnostic_label": "some-test-string2930", - "elements": [ - { - "fill_level_range": { - "end_of_range": 36932.65171036228, - "start_of_range": 12649.272766336762, - }, - "fill_rate": { - "end_of_range": 34553.16163528188, - "start_of_range": 14377.963894945604, - }, - "power_ranges": [ - { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 46924.65023353163, - "start_of_range": 11888.235871902496, - } - ], - "running_costs": { - "end_of_range": 42897.60731684277, - "start_of_range": 33997.56376994998, - }, - } - ], - "id": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - } - ], - "supported_commodities": ["ELECTRICITY"], - "timers": [ - { - "diagnostic_label": "some-test-string4315", - "duration": 14099, - "id": "e1ff9e58-935b-4765-92e3-5e7679f73eb6", - } - ], - "transitions": [ - { - "abnormal_condition_only": True, - "blocking_timers": ["e1ff9e58-935b-4765-92e3-5e7679f73eb6"], - "from": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "id": "c32cc1d3-4722-41e3-a8de-55307c723611", - "start_timers": ["e1ff9e58-935b-4765-92e3-5e7679f73eb6"], - "to": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "transition_costs": 1018.4228054114793, - "transition_duration": 11988, - } - ], - } - ], - "message_id": "97256813-de70-4640-a992-9ae0b2d8e4d1", - "message_type": "FRBC.SystemDescription", - "storage": { - "diagnostic_label": "some-test-string8418", - "fill_level_label": "some-test-string9512", - "fill_level_range": { - "end_of_range": 20876.752745956997, - "start_of_range": 18324.0229135081, - }, - "provides_fill_level_target_profile": False, - "provides_leakage_behaviour": True, - "provides_usage_forecast": False, - }, - "valid_from": "2020-10-07T06:30:55Z", - } + expected_json = { 'actuators': [ { 'diagnostic_label': 'some-test-string2832', + 'id': '9d5243b4-f92c-42cd-9d6b-00e3e5953dd4', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string2393', + 'elements': [ { 'fill_level_range': { 'end_of_range': 50141.518189827795, + 'start_of_range': 22530.41813108864}, + 'fill_rate': { 'end_of_range': 22111.530603779658, + 'start_of_range': 10937.461391570358}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 11155.631994233203, + 'start_of_range': 3372.255894277375}], + 'running_costs': { 'end_of_range': 38380.84063645549, + 'start_of_range': 31076.328103540887}}], + 'id': '4526bc26-cdd8-4cd6-ae35-dfb2a6751375'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string8913', + 'duration': 20103, + 'id': '0c8a46de-b2bf-4adc-9052-0d2aa5dade66'}], + 'transitions': [ { 'abnormal_condition_only': True, + 'blocking_timers': [ '2f690536-73d5-4441-90b4-a28fa60e962f'], + 'from_': 'cac664cf-bec3-49df-aeb4-d5dca13f3f0b', + 'id': '5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e', + 'start_timers': [ 'd2436620-dce4-43fe-a59d-a7bc3045a7cd'], + 'to': '61605548-7d68-4ceb-bbe2-b4d79d385b11', + 'transition_costs': 2339.5028721662775, + 'transition_duration': 35219}]}], + 'message_id': '873112de-a6d8-4123-a523-fc908e9a0310', + 'message_type': 'FRBC.SystemDescription', + 'storage': { 'diagnostic_label': 'some-test-string816', + 'fill_level_label': 'some-test-string5484', + 'fill_level_range': { 'end_of_range': 19907.512143139258, + 'start_of_range': 16537.091716121224}, + 'provides_fill_level_target_profile': True, + 'provides_leakage_behaviour': True, + 'provides_usage_forecast': True}, + 'valid_from': '2021-07-20T04:50:11+12:00'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_timer_status_test.py b/tests/unit/frbc/frbc_timer_status_test.py index 74e2924..e99176d 100644 --- a/tests/unit/frbc/frbc_timer_status_test.py +++ b/tests/unit/frbc/frbc_timer_status_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,11 +13,11 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "actuator_id": "f2e1f540-0235-429f-a45c-4d5cbe65d33f", - "finished_at": "2020-11-03T12:57:27+02:00", - "message_id": "57240f00-0b91-49bb-a4b0-2107d062faec", "message_type": "FRBC.TimerStatus", - "timer_id": "bcb8e64f-ea4c-4b92-b4cb-20026a13d663" + "message_id": "19c25c4f-1dd6-4a74-9e1c-54c09025844a", + "timer_id": "329b57fb-c4e8-41f4-922f-f27eb32214d5", + "actuator_id": "c24e5b81-0047-41db-9dba-1840c0a1aaca", + "finished_at": "2021-08-25T09:46:58-09:00" } """ @@ -24,59 +25,23 @@ def test__from_json__happy_path_full(self): frbc_timer_status = FRBCTimerStatus.from_json(json_str) # Assert - self.assertEqual( - frbc_timer_status.actuator_id, - uuid.UUID("f2e1f540-0235-429f-a45c-4d5cbe65d33f"), - ) - self.assertEqual( - frbc_timer_status.finished_at, - datetime( - year=2020, - month=11, - day=3, - hour=12, - minute=57, - second=27, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - ) - self.assertEqual( - frbc_timer_status.message_id, - uuid.UUID("57240f00-0b91-49bb-a4b0-2107d062faec"), - ) - self.assertEqual(frbc_timer_status.message_type, "FRBC.TimerStatus") - self.assertEqual( - frbc_timer_status.timer_id, - uuid.UUID("bcb8e64f-ea4c-4b92-b4cb-20026a13d663"), - ) + self.assertEqual(frbc_timer_status.message_type, FRBC.TimerStatus) + self.assertEqual(frbc_timer_status.message_id, uuid.UUID("19c25c4f-1dd6-4a74-9e1c-54c09025844a")) + self.assertEqual(frbc_timer_status.timer_id, uuid.UUID("329b57fb-c4e8-41f4-922f-f27eb32214d5")) + self.assertEqual(frbc_timer_status.actuator_id, uuid.UUID("c24e5b81-0047-41db-9dba-1840c0a1aaca")) + self.assertEqual(frbc_timer_status.finished_at, datetime(year=2021, month=8, day=25, hour=9, minute=46, second=58, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) def test__to_json__happy_path_full(self): # Arrange - frbc_timer_status = FRBCTimerStatus( - actuator_id=uuid.UUID("f2e1f540-0235-429f-a45c-4d5cbe65d33f"), - finished_at=datetime( - year=2020, - month=11, - day=3, - hour=12, - minute=57, - second=27, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - message_id=uuid.UUID("57240f00-0b91-49bb-a4b0-2107d062faec"), - message_type="FRBC.TimerStatus", - timer_id=uuid.UUID("bcb8e64f-ea4c-4b92-b4cb-20026a13d663"), - ) + frbc_timer_status = FRBCTimerStatus(message_type=FRBC.TimerStatus, message_id=uuid.UUID("19c25c4f-1dd6-4a74-9e1c-54c09025844a"), timer_id=uuid.UUID("329b57fb-c4e8-41f4-922f-f27eb32214d5"), actuator_id=uuid.UUID("c24e5b81-0047-41db-9dba-1840c0a1aaca"), finished_at=datetime(year=2021, month=8, day=25, hour=9, minute=46, second=58, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) # Act json_str = frbc_timer_status.to_json() # Assert - expected_json = { - "actuator_id": "f2e1f540-0235-429f-a45c-4d5cbe65d33f", - "finished_at": "2020-11-03T12:57:27+02:00", - "message_id": "57240f00-0b91-49bb-a4b0-2107d062faec", - "message_type": "FRBC.TimerStatus", - "timer_id": "bcb8e64f-ea4c-4b92-b4cb-20026a13d663", - } + expected_json = { 'actuator_id': 'c24e5b81-0047-41db-9dba-1840c0a1aaca', + 'finished_at': '2021-08-25T09:46:58-09:00', + 'message_id': '19c25c4f-1dd6-4a74-9e1c-54c09025844a', + 'message_type': 'FRBC.TimerStatus', + 'timer_id': '329b57fb-c4e8-41f4-922f-f27eb32214d5'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_element_test.py b/tests/unit/frbc/frbc_usage_forecast_element_test.py index 3df7f63..0eb44b1 100644 --- a/tests/unit/frbc/frbc_usage_forecast_element_test.py +++ b/tests/unit/frbc/frbc_usage_forecast_element_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,14 +13,14 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "duration": 9317, - "usage_rate_expected": 866.9362374046218, - "usage_rate_lower_68PPR": 3496.6233093198375, - "usage_rate_lower_95PPR": 4206.0536932975065, - "usage_rate_lower_limit": 7353.272756502293, - "usage_rate_upper_68PPR": 5124.8129813156465, - "usage_rate_upper_95PPR": 264.3386978845277, - "usage_rate_upper_limit": 4474.174577002476 + "duration": 3339, + "usage_rate_upper_limit": 5657.302338158246, + "usage_rate_upper_95PPR": 3774.615782357365, + "usage_rate_upper_68PPR": 8333.351165894339, + "usage_rate_expected": 8333.127007404517, + "usage_rate_lower_68PPR": 6418.649606433992, + "usage_rate_lower_95PPR": 3342.9603968663487, + "usage_rate_lower_limit": 8970.532671485054 } """ @@ -27,57 +28,29 @@ def test__from_json__happy_path_full(self): frbc_usage_forecast_element = FRBCUsageForecastElement.from_json(json_str) # Assert - self.assertEqual( - frbc_usage_forecast_element.duration, - Duration.from_timedelta(timedelta(milliseconds=9317)), - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_expected, 866.9362374046218 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_lower_68PPR, 3496.6233093198375 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_lower_95PPR, 4206.0536932975065 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_lower_limit, 7353.272756502293 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_upper_68PPR, 5124.8129813156465 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_upper_95PPR, 264.3386978845277 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_upper_limit, 4474.174577002476 - ) + self.assertEqual(frbc_usage_forecast_element.duration, Duration.from_timedelta(timedelta(milliseconds=3339))) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_limit, 5657.302338158246) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_95PPR, 3774.615782357365) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_68PPR, 8333.351165894339) + self.assertEqual(frbc_usage_forecast_element.usage_rate_expected, 8333.127007404517) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_68PPR, 6418.649606433992) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_95PPR, 3342.9603968663487) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_limit, 8970.532671485054) def test__to_json__happy_path_full(self): # Arrange - frbc_usage_forecast_element = FRBCUsageForecastElement( - duration=Duration.from_timedelta(timedelta(milliseconds=9317)), - usage_rate_expected=866.9362374046218, - usage_rate_lower_68PPR=3496.6233093198375, - usage_rate_lower_95PPR=4206.0536932975065, - usage_rate_lower_limit=7353.272756502293, - usage_rate_upper_68PPR=5124.8129813156465, - usage_rate_upper_95PPR=264.3386978845277, - usage_rate_upper_limit=4474.174577002476, - ) + frbc_usage_forecast_element = FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=3339)), usage_rate_upper_limit=5657.302338158246, usage_rate_upper_95PPR=3774.615782357365, usage_rate_upper_68PPR=8333.351165894339, usage_rate_expected=8333.127007404517, usage_rate_lower_68PPR=6418.649606433992, usage_rate_lower_95PPR=3342.9603968663487, usage_rate_lower_limit=8970.532671485054) # Act json_str = frbc_usage_forecast_element.to_json() # Assert - expected_json = { - "duration": 9317, - "usage_rate_expected": 866.9362374046218, - "usage_rate_lower_68PPR": 3496.6233093198375, - "usage_rate_lower_95PPR": 4206.0536932975065, - "usage_rate_lower_limit": 7353.272756502293, - "usage_rate_upper_68PPR": 5124.8129813156465, - "usage_rate_upper_95PPR": 264.3386978845277, - "usage_rate_upper_limit": 4474.174577002476, - } + expected_json = { 'duration': 3339, + 'usage_rate_expected': 8333.127007404517, + 'usage_rate_lower_68PPR': 6418.649606433992, + 'usage_rate_lower_95PPR': 3342.9603968663487, + 'usage_rate_lower_limit': 8970.532671485054, + 'usage_rate_upper_68PPR': 8333.351165894339, + 'usage_rate_upper_95PPR': 3774.615782357365, + 'usage_rate_upper_limit': 5657.302338158246} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_test.py b/tests/unit/frbc/frbc_usage_forecast_test.py index a7f0da1..98e06fd 100644 --- a/tests/unit/frbc/frbc_usage_forecast_test.py +++ b/tests/unit/frbc/frbc_usage_forecast_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,21 +13,21 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.UsageForecast", + "message_id": "88f742b5-ef8e-4af9-8ae4-633922859a8e", + "start_time": "2022-11-21T01:57:56-09:00", "elements": [ { - "duration": 14010, - "usage_rate_expected": 8032.572599815139, - "usage_rate_lower_68PPR": 3910.197692207213, - "usage_rate_lower_95PPR": 6541.633895752248, - "usage_rate_lower_limit": 3419.1709124422173, - "usage_rate_upper_68PPR": 7146.0702352976305, - "usage_rate_upper_95PPR": 627.7040858037238, - "usage_rate_upper_limit": 8477.800850190179 + "duration": 8336, + "usage_rate_upper_limit": 2600.090517037444, + "usage_rate_upper_95PPR": 5466.368905051084, + "usage_rate_upper_68PPR": 7874.9948212758245, + "usage_rate_expected": 1233.4751600765392, + "usage_rate_lower_68PPR": 1785.90944809586, + "usage_rate_lower_95PPR": 1047.0960233716157, + "usage_rate_lower_limit": 4680.219153555034 } - ], - "message_id": "4a91b4ab-21fb-42ae-b97d-6170f8b922cc", - "message_type": "FRBC.UsageForecast", - "start_time": "2023-03-25T13:48:35+02:00" + ] } """ @@ -34,86 +35,28 @@ def test__from_json__happy_path_full(self): frbc_usage_forecast = FRBCUsageForecast.from_json(json_str) # Assert - self.assertEqual( - frbc_usage_forecast.elements, - [ - FRBCUsageForecastElement( - duration=Duration.from_timedelta(timedelta(milliseconds=14010)), - usage_rate_expected=8032.572599815139, - usage_rate_lower_68PPR=3910.197692207213, - usage_rate_lower_95PPR=6541.633895752248, - usage_rate_lower_limit=3419.1709124422173, - usage_rate_upper_68PPR=7146.0702352976305, - usage_rate_upper_95PPR=627.7040858037238, - usage_rate_upper_limit=8477.800850190179, - ) - ], - ) - self.assertEqual( - frbc_usage_forecast.message_id, - uuid.UUID("4a91b4ab-21fb-42ae-b97d-6170f8b922cc"), - ) - self.assertEqual(frbc_usage_forecast.message_type, "FRBC.UsageForecast") - self.assertEqual( - frbc_usage_forecast.start_time, - datetime( - year=2023, - month=3, - day=25, - hour=13, - minute=48, - second=35, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - ) + self.assertEqual(frbc_usage_forecast.message_type, FRBC.UsageForecast) + self.assertEqual(frbc_usage_forecast.message_id, uuid.UUID("88f742b5-ef8e-4af9-8ae4-633922859a8e")) + self.assertEqual(frbc_usage_forecast.start_time, datetime(year=2022, month=11, day=21, hour=1, minute=57, second=56, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) + self.assertEqual(frbc_usage_forecast.elements, [FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=8336)), usage_rate_upper_limit=2600.090517037444, usage_rate_upper_95PPR=5466.368905051084, usage_rate_upper_68PPR=7874.9948212758245, usage_rate_expected=1233.4751600765392, usage_rate_lower_68PPR=1785.90944809586, usage_rate_lower_95PPR=1047.0960233716157, usage_rate_lower_limit=4680.219153555034)]) def test__to_json__happy_path_full(self): # Arrange - frbc_usage_forecast = FRBCUsageForecast( - elements=[ - FRBCUsageForecastElement( - duration=Duration.from_timedelta(timedelta(milliseconds=14010)), - usage_rate_expected=8032.572599815139, - usage_rate_lower_68PPR=3910.197692207213, - usage_rate_lower_95PPR=6541.633895752248, - usage_rate_lower_limit=3419.1709124422173, - usage_rate_upper_68PPR=7146.0702352976305, - usage_rate_upper_95PPR=627.7040858037238, - usage_rate_upper_limit=8477.800850190179, - ) - ], - message_id=uuid.UUID("4a91b4ab-21fb-42ae-b97d-6170f8b922cc"), - message_type="FRBC.UsageForecast", - start_time=datetime( - year=2023, - month=3, - day=25, - hour=13, - minute=48, - second=35, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - ) + frbc_usage_forecast = FRBCUsageForecast(message_type=FRBC.UsageForecast, message_id=uuid.UUID("88f742b5-ef8e-4af9-8ae4-633922859a8e"), start_time=datetime(year=2022, month=11, day=21, hour=1, minute=57, second=56, tzinfo=offset(offset=timedelta(seconds=-32400.0))), elements=[FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=8336)), usage_rate_upper_limit=2600.090517037444, usage_rate_upper_95PPR=5466.368905051084, usage_rate_upper_68PPR=7874.9948212758245, usage_rate_expected=1233.4751600765392, usage_rate_lower_68PPR=1785.90944809586, usage_rate_lower_95PPR=1047.0960233716157, usage_rate_lower_limit=4680.219153555034)]) # Act json_str = frbc_usage_forecast.to_json() # Assert - expected_json = { - "elements": [ - { - "duration": 14010, - "usage_rate_expected": 8032.572599815139, - "usage_rate_lower_68PPR": 3910.197692207213, - "usage_rate_lower_95PPR": 6541.633895752248, - "usage_rate_lower_limit": 3419.1709124422173, - "usage_rate_upper_68PPR": 7146.0702352976305, - "usage_rate_upper_95PPR": 627.7040858037238, - "usage_rate_upper_limit": 8477.800850190179, - } - ], - "message_id": "4a91b4ab-21fb-42ae-b97d-6170f8b922cc", - "message_type": "FRBC.UsageForecast", - "start_time": "2023-03-25T13:48:35+02:00", - } + expected_json = { 'elements': [ { 'duration': 8336, + 'usage_rate_expected': 1233.4751600765392, + 'usage_rate_lower_68PPR': 1785.90944809586, + 'usage_rate_lower_95PPR': 1047.0960233716157, + 'usage_rate_lower_limit': 4680.219153555034, + 'usage_rate_upper_68PPR': 7874.9948212758245, + 'usage_rate_upper_95PPR': 5466.368905051084, + 'usage_rate_upper_limit': 2600.090517037444}], + 'message_id': '88f742b5-ef8e-4af9-8ae4-633922859a8e', + 'message_type': 'FRBC.UsageForecast', + 'start_time': '2022-11-21T01:57:56-09:00'} self.assertEqual(json.loads(json_str), expected_json) From 56b83c3554e063308ca38a389c08fa76a6a2e039 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Thu, 19 Dec 2024 00:13:42 +0200 Subject: [PATCH 06/27] Removed the build files --- build/lib/s2python/__init__.py | 10 - build/lib/s2python/common/__init__.py | 32 - build/lib/s2python/common/duration.py | 22 - build/lib/s2python/common/handshake.py | 15 - .../lib/s2python/common/handshake_response.py | 15 - .../common/instruction_status_update.py | 18 - build/lib/s2python/common/number_range.py | 22 - build/lib/s2python/common/power_forecast.py | 18 - .../s2python/common/power_forecast_element.py | 20 - .../s2python/common/power_forecast_value.py | 11 - .../lib/s2python/common/power_measurement.py | 18 - build/lib/s2python/common/power_range.py | 22 - build/lib/s2python/common/power_value.py | 11 - build/lib/s2python/common/reception_status.py | 15 - .../common/resource_manager_details.py | 25 - build/lib/s2python/common/revoke_object.py | 16 - build/lib/s2python/common/role.py | 11 - .../s2python/common/select_control_type.py | 15 - build/lib/s2python/common/session_request.py | 15 - build/lib/s2python/common/support.py | 27 - build/lib/s2python/common/timer.py | 17 - build/lib/s2python/common/transition.py | 24 - build/lib/s2python/frbc/__init__.py | 17 - .../frbc/frbc_actuator_description.py | 143 -- .../lib/s2python/frbc/frbc_actuator_status.py | 23 - .../frbc/frbc_fill_level_target_profile.py | 24 - .../frbc_fill_level_target_profile_element.py | 34 - build/lib/s2python/frbc/frbc_instruction.py | 18 - .../s2python/frbc/frbc_leakage_behaviour.py | 20 - .../frbc/frbc_leakage_behaviour_element.py | 30 - .../lib/s2python/frbc/frbc_operation_mode.py | 42 - .../frbc/frbc_operation_mode_element.py | 27 - .../s2python/frbc/frbc_storage_description.py | 18 - .../lib/s2python/frbc/frbc_storage_status.py | 15 - .../s2python/frbc/frbc_system_description.py | 22 - build/lib/s2python/frbc/frbc_timer_status.py | 17 - .../lib/s2python/frbc/frbc_usage_forecast.py | 18 - .../frbc/frbc_usage_forecast_element.py | 17 - build/lib/s2python/frbc/rm.py | 0 build/lib/s2python/generated/__init__.py | 0 build/lib/s2python/generated/gen_s2.py | 1611 ----------------- build/lib/s2python/ppbc/__init__.py | 1 - .../ppbc/ppbc_schedule_instruction.py | 33 - build/lib/s2python/py.typed | 0 .../lib/s2python/reception_status_awaiter.py | 60 - build/lib/s2python/s2_connection.py | 526 ------ build/lib/s2python/s2_control_type.py | 76 - build/lib/s2python/s2_parser.py | 120 -- build/lib/s2python/s2_validation_error.py | 13 - build/lib/s2python/utils.py | 8 - build/lib/s2python/validate_values_mixin.py | 70 - build/lib/s2python/version.py | 3 - .../gen_unit_test_template.py | 5 + src/s2python/generated/gen_s2.py | 1012 +++++------ src/s2python/ppbc/__init__.py | 12 +- .../ppbc/ppbc_power_profile_definition.py | 1 + src/s2python/ppbc/ppbc_power_sequence.py | 4 +- .../ppbc_power_sequence_container_status.py | 6 +- .../ppbc/ppbc_power_sequence_element.py | 2 - .../frbc/frbc_actuator_description_test.py | 122 ++ tests/unit/frbc/frbc_actuator_status_test.py | 84 +- ..._fill_level_target_profile_element_test.py | 45 +- .../frbc_fill_level_target_profile_test.py | 91 +- tests/unit/frbc/frbc_instruction_test.py | 88 +- .../frbc_leakage_behaviour_element_test.py | 42 +- .../unit/frbc/frbc_leakage_behaviour_test.py | 91 +- .../frbc/frbc_operation_mode_element_test.py | 105 +- tests/unit/frbc/frbc_operation_mode_test.py | 125 +- .../frbc/frbc_storage_description_test.py | 67 +- tests/unit/frbc/frbc_storage_status_test.py | 28 +- .../unit/frbc/frbc_system_description_test.py | 380 +--- tests/unit/frbc/frbc_timer_status_test.py | 67 +- .../frbc/frbc_usage_forecast_element_test.py | 79 +- tests/unit/frbc/frbc_usage_forecast_test.py | 115 +- 74 files changed, 1005 insertions(+), 4971 deletions(-) delete mode 100644 build/lib/s2python/__init__.py delete mode 100644 build/lib/s2python/common/__init__.py delete mode 100644 build/lib/s2python/common/duration.py delete mode 100644 build/lib/s2python/common/handshake.py delete mode 100644 build/lib/s2python/common/handshake_response.py delete mode 100644 build/lib/s2python/common/instruction_status_update.py delete mode 100644 build/lib/s2python/common/number_range.py delete mode 100644 build/lib/s2python/common/power_forecast.py delete mode 100644 build/lib/s2python/common/power_forecast_element.py delete mode 100644 build/lib/s2python/common/power_forecast_value.py delete mode 100644 build/lib/s2python/common/power_measurement.py delete mode 100644 build/lib/s2python/common/power_range.py delete mode 100644 build/lib/s2python/common/power_value.py delete mode 100644 build/lib/s2python/common/reception_status.py delete mode 100644 build/lib/s2python/common/resource_manager_details.py delete mode 100644 build/lib/s2python/common/revoke_object.py delete mode 100644 build/lib/s2python/common/role.py delete mode 100644 build/lib/s2python/common/select_control_type.py delete mode 100644 build/lib/s2python/common/session_request.py delete mode 100644 build/lib/s2python/common/support.py delete mode 100644 build/lib/s2python/common/timer.py delete mode 100644 build/lib/s2python/common/transition.py delete mode 100644 build/lib/s2python/frbc/__init__.py delete mode 100644 build/lib/s2python/frbc/frbc_actuator_description.py delete mode 100644 build/lib/s2python/frbc/frbc_actuator_status.py delete mode 100644 build/lib/s2python/frbc/frbc_fill_level_target_profile.py delete mode 100644 build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py delete mode 100644 build/lib/s2python/frbc/frbc_instruction.py delete mode 100644 build/lib/s2python/frbc/frbc_leakage_behaviour.py delete mode 100644 build/lib/s2python/frbc/frbc_leakage_behaviour_element.py delete mode 100644 build/lib/s2python/frbc/frbc_operation_mode.py delete mode 100644 build/lib/s2python/frbc/frbc_operation_mode_element.py delete mode 100644 build/lib/s2python/frbc/frbc_storage_description.py delete mode 100644 build/lib/s2python/frbc/frbc_storage_status.py delete mode 100644 build/lib/s2python/frbc/frbc_system_description.py delete mode 100644 build/lib/s2python/frbc/frbc_timer_status.py delete mode 100644 build/lib/s2python/frbc/frbc_usage_forecast.py delete mode 100644 build/lib/s2python/frbc/frbc_usage_forecast_element.py delete mode 100644 build/lib/s2python/frbc/rm.py delete mode 100644 build/lib/s2python/generated/__init__.py delete mode 100644 build/lib/s2python/generated/gen_s2.py delete mode 100644 build/lib/s2python/ppbc/__init__.py delete mode 100644 build/lib/s2python/ppbc/ppbc_schedule_instruction.py delete mode 100644 build/lib/s2python/py.typed delete mode 100644 build/lib/s2python/reception_status_awaiter.py delete mode 100644 build/lib/s2python/s2_connection.py delete mode 100644 build/lib/s2python/s2_control_type.py delete mode 100644 build/lib/s2python/s2_parser.py delete mode 100644 build/lib/s2python/s2_validation_error.py delete mode 100644 build/lib/s2python/utils.py delete mode 100644 build/lib/s2python/validate_values_mixin.py delete mode 100644 build/lib/s2python/version.py create mode 100644 tests/unit/frbc/frbc_actuator_description_test.py diff --git a/build/lib/s2python/__init__.py b/build/lib/s2python/__init__.py deleted file mode 100644 index 0ab0a42..0000000 --- a/build/lib/s2python/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from importlib.metadata import PackageNotFoundError, version # pragma: no cover - -try: - # Change here if project is renamed and does not equal the package name - dist_name = "s2-python" # pylint: disable=invalid-name - __version__ = version(dist_name) -except PackageNotFoundError: # pragma: no cover - __version__ = "unknown" -finally: - del version, PackageNotFoundError diff --git a/build/lib/s2python/common/__init__.py b/build/lib/s2python/common/__init__.py deleted file mode 100644 index 806de7e..0000000 --- a/build/lib/s2python/common/__init__.py +++ /dev/null @@ -1,32 +0,0 @@ -from s2python.generated.gen_s2 import ( - RoleType, - Currency, - CommodityQuantity, - Commodity, - InstructionStatus, - ReceptionStatusValues, - EnergyManagementRole, - SessionRequestType, - ControlType, - RevokableObjects, -) - -from s2python.common.duration import Duration -from s2python.common.role import Role -from s2python.common.handshake import Handshake -from s2python.common.handshake_response import HandshakeResponse -from s2python.common.instruction_status_update import InstructionStatusUpdate -from s2python.common.number_range import NumberRange -from s2python.common.power_forecast_value import PowerForecastValue -from s2python.common.power_forecast_element import PowerForecastElement -from s2python.common.power_forecast import PowerForecast -from s2python.common.power_value import PowerValue -from s2python.common.power_measurement import PowerMeasurement -from s2python.common.power_range import PowerRange -from s2python.common.reception_status import ReceptionStatus -from s2python.common.resource_manager_details import ResourceManagerDetails -from s2python.common.revoke_object import RevokeObject -from s2python.common.select_control_type import SelectControlType -from s2python.common.session_request import SessionRequest -from s2python.common.timer import Timer -from s2python.common.transition import Transition diff --git a/build/lib/s2python/common/duration.py b/build/lib/s2python/common/duration.py deleted file mode 100644 index 65663c0..0000000 --- a/build/lib/s2python/common/duration.py +++ /dev/null @@ -1,22 +0,0 @@ -from datetime import timedelta -import math - -from s2python.generated.gen_s2 import Duration as GenDuration -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class Duration(GenDuration, S2Message["Duration"]): - def to_timedelta(self) -> timedelta: - return timedelta(milliseconds=self.root) - - @staticmethod - def from_timedelta(duration: timedelta) -> "Duration": - return Duration(root=math.ceil(duration.total_seconds() * 1000)) - - @staticmethod - def from_milliseconds(milliseconds: int) -> "Duration": - return Duration(root=milliseconds) diff --git a/build/lib/s2python/common/handshake.py b/build/lib/s2python/common/handshake.py deleted file mode 100644 index c068150..0000000 --- a/build/lib/s2python/common/handshake.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import Handshake as GenHandshake -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class Handshake(GenHandshake, S2Message["Handshake"]): - model_config = GenHandshake.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenHandshake.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/handshake_response.py b/build/lib/s2python/common/handshake_response.py deleted file mode 100644 index fcc2eb5..0000000 --- a/build/lib/s2python/common/handshake_response.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import HandshakeResponse as GenHandshakeResponse -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class HandshakeResponse(GenHandshakeResponse, S2Message["HandshakeResponse"]): - model_config = GenHandshakeResponse.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenHandshakeResponse.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/instruction_status_update.py b/build/lib/s2python/common/instruction_status_update.py deleted file mode 100644 index 5a8c45f..0000000 --- a/build/lib/s2python/common/instruction_status_update.py +++ /dev/null @@ -1,18 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import ( - InstructionStatusUpdate as GenInstructionStatusUpdate, -) -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class InstructionStatusUpdate(GenInstructionStatusUpdate, S2Message["InstructionStatusUpdate"]): - model_config = GenInstructionStatusUpdate.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenInstructionStatusUpdate.model_fields["message_id"] # type: ignore[assignment] - instruction_id: uuid.UUID = GenInstructionStatusUpdate.model_fields["instruction_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/number_range.py b/build/lib/s2python/common/number_range.py deleted file mode 100644 index 070b74a..0000000 --- a/build/lib/s2python/common/number_range.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Any - -from s2python.validate_values_mixin import S2Message, catch_and_convert_exceptions -from s2python.generated.gen_s2 import NumberRange as GenNumberRange - - -@catch_and_convert_exceptions -class NumberRange(GenNumberRange, S2Message["NumberRange"]): - model_config = GenNumberRange.model_config - model_config["validate_assignment"] = True - - def __hash__(self) -> int: - return hash(f"{self.start_of_range}|{self.end_of_range}") - - def __eq__(self, other: Any) -> bool: - if isinstance(other, NumberRange): - return ( - self.start_of_range == other.start_of_range - and self.end_of_range == other.end_of_range - ) - - return False diff --git a/build/lib/s2python/common/power_forecast.py b/build/lib/s2python/common/power_forecast.py deleted file mode 100644 index 31c595d..0000000 --- a/build/lib/s2python/common/power_forecast.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import List -import uuid - -from s2python.common.power_forecast_element import PowerForecastElement -from s2python.generated.gen_s2 import PowerForecast as GenPowerForecast -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class PowerForecast(GenPowerForecast, S2Message["PowerForecast"]): - model_config = GenPowerForecast.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenPowerForecast.model_fields["message_id"] # type: ignore[assignment] - elements: List[PowerForecastElement] = GenPowerForecast.model_fields["elements"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/power_forecast_element.py b/build/lib/s2python/common/power_forecast_element.py deleted file mode 100644 index 10460f7..0000000 --- a/build/lib/s2python/common/power_forecast_element.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import List - -from s2python.generated.gen_s2 import PowerForecastElement as GenPowerForecastElement -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) -from s2python.common.duration import Duration -from s2python.common.power_forecast_value import PowerForecastValue - - -@catch_and_convert_exceptions -class PowerForecastElement(GenPowerForecastElement, S2Message["PowerForecastElement"]): - model_config = GenPowerForecastElement.model_config - model_config["validate_assignment"] = True - - duration: Duration = GenPowerForecastElement.model_fields["duration"] # type: ignore[assignment] - power_values: List[PowerForecastValue] = GenPowerForecastElement.model_fields[ - "power_values" - ] # type: ignore[assignment] diff --git a/build/lib/s2python/common/power_forecast_value.py b/build/lib/s2python/common/power_forecast_value.py deleted file mode 100644 index 3ee2cc3..0000000 --- a/build/lib/s2python/common/power_forecast_value.py +++ /dev/null @@ -1,11 +0,0 @@ -from s2python.generated.gen_s2 import PowerForecastValue as GenPowerForecastValue -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class PowerForecastValue(GenPowerForecastValue, S2Message["PowerForecastValue"]): - model_config = GenPowerForecastValue.model_config - model_config["validate_assignment"] = True diff --git a/build/lib/s2python/common/power_measurement.py b/build/lib/s2python/common/power_measurement.py deleted file mode 100644 index 27896c9..0000000 --- a/build/lib/s2python/common/power_measurement.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import List -import uuid - -from s2python.common.power_value import PowerValue -from s2python.generated.gen_s2 import PowerMeasurement as GenPowerMeasurement -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class PowerMeasurement(GenPowerMeasurement, S2Message["PowerMeasurement"]): - model_config = GenPowerMeasurement.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenPowerMeasurement.model_fields["message_id"] # type: ignore[assignment] - values: List[PowerValue] = GenPowerMeasurement.model_fields["values"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/power_range.py b/build/lib/s2python/common/power_range.py deleted file mode 100644 index 4ca1ec8..0000000 --- a/build/lib/s2python/common/power_range.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing_extensions import Self - -from pydantic import model_validator - -from s2python.generated.gen_s2 import PowerRange as GenPowerRange -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) - - -@catch_and_convert_exceptions -class PowerRange(GenPowerRange, S2Message["PowerRange"]): - model_config = GenPowerRange.model_config - model_config["validate_assignment"] = True - - @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") - - return self diff --git a/build/lib/s2python/common/power_value.py b/build/lib/s2python/common/power_value.py deleted file mode 100644 index c623627..0000000 --- a/build/lib/s2python/common/power_value.py +++ /dev/null @@ -1,11 +0,0 @@ -from s2python.generated.gen_s2 import PowerValue as GenPowerValue -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class PowerValue(GenPowerValue, S2Message["PowerValue"]): - model_config = GenPowerValue.model_config - model_config["validate_assignment"] = True diff --git a/build/lib/s2python/common/reception_status.py b/build/lib/s2python/common/reception_status.py deleted file mode 100644 index a759897..0000000 --- a/build/lib/s2python/common/reception_status.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import ReceptionStatus as GenReceptionStatus -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class ReceptionStatus(GenReceptionStatus, S2Message["ReceptionStatus"]): - model_config = GenReceptionStatus.model_config - model_config["validate_assignment"] = True - - subject_message_id: uuid.UUID = GenReceptionStatus.model_fields["subject_message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/resource_manager_details.py b/build/lib/s2python/common/resource_manager_details.py deleted file mode 100644 index 82ce844..0000000 --- a/build/lib/s2python/common/resource_manager_details.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import List -import uuid - -from s2python.common.duration import Duration -from s2python.common.role import Role -from s2python.generated.gen_s2 import ( - ResourceManagerDetails as GenResourceManagerDetails, -) -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class ResourceManagerDetails(GenResourceManagerDetails, S2Message["ResourceManagerDetails"]): - model_config = GenResourceManagerDetails.model_config - model_config["validate_assignment"] = True - - instruction_processing_delay: Duration = GenResourceManagerDetails.model_fields[ - "instruction_processing_delay" - ] # type: ignore[assignment] - message_id: uuid.UUID = GenResourceManagerDetails.model_fields["message_id"] # type: ignore[assignment] - resource_id: uuid.UUID = GenResourceManagerDetails.model_fields["resource_id"] # type: ignore[assignment] - roles: List[Role] = GenResourceManagerDetails.model_fields["roles"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/revoke_object.py b/build/lib/s2python/common/revoke_object.py deleted file mode 100644 index d133c79..0000000 --- a/build/lib/s2python/common/revoke_object.py +++ /dev/null @@ -1,16 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import RevokeObject as GenRevokeObject -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class RevokeObject(GenRevokeObject, S2Message["RevokeObject"]): - model_config = GenRevokeObject.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenRevokeObject.model_fields["message_id"] # type: ignore[assignment] - object_id: uuid.UUID = GenRevokeObject.model_fields["object_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/role.py b/build/lib/s2python/common/role.py deleted file mode 100644 index 4a3d3ef..0000000 --- a/build/lib/s2python/common/role.py +++ /dev/null @@ -1,11 +0,0 @@ -from s2python.generated.gen_s2 import Role as GenRole -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) - - -@catch_and_convert_exceptions -class Role(GenRole, S2Message["Role"]): - model_config = GenRole.model_config - model_config["validate_assignment"] = True diff --git a/build/lib/s2python/common/select_control_type.py b/build/lib/s2python/common/select_control_type.py deleted file mode 100644 index 5f02954..0000000 --- a/build/lib/s2python/common/select_control_type.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import SelectControlType as GenSelectControlType -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class SelectControlType(GenSelectControlType, S2Message["SelectControlType"]): - model_config = GenSelectControlType.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenSelectControlType.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/session_request.py b/build/lib/s2python/common/session_request.py deleted file mode 100644 index f962427..0000000 --- a/build/lib/s2python/common/session_request.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import SessionRequest as GenSessionRequest -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class SessionRequest(GenSessionRequest, S2Message["SessionRequest"]): - model_config = GenSessionRequest.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenSessionRequest.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/support.py b/build/lib/s2python/common/support.py deleted file mode 100644 index 027f65b..0000000 --- a/build/lib/s2python/common/support.py +++ /dev/null @@ -1,27 +0,0 @@ -from s2python.common import CommodityQuantity, Commodity - - -def commodity_has_quantity(commodity: "Commodity", quantity: CommodityQuantity) -> bool: - if commodity == Commodity.HEAT: - result = quantity in [ - CommodityQuantity.HEAT_THERMAL_POWER, - CommodityQuantity.HEAT_TEMPERATURE, - CommodityQuantity.HEAT_FLOW_RATE, - ] - elif commodity == Commodity.ELECTRICITY: - result = quantity in [ - CommodityQuantity.ELECTRIC_POWER_3_PHASE_SYMMETRIC, - CommodityQuantity.ELECTRIC_POWER_L1, - CommodityQuantity.ELECTRIC_POWER_L2, - CommodityQuantity.ELECTRIC_POWER_L3, - ] - elif commodity == Commodity.GAS: - result = quantity in [CommodityQuantity.NATURAL_GAS_FLOW_RATE] - elif commodity == Commodity.OIL: - result = quantity in [CommodityQuantity.OIL_FLOW_RATE] - else: - raise RuntimeError( - f"Unsupported commodity {commodity}. Missing implementation." - ) - - return result diff --git a/build/lib/s2python/common/timer.py b/build/lib/s2python/common/timer.py deleted file mode 100644 index 3811082..0000000 --- a/build/lib/s2python/common/timer.py +++ /dev/null @@ -1,17 +0,0 @@ -import uuid - -from s2python.common.duration import Duration -from s2python.generated.gen_s2 import Timer as GenTimer -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) - - -@catch_and_convert_exceptions -class Timer(GenTimer, S2Message["Timer"]): - model_config = GenTimer.model_config - model_config["validate_assignment"] = True - - id: uuid.UUID = GenTimer.model_fields["id"] # type: ignore[assignment] - duration: Duration = GenTimer.model_fields["duration"] # type: ignore[assignment] diff --git a/build/lib/s2python/common/transition.py b/build/lib/s2python/common/transition.py deleted file mode 100644 index e1e1a25..0000000 --- a/build/lib/s2python/common/transition.py +++ /dev/null @@ -1,24 +0,0 @@ -import uuid -from typing import Optional, List - -from s2python.common.duration import Duration -from s2python.generated.gen_s2 import Transition as GenTransition -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) - - -@catch_and_convert_exceptions -class Transition(GenTransition, S2Message["Transition"]): - model_config = GenTransition.model_config - model_config["validate_assignment"] = True - - id: uuid.UUID = GenTransition.model_fields["id"] # type: ignore[assignment] - from_: uuid.UUID = GenTransition.model_fields["from_"] # type: ignore[assignment] - to: uuid.UUID = GenTransition.model_fields["to"] # type: ignore[assignment] - start_timers: List[uuid.UUID] = GenTransition.model_fields["start_timers"] # type: ignore[assignment] - blocking_timers: List[uuid.UUID] = GenTransition.model_fields["blocking_timers"] # type: ignore[assignment] - transition_duration: Optional[Duration] = GenTransition.model_fields[ - "transition_duration" - ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/__init__.py b/build/lib/s2python/frbc/__init__.py deleted file mode 100644 index da3d5bc..0000000 --- a/build/lib/s2python/frbc/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -from s2python.frbc.frbc_fill_level_target_profile_element import ( - FRBCFillLevelTargetProfileElement, -) -from s2python.frbc.frbc_fill_level_target_profile import FRBCFillLevelTargetProfile -from s2python.frbc.frbc_instruction import FRBCInstruction -from s2python.frbc.frbc_leakage_behaviour_element import FRBCLeakageBehaviourElement -from s2python.frbc.frbc_leakage_behaviour import FRBCLeakageBehaviour -from s2python.frbc.frbc_usage_forecast_element import FRBCUsageForecastElement -from s2python.frbc.frbc_usage_forecast import FRBCUsageForecast -from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement -from s2python.frbc.frbc_operation_mode import FRBCOperationMode -from s2python.frbc.frbc_actuator_description import FRBCActuatorDescription -from s2python.frbc.frbc_actuator_status import FRBCActuatorStatus -from s2python.frbc.frbc_storage_description import FRBCStorageDescription -from s2python.frbc.frbc_storage_status import FRBCStorageStatus -from s2python.frbc.frbc_system_description import FRBCSystemDescription -from s2python.frbc.frbc_timer_status import FRBCTimerStatus diff --git a/build/lib/s2python/frbc/frbc_actuator_description.py b/build/lib/s2python/frbc/frbc_actuator_description.py deleted file mode 100644 index 08afce6..0000000 --- a/build/lib/s2python/frbc/frbc_actuator_description.py +++ /dev/null @@ -1,143 +0,0 @@ -import uuid - -from typing import List -from typing_extensions import Self - -from pydantic import model_validator - -from s2python.common import Transition, Timer, Commodity -from s2python.common.support import commodity_has_quantity -from s2python.frbc.frbc_operation_mode import FRBCOperationMode -from s2python.generated.gen_s2 import ( - FRBCActuatorDescription as GenFRBCActuatorDescription, -) -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) - - -@catch_and_convert_exceptions -class FRBCActuatorDescription(GenFRBCActuatorDescription, S2Message["FRBCActuatorDescription"]): - model_config = GenFRBCActuatorDescription.model_config - model_config["validate_assignment"] = True - - id: uuid.UUID = GenFRBCActuatorDescription.model_fields["id"] # type: ignore[assignment] - operation_modes: List[FRBCOperationMode] = GenFRBCActuatorDescription.model_fields[ - "operation_modes" - ] # type: ignore[assignment] - transitions: List[Transition] = GenFRBCActuatorDescription.model_fields["transitions"] # type: ignore[assignment] - timers: List[Timer] = GenFRBCActuatorDescription.model_fields["timers"] # type: ignore[assignment] - supported_commodities: List[Commodity] = GenFRBCActuatorDescription.model_fields[ - "supported_commodities" - ] # type: ignore[assignment] - - @model_validator(mode="after") - def validate_timers_in_transitions(self) -> Self: - timers_by_id = {timer.id: timer for timer in self.timers} - transition: Transition - for transition in self.transitions: - for start_timer_id in transition.start_timers: - if start_timer_id not in timers_by_id: - raise ValueError( - self, - f"{start_timer_id} was referenced as start timer in transition " - f"{transition.id} but was not defined in 'timers'.", - ) - - for blocking_timer_id in transition.blocking_timers: - if blocking_timer_id not in timers_by_id: - raise ValueError( - self, - f"{blocking_timer_id} was referenced as blocking timer in transition " - f"{transition.id} but was not defined in 'timers'.", - ) - - return self - - @model_validator(mode="after") - def validate_timers_unique_ids(self) -> Self: - ids = [] - 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'.") - ids.append(timer.id) - - return self - - @model_validator(mode="after") - def validate_operation_modes_in_transitions(self) -> Self: - operation_mode_by_id = {operation_mode.id: operation_mode for operation_mode in self.operation_modes} - transition: Transition - for transition in self.transitions: - if transition.from_ not in operation_mode_by_id: - raise ValueError( - self, - f"Operation mode {transition.from_} was referenced as 'from' in transition " - f"{transition.id} but was not defined in 'operation_modes'.", - ) - - if transition.to not in operation_mode_by_id: - raise ValueError( - self, - f"Operation mode {transition.to} was referenced as 'to' in transition " - f"{transition.id} but was not defined in 'operation_modes'.", - ) - - return self - - @model_validator(mode="after") - def validate_operation_modes_unique_ids(self) -> Self: - ids = [] - operation_mode: FRBCOperationMode - for operation_mode in self.operation_modes: - if operation_mode.id in ids: - raise ValueError( - self, - f"Id {operation_mode.id} was found multiple times in 'operation_modes'.", - ) - ids.append(operation_mode.id) - - return self - - @model_validator(mode="after") - def validate_operation_mode_elements_have_all_supported_commodities(self) -> Self: - supported_commodities = self.supported_commodities - operation_mode: FRBCOperationMode - for operation_mode in self.operation_modes: - for operation_mode_element in operation_mode.elements: - for commodity in supported_commodities: - 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 len(power_ranges_for_commodity) > 1: - raise ValueError( - self, - f"Multiple power ranges defined for commodity {commodity} in operation " - f"mode {operation_mode.id} and element with fill_level_range " - f"{operation_mode_element.fill_level_range}", - ) - if not power_ranges_for_commodity: - raise ValueError( - self, - f"No power ranges defined for commodity {commodity} in operation " - f"mode {operation_mode.id} and element with fill_level_range " - f"{operation_mode_element.fill_level_range}", - ) - return self - - @model_validator(mode="after") - def validate_unique_supported_commodities(self) -> Self: - supported_commodities: List[Commodity] = self.supported_commodities - - for supported_commodity in supported_commodities: - if supported_commodities.count(supported_commodity) > 1: - raise ValueError( - self, - f"Found duplicate {supported_commodity} commodity in 'supported_commodities'", - ) - return self diff --git a/build/lib/s2python/frbc/frbc_actuator_status.py b/build/lib/s2python/frbc/frbc_actuator_status.py deleted file mode 100644 index 585a23d..0000000 --- a/build/lib/s2python/frbc/frbc_actuator_status.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional -import uuid - -from s2python.generated.gen_s2 import FRBCActuatorStatus as GenFRBCActuatorStatus -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCActuatorStatus(GenFRBCActuatorStatus, S2Message["FRBCActuatorStatus"]): - model_config = GenFRBCActuatorStatus.model_config - model_config["validate_assignment"] = True - - active_operation_mode_id: uuid.UUID = GenFRBCActuatorStatus.model_fields[ - "active_operation_mode_id" - ] # type: ignore[assignment] - actuator_id: uuid.UUID = GenFRBCActuatorStatus.model_fields["actuator_id"] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCActuatorStatus.model_fields["message_id"] # type: ignore[assignment] - previous_operation_mode_id: Optional[uuid.UUID] = GenFRBCActuatorStatus.model_fields[ - "previous_operation_mode_id" - ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_fill_level_target_profile.py b/build/lib/s2python/frbc/frbc_fill_level_target_profile.py deleted file mode 100644 index 38ef83b..0000000 --- a/build/lib/s2python/frbc/frbc_fill_level_target_profile.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import List -import uuid - -from s2python.frbc.frbc_fill_level_target_profile_element import ( - FRBCFillLevelTargetProfileElement, -) -from s2python.generated.gen_s2 import ( - FRBCFillLevelTargetProfile as GenFRBCFillLevelTargetProfile, -) -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCFillLevelTargetProfile(GenFRBCFillLevelTargetProfile, S2Message["FRBCFillLevelTargetProfile"]): - model_config = GenFRBCFillLevelTargetProfile.model_config - model_config["validate_assignment"] = True - - elements: List[FRBCFillLevelTargetProfileElement] = GenFRBCFillLevelTargetProfile.model_fields[ - "elements" - ] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCFillLevelTargetProfile.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py b/build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py deleted file mode 100644 index cdb7d84..0000000 --- a/build/lib/s2python/frbc/frbc_fill_level_target_profile_element.py +++ /dev/null @@ -1,34 +0,0 @@ -# pylint: disable=duplicate-code - -from typing_extensions import Self - -from pydantic import model_validator - -from s2python.common import Duration, NumberRange -from s2python.generated.gen_s2 import ( - FRBCFillLevelTargetProfileElement as GenFRBCFillLevelTargetProfileElement, -) -from s2python.validate_values_mixin import catch_and_convert_exceptions, S2Message - - -@catch_and_convert_exceptions -class FRBCFillLevelTargetProfileElement( - GenFRBCFillLevelTargetProfileElement, S2Message["FRBCFillLevelTargetProfileElement"] -): - model_config = GenFRBCFillLevelTargetProfileElement.model_config - model_config["validate_assignment"] = True - - duration: Duration = GenFRBCFillLevelTargetProfileElement.model_fields["duration"] # type: ignore[assignment] - fill_level_range: NumberRange = GenFRBCFillLevelTargetProfileElement.model_fields[ - "fill_level_range" - ] # type: ignore[assignment] - - @model_validator(mode="after") - def validate_start_end_order(self) -> Self: - if self.fill_level_range.start_of_range > self.fill_level_range.end_of_range: - raise ValueError( - self, - "start_of_range should not be higher than end_of_range for the fill_level_range", - ) - - return self diff --git a/build/lib/s2python/frbc/frbc_instruction.py b/build/lib/s2python/frbc/frbc_instruction.py deleted file mode 100644 index 584cfba..0000000 --- a/build/lib/s2python/frbc/frbc_instruction.py +++ /dev/null @@ -1,18 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import FRBCInstruction as GenFRBCInstruction -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCInstruction(GenFRBCInstruction, S2Message["FRBCInstruction"]): - model_config = GenFRBCInstruction.model_config - model_config["validate_assignment"] = True - - actuator_id: uuid.UUID = GenFRBCInstruction.model_fields["actuator_id"] # type: ignore[assignment] - id: uuid.UUID = GenFRBCInstruction.model_fields["id"] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCInstruction.model_fields["message_id"] # type: ignore[assignment] - operation_mode: uuid.UUID = GenFRBCInstruction.model_fields["operation_mode"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_leakage_behaviour.py b/build/lib/s2python/frbc/frbc_leakage_behaviour.py deleted file mode 100644 index fda7d3b..0000000 --- a/build/lib/s2python/frbc/frbc_leakage_behaviour.py +++ /dev/null @@ -1,20 +0,0 @@ -from typing import List -import uuid - -from s2python.frbc.frbc_leakage_behaviour_element import FRBCLeakageBehaviourElement -from s2python.generated.gen_s2 import FRBCLeakageBehaviour as GenFRBCLeakageBehaviour -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCLeakageBehaviour(GenFRBCLeakageBehaviour, S2Message["FRBCLeakageBehaviour"]): - model_config = GenFRBCLeakageBehaviour.model_config - model_config["validate_assignment"] = True - - elements: List[FRBCLeakageBehaviourElement] = GenFRBCLeakageBehaviour.model_fields[ - "elements" - ] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCLeakageBehaviour.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_leakage_behaviour_element.py b/build/lib/s2python/frbc/frbc_leakage_behaviour_element.py deleted file mode 100644 index b9ca2eb..0000000 --- a/build/lib/s2python/frbc/frbc_leakage_behaviour_element.py +++ /dev/null @@ -1,30 +0,0 @@ -# pylint: disable=duplicate-code - -from pydantic import model_validator -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, S2Message - - -@catch_and_convert_exceptions -class FRBCLeakageBehaviourElement( - GenFRBCLeakageBehaviourElement, S2Message["FRBCLeakageBehaviourElement"] -): - model_config = GenFRBCLeakageBehaviourElement.model_config - model_config["validate_assignment"] = True - - fill_level_range: NumberRange = GenFRBCLeakageBehaviourElement.model_fields[ - "fill_level_range" - ] # type: ignore[assignment] - - @model_validator(mode="after") - def validate_start_end_order(self) -> Self: - if self.fill_level_range.start_of_range > self.fill_level_range.end_of_range: - raise ValueError( - self, - "start_of_range should not be higher than end_of_range for the fill_level_range", - ) - - return self diff --git a/build/lib/s2python/frbc/frbc_operation_mode.py b/build/lib/s2python/frbc/frbc_operation_mode.py deleted file mode 100644 index c6758ad..0000000 --- a/build/lib/s2python/frbc/frbc_operation_mode.py +++ /dev/null @@ -1,42 +0,0 @@ -# from itertools import pairwise -import uuid -from typing import List, Dict -from typing_extensions import Self - -from pydantic import model_validator - -from s2python.common import NumberRange -from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement -from s2python.generated.gen_s2 import FRBCOperationMode as GenFRBCOperationMode -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) -from s2python.utils import pairwise - - -@catch_and_convert_exceptions -class FRBCOperationMode(GenFRBCOperationMode, S2Message["FRBCOperationMode"]): - model_config = GenFRBCOperationMode.model_config - model_config["validate_assignment"] = True - - id: uuid.UUID = GenFRBCOperationMode.model_fields["id"] # type: ignore[assignment] - elements: List[FRBCOperationModeElement] = GenFRBCOperationMode.model_fields["elements"] # type: ignore[assignment] - - @model_validator(mode="after") - def validate_contiguous_fill_levels_operation_mode_elements(self) -> Self: - elements_by_fill_level_range: Dict[NumberRange, FRBCOperationModeElement] - elements_by_fill_level_range = {element.fill_level_range: element for element in self.elements} - - sorted_fill_level_ranges: List[NumberRange] - 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: - raise ValueError( - self, - f"Elements with fill level ranges {current_fill_level_range} and " - f"{next_fill_level_range} are closest match to each other but not contiguous.", - ) - return self diff --git a/build/lib/s2python/frbc/frbc_operation_mode_element.py b/build/lib/s2python/frbc/frbc_operation_mode_element.py deleted file mode 100644 index d154d11..0000000 --- a/build/lib/s2python/frbc/frbc_operation_mode_element.py +++ /dev/null @@ -1,27 +0,0 @@ -from typing import Optional, List - -from s2python.common import NumberRange, PowerRange -from s2python.generated.gen_s2 import ( - FRBCOperationModeElement as GenFRBCOperationModeElement, -) -from s2python.validate_values_mixin import ( - S2Message, - catch_and_convert_exceptions, -) - - -@catch_and_convert_exceptions -class FRBCOperationModeElement(GenFRBCOperationModeElement, S2Message["FRBCOperationModeElement"]): - model_config = GenFRBCOperationModeElement.model_config - model_config["validate_assignment"] = True - - fill_level_range: NumberRange = GenFRBCOperationModeElement.model_fields[ - "fill_level_range" - ] # type: ignore[assignment] - fill_rate: NumberRange = GenFRBCOperationModeElement.model_fields["fill_rate"] # type: ignore[assignment] - power_ranges: List[PowerRange] = GenFRBCOperationModeElement.model_fields[ - "power_ranges" - ] # type: ignore[assignment] - running_costs: Optional[NumberRange] = GenFRBCOperationModeElement.model_fields[ - "running_costs" - ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_storage_description.py b/build/lib/s2python/frbc/frbc_storage_description.py deleted file mode 100644 index eb141b8..0000000 --- a/build/lib/s2python/frbc/frbc_storage_description.py +++ /dev/null @@ -1,18 +0,0 @@ -from s2python.common import NumberRange -from s2python.generated.gen_s2 import ( - FRBCStorageDescription as GenFRBCStorageDescription, -) -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCStorageDescription(GenFRBCStorageDescription, S2Message["FRBCStorageDescription"]): - model_config = GenFRBCStorageDescription.model_config - model_config["validate_assignment"] = True - - fill_level_range: NumberRange = GenFRBCStorageDescription.model_fields[ - "fill_level_range" - ] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_storage_status.py b/build/lib/s2python/frbc/frbc_storage_status.py deleted file mode 100644 index 7940b79..0000000 --- a/build/lib/s2python/frbc/frbc_storage_status.py +++ /dev/null @@ -1,15 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import FRBCStorageStatus as GenFRBCStorageStatus -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCStorageStatus(GenFRBCStorageStatus, S2Message["FRBCStorageStatus"]): - model_config = GenFRBCStorageStatus.model_config - model_config["validate_assignment"] = True - - message_id: uuid.UUID = GenFRBCStorageStatus.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_system_description.py b/build/lib/s2python/frbc/frbc_system_description.py deleted file mode 100644 index 2eb5899..0000000 --- a/build/lib/s2python/frbc/frbc_system_description.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import List -import uuid - -from s2python.generated.gen_s2 import FRBCSystemDescription as GenFRBCSystemDescription -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) -from s2python.frbc.frbc_actuator_description import FRBCActuatorDescription -from s2python.frbc.frbc_storage_description import FRBCStorageDescription - - -@catch_and_convert_exceptions -class FRBCSystemDescription(GenFRBCSystemDescription, S2Message["FRBCSystemDescription"]): - model_config = GenFRBCSystemDescription.model_config - model_config["validate_assignment"] = True - - actuators: List[FRBCActuatorDescription] = GenFRBCSystemDescription.model_fields[ - "actuators" - ] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCSystemDescription.model_fields["message_id"] # type: ignore[assignment] - storage: FRBCStorageDescription = GenFRBCSystemDescription.model_fields["storage"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_timer_status.py b/build/lib/s2python/frbc/frbc_timer_status.py deleted file mode 100644 index 80c86d6..0000000 --- a/build/lib/s2python/frbc/frbc_timer_status.py +++ /dev/null @@ -1,17 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import FRBCTimerStatus as GenFRBCTimerStatus -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCTimerStatus(GenFRBCTimerStatus, S2Message["FRBCTimerStatus"]): - model_config = GenFRBCTimerStatus.model_config - model_config["validate_assignment"] = True - - actuator_id: uuid.UUID = GenFRBCTimerStatus.model_fields["actuator_id"] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCTimerStatus.model_fields["message_id"] # type: ignore[assignment] - timer_id: uuid.UUID = GenFRBCTimerStatus.model_fields["timer_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_usage_forecast.py b/build/lib/s2python/frbc/frbc_usage_forecast.py deleted file mode 100644 index f71fda4..0000000 --- a/build/lib/s2python/frbc/frbc_usage_forecast.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import List -import uuid - -from s2python.generated.gen_s2 import FRBCUsageForecast as GenFRBCUsageForecast -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) -from s2python.frbc.frbc_usage_forecast_element import FRBCUsageForecastElement - - -@catch_and_convert_exceptions -class FRBCUsageForecast(GenFRBCUsageForecast, S2Message["FRBCUsageForecast"]): - model_config = GenFRBCUsageForecast.model_config - model_config["validate_assignment"] = True - - elements: List[FRBCUsageForecastElement] = GenFRBCUsageForecast.model_fields["elements"] # type: ignore[assignment] - message_id: uuid.UUID = GenFRBCUsageForecast.model_fields["message_id"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/frbc_usage_forecast_element.py b/build/lib/s2python/frbc/frbc_usage_forecast_element.py deleted file mode 100644 index 370c04e..0000000 --- a/build/lib/s2python/frbc/frbc_usage_forecast_element.py +++ /dev/null @@ -1,17 +0,0 @@ -from s2python.common import Duration - -from s2python.generated.gen_s2 import ( - FRBCUsageForecastElement as GenFRBCUsageForecastElement, -) -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class FRBCUsageForecastElement(GenFRBCUsageForecastElement, S2Message["FRBCUsageForecastElement"]): - model_config = GenFRBCUsageForecastElement.model_config - model_config["validate_assignment"] = True - - duration: Duration = GenFRBCUsageForecastElement.model_fields["duration"] # type: ignore[assignment] diff --git a/build/lib/s2python/frbc/rm.py b/build/lib/s2python/frbc/rm.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/s2python/generated/__init__.py b/build/lib/s2python/generated/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/s2python/generated/gen_s2.py b/build/lib/s2python/generated/gen_s2.py deleted file mode 100644 index c7febd6..0000000 --- a/build/lib/s2python/generated/gen_s2.py +++ /dev/null @@ -1,1611 +0,0 @@ -# generated by datamodel-codegen: -# filename: openapi.yml -# timestamp: 2024-07-29T10:18:52+00:00 - -from __future__ import annotations - -from enum import Enum -from typing import List, Optional - -from pydantic import ( - AwareDatetime, - BaseModel, - ConfigDict, - Field, - RootModel, - conint, - constr, -) -from typing_extensions import Literal - - -class Duration(RootModel[conint(ge=0)]): - root: conint(ge=0) = Field(..., description='Duration in milliseconds') - - -class ID(RootModel[constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}')]): - root: constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') - - -class Currency(Enum): - AED = 'AED' - ANG = 'ANG' - AUD = 'AUD' - CHE = 'CHE' - CHF = 'CHF' - CHW = 'CHW' - EUR = 'EUR' - GBP = 'GBP' - LBP = 'LBP' - LKR = 'LKR' - LRD = 'LRD' - LSL = 'LSL' - LYD = 'LYD' - MAD = 'MAD' - MDL = 'MDL' - MGA = 'MGA' - MKD = 'MKD' - MMK = 'MMK' - MNT = 'MNT' - MOP = 'MOP' - MRO = 'MRO' - MUR = 'MUR' - MVR = 'MVR' - MWK = 'MWK' - MXN = 'MXN' - MXV = 'MXV' - MYR = 'MYR' - MZN = 'MZN' - NAD = 'NAD' - NGN = 'NGN' - NIO = 'NIO' - NOK = 'NOK' - NPR = 'NPR' - NZD = 'NZD' - OMR = 'OMR' - PAB = 'PAB' - PEN = 'PEN' - PGK = 'PGK' - PHP = 'PHP' - PKR = 'PKR' - PLN = 'PLN' - PYG = 'PYG' - QAR = 'QAR' - RON = 'RON' - RSD = 'RSD' - RUB = 'RUB' - RWF = 'RWF' - SAR = 'SAR' - SBD = 'SBD' - SCR = 'SCR' - SDG = 'SDG' - SEK = 'SEK' - SGD = 'SGD' - SHP = 'SHP' - SLL = 'SLL' - SOS = 'SOS' - SRD = 'SRD' - SSP = 'SSP' - STD = 'STD' - SYP = 'SYP' - SZL = 'SZL' - THB = 'THB' - TJS = 'TJS' - TMT = 'TMT' - TND = 'TND' - TOP = 'TOP' - TRY = 'TRY' - TTD = 'TTD' - TWD = 'TWD' - TZS = 'TZS' - UAH = 'UAH' - UGX = 'UGX' - USD = 'USD' - USN = 'USN' - UYI = 'UYI' - UYU = 'UYU' - UZS = 'UZS' - VEF = 'VEF' - VND = 'VND' - VUV = 'VUV' - WST = 'WST' - XAG = 'XAG' - XAU = 'XAU' - XBA = 'XBA' - XBB = 'XBB' - XBC = 'XBC' - XBD = 'XBD' - XCD = 'XCD' - XOF = 'XOF' - XPD = 'XPD' - XPF = 'XPF' - XPT = 'XPT' - XSU = 'XSU' - XTS = 'XTS' - XUA = 'XUA' - XXX = 'XXX' - YER = 'YER' - ZAR = 'ZAR' - ZMW = 'ZMW' - ZWL = 'ZWL' - - -class SessionRequestType(Enum): - RECONNECT = 'RECONNECT' - TERMINATE = 'TERMINATE' - - -class RevokableObjects(Enum): - PEBC_PowerConstraints = 'PEBC.PowerConstraints' - PEBC_EnergyConstraint = 'PEBC.EnergyConstraint' - PEBC_Instruction = 'PEBC.Instruction' - PPBC_PowerProfileDefinition = 'PPBC.PowerProfileDefinition' - PPBC_ScheduleInstruction = 'PPBC.ScheduleInstruction' - PPBC_StartInterruptionInstruction = 'PPBC.StartInterruptionInstruction' - PPBC_EndInterruptionInstruction = 'PPBC.EndInterruptionInstruction' - OMBC_SystemDescription = 'OMBC.SystemDescription' - OMBC_Instruction = 'OMBC.Instruction' - FRBC_SystemDescription = 'FRBC.SystemDescription' - FRBC_Instruction = 'FRBC.Instruction' - DDBC_SystemDescription = 'DDBC.SystemDescription' - DDBC_Instruction = 'DDBC.Instruction' - - -class EnergyManagementRole(Enum): - CEM = 'CEM' - RM = 'RM' - - -class ReceptionStatusValues(Enum): - INVALID_DATA = 'INVALID_DATA' - INVALID_MESSAGE = 'INVALID_MESSAGE' - INVALID_CONTENT = 'INVALID_CONTENT' - TEMPORARY_ERROR = 'TEMPORARY_ERROR' - PERMANENT_ERROR = 'PERMANENT_ERROR' - OK = 'OK' - - -class NumberRange(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start_of_range: float = Field( - ..., description='Number that defines the start of the range' - ) - end_of_range: float = Field( - ..., description='Number that defines the end of the range' - ) - - -class Transition(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', - ) - from_: ID = Field( - ..., - alias='from', - description='ID of the OperationMode (exact type differs per ControlType) that should be switched from.', - ) - to: ID = Field( - ..., - description='ID of the OperationMode (exact type differs per ControlType) that will be switched to.', - ) - start_timers: List[ID] = Field( - ..., - description='List of IDs of Timers that will be (re)started when this transition is initiated', - max_length=1000, - min_length=0, - ) - blocking_timers: List[ID] = Field( - ..., - description='List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished', - max_length=1000, - min_length=0, - ) - transition_costs: Optional[float] = Field( - None, - description='Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.', - ) - transition_duration: Optional[Duration] = Field( - None, - description='Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.', - ) - abnormal_condition_only: bool = Field( - ..., - description='Indicates if this Transition may only be used during an abnormal condition (see Clause )', - ) - - -class Timer(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.', - ) - duration: Duration = Field( - ..., - description='The time it takes for the Timer to finish after it has been started', - ) - - -class PEBCPowerEnvelopeElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - duration: Duration = Field(..., description='The duration of the element') - upper_limit: float = Field( - ..., - description='Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.', - ) - lower_limit: float = Field( - ..., - description='Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.', - ) - - -class FRBCStorageDescription(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.', - ) - fill_level_label: Optional[str] = Field( - None, - description='Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.', - ) - provides_leakage_behaviour: bool = Field( - ..., - description='Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.', - ) - provides_fill_level_target_profile: bool = Field( - ..., - description='Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.', - ) - provides_usage_forecast: bool = Field( - ..., - description='Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.', - ) - fill_level_range: NumberRange = Field( - ..., - description='The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ', - ) - - -class FRBCLeakageBehaviourElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - fill_level_range: NumberRange = Field( - ..., - description='The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.', - ) - leakage_rate: float = Field( - ..., - description='Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.', - ) - - -class FRBCUsageForecastElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - duration: Duration = Field( - ..., description='Indicator for how long the given usage_rate is valid.' - ) - usage_rate_upper_limit: Optional[float] = Field( - None, - description='The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', - ) - usage_rate_upper_95PPR: Optional[float] = Field( - None, - description='The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', - ) - usage_rate_upper_68PPR: Optional[float] = Field( - None, - description='The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', - ) - usage_rate_expected: float = Field( - ..., - description='The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.', - ) - usage_rate_lower_68PPR: Optional[float] = Field( - None, - description='The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', - ) - usage_rate_lower_95PPR: Optional[float] = Field( - None, - description='The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', - ) - usage_rate_lower_limit: Optional[float] = Field( - None, - description='The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', - ) - - -class FRBCFillLevelTargetProfileElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - duration: Duration = Field(..., description='The duration of the element.') - fill_level_range: NumberRange = Field( - ..., - description='The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.', - ) - - -class DDBCAverageDemandRateForecastElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - duration: Duration = Field(..., description='Duration of the element') - demand_rate_upper_limit: Optional[float] = Field( - None, - description='The upper limit of the range with a 100 % probability that the demand rate is within that range', - ) - demand_rate_upper_95PPR: Optional[float] = Field( - None, - description='The upper limit of the range with a 95 % probability that the demand rate is within that range', - ) - demand_rate_upper_68PPR: Optional[float] = Field( - None, - description='The upper limit of the range with a 68 % probability that the demand rate is within that range', - ) - demand_rate_expected: float = Field( - ..., - description='The most likely value for the demand rate; the expected increase or decrease of the fill_level per second', - ) - demand_rate_lower_68PPR: Optional[float] = Field( - None, - description='The lower limit of the range with a 68 % probability that the demand rate is within that range', - ) - demand_rate_lower_95PPR: Optional[float] = Field( - None, - description='The lower limit of the range with a 95 % probability that the demand rate is within that range', - ) - demand_rate_lower_limit: Optional[float] = Field( - None, - description='The lower limit of the range with a 100 % probability that the demand rate is within that range', - ) - - -class RoleType(Enum): - ENERGY_PRODUCER = 'ENERGY_PRODUCER' - ENERGY_CONSUMER = 'ENERGY_CONSUMER' - ENERGY_STORAGE = 'ENERGY_STORAGE' - - -class Commodity(Enum): - GAS = 'GAS' - HEAT = 'HEAT' - ELECTRICITY = 'ELECTRICITY' - OIL = 'OIL' - - -class CommodityQuantity(Enum): - ELECTRIC_POWER_L1 = 'ELECTRIC.POWER.L1' - ELECTRIC_POWER_L2 = 'ELECTRIC.POWER.L2' - ELECTRIC_POWER_L3 = 'ELECTRIC.POWER.L3' - ELECTRIC_POWER_3_PHASE_SYMMETRIC = 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' - NATURAL_GAS_FLOW_RATE = 'NATURAL_GAS.FLOW_RATE' - HYDROGEN_FLOW_RATE = 'HYDROGEN.FLOW_RATE' - HEAT_TEMPERATURE = 'HEAT.TEMPERATURE' - HEAT_FLOW_RATE = 'HEAT.FLOW_RATE' - HEAT_THERMAL_POWER = 'HEAT.THERMAL_POWER' - OIL_FLOW_RATE = 'OIL.FLOW_RATE' - - -class InstructionStatus(Enum): - NEW = 'NEW' - ACCEPTED = 'ACCEPTED' - REJECTED = 'REJECTED' - REVOKED = 'REVOKED' - STARTED = 'STARTED' - SUCCEEDED = 'SUCCEEDED' - ABORTED = 'ABORTED' - - -class ControlType(Enum): - POWER_ENVELOPE_BASED_CONTROL = 'POWER_ENVELOPE_BASED_CONTROL' - POWER_PROFILE_BASED_CONTROL = 'POWER_PROFILE_BASED_CONTROL' - OPERATION_MODE_BASED_CONTROL = 'OPERATION_MODE_BASED_CONTROL' - FILL_RATE_BASED_CONTROL = 'FILL_RATE_BASED_CONTROL' - DEMAND_DRIVEN_BASED_CONTROL = 'DEMAND_DRIVEN_BASED_CONTROL' - NOT_CONTROLABLE = 'NOT_CONTROLABLE' - NO_SELECTION = 'NO_SELECTION' - - -class PEBCPowerEnvelopeLimitType(Enum): - UPPER_LIMIT = 'UPPER_LIMIT' - LOWER_LIMIT = 'LOWER_LIMIT' - - -class PEBCPowerEnvelopeConsequenceType(Enum): - VANISH = 'VANISH' - DEFER = 'DEFER' - - -class PPBCPowerSequenceStatus(Enum): - NOT_SCHEDULED = 'NOT_SCHEDULED' - SCHEDULED = 'SCHEDULED' - EXECUTING = 'EXECUTING' - INTERRUPTED = 'INTERRUPTED' - FINISHED = 'FINISHED' - ABORTED = 'ABORTED' - - -class OMBCTimerStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['OMBC.TimerStatus'] = 'OMBC.TimerStatus' - message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') - finished_at: AwareDatetime = Field( - ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', - ) - - -class FRBCTimerStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.TimerStatus'] = 'FRBC.TimerStatus' - message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') - actuator_id: ID = Field( - ..., description='The ID of the actuator the timer belongs to' - ) - finished_at: AwareDatetime = Field( - ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', - ) - - -class DDBCTimerStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['DDBC.TimerStatus'] = 'DDBC.TimerStatus' - message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') - actuator_id: ID = Field( - ..., description='The ID of the actuator the timer belongs to' - ) - finished_at: AwareDatetime = Field( - ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', - ) - - -class SelectControlType(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['SelectControlType'] = 'SelectControlType' - message_id: ID - control_type: ControlType = Field( - ..., - description='The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails', - ) - - -class SessionRequest(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['SessionRequest'] = 'SessionRequest' - message_id: ID - request: SessionRequestType = Field(..., description='The type of request') - diagnostic_label: Optional[str] = Field( - None, - description='Optional field for a human readible descirption for debugging purposes', - ) - - -class RevokeObject(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['RevokeObject'] = 'RevokeObject' - message_id: ID - object_type: RevokableObjects = Field( - ..., description='The type of object that needs to be revoked' - ) - object_id: ID = Field(..., description='The ID of object that needs to be revoked') - - -class Handshake(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['Handshake'] = 'Handshake' - message_id: ID - role: EnergyManagementRole = Field( - ..., description='The role of the sender of this message' - ) - supported_protocol_versions: Optional[List[str]] = Field( - None, - description='Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.', - min_length=1, - ) - - -class HandshakeResponse(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['HandshakeResponse'] = 'HandshakeResponse' - message_id: ID - selected_protocol_version: str = Field( - ..., description='The protocol version the CEM selected for this session' - ) - - -class ReceptionStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['ReceptionStatus'] = 'ReceptionStatus' - subject_message_id: ID = Field( - ..., description='The message this ReceptionStatus refers to' - ) - status: ReceptionStatusValues = Field( - ..., description='Enumeration of status values' - ) - diagnostic_label: Optional[str] = Field( - None, - description='Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.', - ) - - -class InstructionStatusUpdate(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['InstructionStatusUpdate'] = 'InstructionStatusUpdate' - message_id: ID - instruction_id: ID = Field( - ..., description='ID of this instruction (as provided by the CEM) ' - ) - status_type: InstructionStatus = Field( - ..., description='Present status of this instruction.' - ) - timestamp: AwareDatetime = Field( - ..., description='Timestamp when status_type has changed the last time.' - ) - - -class PEBCEnergyConstraint(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PEBC.EnergyConstraint'] = 'PEBC.EnergyConstraint' - message_id: ID - id: ID = Field( - ..., - description='Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - valid_from: AwareDatetime = Field( - ..., - description='Moment this PEBC.EnergyConstraints information starts to be valid', - ) - valid_until: AwareDatetime = Field( - ..., - description='Moment until this PEBC.EnergyConstraints information is valid.', - ) - upper_average_power: float = Field( - ..., - description='Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', - ) - lower_average_power: float = Field( - ..., - description='Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', - ) - commodity_quantity: CommodityQuantity = Field( - ..., - description='Type of power quantity which applies to upper_average_power and lower_average_power', - ) - - -class PPBCScheduleInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PPBC.ScheduleInstruction'] = 'PPBC.ScheduleInstruction' - message_id: ID - id: ID = Field( - ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - power_profile_id: ID = Field( - ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', - ) - sequence_container_id: ID = Field( - ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', - ) - power_sequence_id: ID = Field( - ..., - description='ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.', - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition', - ) - - -class PPBCStartInterruptionInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PPBC.StartInterruptionInstruction'] = ( - 'PPBC.StartInterruptionInstruction' - ) - message_id: ID - id: ID = Field( - ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - power_profile_id: ID = Field( - ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.', - ) - sequence_container_id: ID = Field( - ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.', - ) - power_sequence_id: ID = Field( - ..., description='ID of the PPBC.PowerSequence that the CEM wants to interrupt.' - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition', - ) - - -class PPBCEndInterruptionInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PPBC.EndInterruptionInstruction'] = ( - 'PPBC.EndInterruptionInstruction' - ) - message_id: ID - id: ID = Field( - ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - power_profile_id: ID = Field( - ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.', - ) - sequence_container_id: ID = Field( - ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.', - ) - power_sequence_id: ID = Field( - ..., - description='ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.', - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition', - ) - - -class OMBCStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['OMBC.Status'] = 'OMBC.Status' - message_id: ID - active_operation_mode_id: ID = Field( - ..., description='ID of the active OMBC.OperationMode.' - ) - operation_mode_factor: float = Field( - ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', - ) - previous_operation_mode_id: Optional[ID] = Field( - None, - description='ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', - ) - transition_timestamp: Optional[AwareDatetime] = Field( - None, - description='Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', - ) - - -class OMBCInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['OMBC.Instruction'] = 'OMBC.Instruction' - message_id: ID - id: ID = Field( - ..., - description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', - ) - operation_mode_id: ID = Field( - ..., description='ID of the OMBC.OperationMode that should be activated' - ) - operation_mode_factor: float = Field( - ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition', - ) - - -class FRBCActuatorStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.ActuatorStatus'] = 'FRBC.ActuatorStatus' - message_id: ID - actuator_id: ID = Field( - ..., description='ID of the actuator this messages refers to' - ) - active_operation_mode_id: ID = Field( - ..., description='ID of the FRBC.OperationMode that is presently active.' - ) - operation_mode_factor: float = Field( - ..., - description='The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.', - ) - previous_operation_mode_id: Optional[ID] = Field( - None, - description='ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', - ) - transition_timestamp: Optional[AwareDatetime] = Field( - None, - description='Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', - ) - - -class FRBCStorageStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.StorageStatus'] = 'FRBC.StorageStatus' - message_id: ID - present_fill_level: float = Field( - ..., description='Present fill level of the Storage' - ) - - -class FRBCLeakageBehaviour(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.LeakageBehaviour'] = 'FRBC.LeakageBehaviour' - message_id: ID - valid_from: AwareDatetime = Field( - ..., - description='Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.', - ) - elements: List[FRBCLeakageBehaviourElement] = Field( - ..., - description='List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.', - max_length=288, - min_length=1, - ) - - -class FRBCInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.Instruction'] = 'FRBC.Instruction' - message_id: ID - id: ID = Field( - ..., - description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - actuator_id: ID = Field( - ..., description='ID of the actuator this instruction belongs to.' - ) - operation_mode: ID = Field( - ..., description='ID of the FRBC.OperationMode that should be activated.' - ) - operation_mode_factor: float = Field( - ..., - description='The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition.', - ) - - -class FRBCUsageForecast(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.UsageForecast'] = 'FRBC.UsageForecast' - message_id: ID - start_time: AwareDatetime = Field( - ..., description='Time at which the FRBC.UsageForecast starts.' - ) - elements: List[FRBCUsageForecastElement] = Field( - ..., - description='Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.', - max_length=288, - min_length=1, - ) - - -class FRBCFillLevelTargetProfile(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.FillLevelTargetProfile'] = 'FRBC.FillLevelTargetProfile' - message_id: ID - start_time: AwareDatetime = Field( - ..., description='Time at which the FRBC.FillLevelTargetProfile starts.' - ) - elements: List[FRBCFillLevelTargetProfileElement] = Field( - ..., - description='List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.', - max_length=288, - min_length=1, - ) - - -class DDBCActuatorStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['DDBC.ActuatorStatus'] = 'DDBC.ActuatorStatus' - message_id: ID - actuator_id: ID = Field( - ..., description='ID of the actuator this messages refers to' - ) - active_operation_mode_id: ID = Field( - ..., - description='The operation mode that is presently active for this actuator.', - ) - operation_mode_factor: float = Field( - ..., - description='The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.', - ) - previous_operation_mode_id: Optional[ID] = Field( - None, - description='ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', - ) - transition_timestamp: Optional[AwareDatetime] = Field( - None, - description='Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', - ) - - -class DDBCInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['DDBC.Instruction'] = 'DDBC.Instruction' - message_id: ID - id: ID = Field( - ..., - description='Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition', - ) - actuator_id: ID = Field( - ..., description='ID of the actuator this Instruction belongs to.' - ) - operation_mode_id: ID = Field(..., description='ID of the DDBC.OperationMode') - operation_mode_factor: float = Field( - ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', - ) - - -class DDBCAverageDemandRateForecast(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['DDBC.AverageDemandRateForecast'] = ( - 'DDBC.AverageDemandRateForecast' - ) - message_id: ID - start_time: AwareDatetime = Field(..., description='Start time of the profile.') - elements: List[DDBCAverageDemandRateForecastElement] = Field( - ..., - description='Elements of the profile. Elements must be placed in chronological order.', - max_length=288, - min_length=1, - ) - - -class PowerValue(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the value refers to' - ) - value: float = Field( - ..., - description='Power value expressed in the unit associated with the CommodityQuantity', - ) - - -class PowerForecastValue(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value_upper_limit: Optional[float] = Field( - None, - description='The upper boundary of the range with 100 % certainty the power value is in it', - ) - value_upper_95PPR: Optional[float] = Field( - None, - description='The upper boundary of the range with 95 % certainty the power value is in it', - ) - value_upper_68PPR: Optional[float] = Field( - None, - description='The upper boundary of the range with 68 % certainty the power value is in it', - ) - value_expected: float = Field(..., description='The expected power value.') - value_lower_68PPR: Optional[float] = Field( - None, - description='The lower boundary of the range with 68 % certainty the power value is in it', - ) - value_lower_95PPR: Optional[float] = Field( - None, - description='The lower boundary of the range with 95 % certainty the power value is in it', - ) - value_lower_limit: Optional[float] = Field( - None, - description='The lower boundary of the range with 100 % certainty the power value is in it', - ) - commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the value refers to' - ) - - -class PowerRange(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - start_of_range: float = Field( - ..., description='Power value that defines the start of the range.' - ) - end_of_range: float = Field( - ..., description='Power value that defines the end of the range.' - ) - commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the values refer to' - ) - - -class Role(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: RoleType = Field( - ..., description='Role type of the Resource Manager for the given commodity' - ) - commodity: Commodity = Field(..., description='Commodity the role refers to.') - - -class PowerForecastElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - duration: Duration = Field(..., description='Duration of the PowerForecastElement') - power_values: List[PowerForecastValue] = Field( - ..., - description='The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.', - max_length=10, - min_length=1, - ) - - -class PEBCAllowedLimitRange(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - commodity_quantity: CommodityQuantity = Field( - ..., description='Type of power quantity this PEBC.AllowedLimitRange applies to' - ) - limit_type: PEBCPowerEnvelopeLimitType = Field( - ..., - description='Indicates if this ranges applies to the upper limit or the lower limit', - ) - range_boundary: NumberRange = Field( - ..., - description='Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ', - ) - abnormal_condition_only: bool = Field( - ..., - description='Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition', - ) - - -class PEBCPowerEnvelope(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - commodity_quantity: CommodityQuantity = Field( - ..., description='Type of power quantity this PEBC.PowerEnvelope applies to' - ) - power_envelope_elements: List[PEBCPowerEnvelopeElement] = Field( - ..., - description='The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.', - max_length=288, - min_length=1, - ) - - -class PPBCPowerSequenceElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - duration: Duration = Field( - ..., description='Duration of the PPBC.PowerSequenceElement.' - ) - power_values: List[PowerForecastValue] = Field( - ..., - description='The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.', - max_length=10, - min_length=1, - ) - - -class PPBCPowerSequenceContainerStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - power_profile_id: ID = Field( - ..., - description='ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ', - ) - sequence_container_id: ID = Field( - ..., - description='ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.', - ) - selected_sequence_id: Optional[ID] = Field( - None, - description='ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.', - ) - progress: Optional[Duration] = Field( - None, - description='Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.', - ) - status: PPBCPowerSequenceStatus = Field( - ..., description='Status of the selected PPBC.PowerSequence' - ) - - -class OMBCOperationMode(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', - ) - power_ranges: List[PowerRange] = Field( - ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', - max_length=10, - min_length=1, - ) - running_costs: Optional[NumberRange] = Field( - None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', - ) - abnormal_condition_only: bool = Field( - ..., - description='Indicates if this OMBC.OperationMode may only be used during an abnormal condition.', - ) - - -class FRBCOperationModeElement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - fill_level_range: NumberRange = Field( - ..., - description='The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.', - ) - fill_rate: NumberRange = Field( - ..., - description='Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ', - ) - power_ranges: List[PowerRange] = Field( - ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', - max_length=10, - min_length=1, - ) - running_costs: Optional[NumberRange] = Field( - None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', - ) - - -class DDBCOperationMode(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - Id: ID = Field( - ..., - description='ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', - ) - power_ranges: List[PowerRange] = Field( - ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', - max_length=10, - min_length=1, - ) - supply_range: NumberRange = Field( - ..., - description='The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.', - ) - running_costs: Optional[NumberRange] = Field( - None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', - ) - abnormal_condition_only: bool = Field( - ..., - description='Indicates if this DDBC.OperationMode may only be used during an abnormal condition.', - ) - - -class ResourceManagerDetails(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['ResourceManagerDetails'] = 'ResourceManagerDetails' - message_id: ID - resource_id: ID = Field( - ..., - description='Identifier of the Resource Manager. Must be unique within the scope of the CEM.', - ) - name: Optional[str] = Field(None, description='Human readable name given by user') - roles: List[Role] = Field( - ..., - description='Each Resource Manager provides one or more energy Roles', - max_length=3, - min_length=1, - ) - manufacturer: Optional[str] = Field(None, description='Name of Manufacturer') - model: Optional[str] = Field( - None, - description='Name of the model of the device (provided by the manufacturer)', - ) - serial_number: Optional[str] = Field( - None, description='Serial number of the device (provided by the manufacturer)' - ) - firmware_version: Optional[str] = Field( - None, - description='Version identifier of the firmware used in the device (provided by the manufacturer)', - ) - instruction_processing_delay: Duration = Field( - ..., - description='The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction', - ) - available_control_types: List[ControlType] = Field( - ..., - description='The control types supported by this Resource Manager.', - max_length=5, - min_length=1, - ) - currency: Optional[Currency] = Field( - None, - description='Currency to be used for all information regarding costs. Mandatory if cost information is published.', - ) - provides_forecast: bool = Field( - ..., - description='Indicates whether the ResourceManager is able to provide PowerForecasts', - ) - provides_power_measurement_types: List[CommodityQuantity] = Field( - ..., - description='Array of all CommodityQuantities that this Resource Manager can provide measurements for. ', - max_length=10, - min_length=1, - ) - - -class PowerMeasurement(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PowerMeasurement'] = 'PowerMeasurement' - message_id: ID - measurement_timestamp: AwareDatetime = Field( - ..., description='Timestamp when PowerValues were measured.' - ) - values: List[PowerValue] = Field( - ..., - description='Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).', - max_length=10, - min_length=1, - ) - - -class PowerForecast(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PowerForecast'] = 'PowerForecast' - message_id: ID - start_time: AwareDatetime = Field( - ..., description='Start time of time period that is covered by the profile.' - ) - elements: List[PowerForecastElement] = Field( - ..., - description='Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.', - max_length=288, - min_length=1, - ) - - -class PEBCPowerConstraints(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PEBC.PowerConstraints'] = 'PEBC.PowerConstraints' - message_id: ID - id: ID = Field( - ..., - description='Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - valid_from: AwareDatetime = Field( - ..., description='Moment this PEBC.PowerConstraints start to be valid' - ) - valid_until: Optional[AwareDatetime] = Field( - None, - description='Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.', - ) - consequence_type: PEBCPowerEnvelopeConsequenceType = Field( - ..., description='Type of consequence of limiting power' - ) - allowed_limit_ranges: List[PEBCAllowedLimitRange] = Field( - ..., - description='The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.', - max_length=100, - min_length=2, - ) - - -class PEBCInstruction(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PEBC.Instruction'] = 'PEBC.Instruction' - message_id: ID - id: ID = Field( - ..., - description='Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - execution_time: AwareDatetime = Field( - ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', - ) - abnormal_condition: bool = Field( - ..., - description='Indicates if this is an instruction during an abnormal condition.', - ) - power_constraints_id: ID = Field( - ..., - description='Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.', - ) - power_envelopes: List[PEBCPowerEnvelope] = Field( - ..., - description='The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.', - max_length=10, - min_length=1, - ) - - -class PPBCPowerProfileStatus(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PPBC.PowerProfileStatus'] = 'PPBC.PowerProfileStatus' - message_id: ID - sequence_container_status: List[PPBCPowerSequenceContainerStatus] = Field( - ..., - description='Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.', - max_length=1000, - min_length=1, - ) - - -class OMBCSystemDescription(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['OMBC.SystemDescription'] = 'OMBC.SystemDescription' - message_id: ID - valid_from: AwareDatetime = Field( - ..., - description='Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', - ) - operation_modes: List[OMBCOperationMode] = Field( - ..., - description='OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.', - max_length=100, - min_length=1, - ) - transitions: List[Transition] = Field( - ..., - description='Possible transitions to switch from one OMBC.OperationMode to another.', - max_length=1000, - min_length=0, - ) - timers: List[Timer] = Field( - ..., - description='Timers that control when certain transitions can be made.', - max_length=1000, - min_length=0, - ) - - -class PPBCPowerSequence(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.', - ) - elements: List[PPBCPowerSequenceElement] = Field( - ..., - description='List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.', - max_length=288, - min_length=1, - ) - is_interruptible: bool = Field( - ..., - description='Indicates whether the option of pausing a sequence is available.', - ) - max_pause_before: Optional[Duration] = Field( - None, - description='The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one', - ) - abnormal_condition_only: bool = Field( - ..., - description='Indicates if this PPBC.PowerSequence may only be used during an abnormal condition', - ) - - -class FRBCOperationMode(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', - ) - elements: List[FRBCOperationModeElement] = Field( - ..., - description='List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.', - max_length=100, - min_length=1, - ) - abnormal_condition_only: bool = Field( - ..., - description='Indicates if this FRBC.OperationMode may only be used during an abnormal condition', - ) - - -class DDBCActuatorDescription(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', - ) - supported_commodites: List[Commodity] = Field( - ..., - description='Commodities supported by the operation modes of this actuator. There shall be at least one commodity', - max_length=4, - min_length=1, - ) - operation_modes: List[DDBCOperationMode] = Field( - ..., - description='List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.', - max_length=100, - min_length=1, - ) - transitions: List[Transition] = Field( - ..., - description='List of Transitions between Operation Modes. Shall contain at least one Transition.', - max_length=1000, - min_length=0, - ) - timers: List[Timer] = Field( - ..., - description='List of Timers associated with Transitions for this Actuator. Can be empty.', - max_length=1000, - min_length=0, - ) - - -class DDBCSystemDescription(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['DDBC.SystemDescription'] = 'DDBC.SystemDescription' - message_id: ID - valid_from: AwareDatetime = Field( - ..., - description='Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', - ) - actuators: List[DDBCActuatorDescription] = Field( - ..., - description='List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.', - max_length=10, - min_length=1, - ) - present_demand_rate: NumberRange = Field( - ..., description='Present demand rate that needs to be satisfied by the system' - ) - provides_average_demand_rate_forecast: bool = Field( - ..., - description='Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.', - ) - - -class PPBCPowerSequenceContainer(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.', - ) - power_sequences: List[PPBCPowerSequence] = Field( - ..., - description='List of alternative Sequences where one could be chosen by the CEM', - max_length=288, - min_length=1, - ) - - -class FRBCActuatorDescription(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: ID = Field( - ..., - description='ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - diagnostic_label: Optional[str] = Field( - None, - description='Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', - ) - supported_commodities: List[Commodity] = Field( - ..., - description='List of all supported Commodities.', - max_length=4, - min_length=1, - ) - operation_modes: List[FRBCOperationMode] = Field( - ..., - description='Provided FRBC.OperationModes associated with this actuator', - max_length=100, - min_length=1, - ) - transitions: List[Transition] = Field( - ..., - description='Possible transitions between FRBC.OperationModes associated with this actuator.', - max_length=1000, - min_length=0, - ) - timers: List[Timer] = Field( - ..., - description='List of Timers associated with this actuator', - max_length=1000, - min_length=0, - ) - - -class PPBCPowerProfileDefinition(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['PPBC.PowerProfileDefinition'] = 'PPBC.PowerProfileDefinition' - message_id: ID - id: ID = Field( - ..., - description='ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', - ) - start_time: AwareDatetime = Field( - ..., - description='Indicates the first possible time the first PPBC.PowerSequence could start', - ) - end_time: AwareDatetime = Field( - ..., - description='Indicates when the last PPBC.PowerSequence shall be finished at the latest', - ) - power_sequences_containers: List[PPBCPowerSequenceContainer] = Field( - ..., - description='The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.', - max_length=1000, - min_length=1, - ) - - -class FRBCSystemDescription(BaseModel): - model_config = ConfigDict( - extra='forbid', - ) - message_type: Literal['FRBC.SystemDescription'] = 'FRBC.SystemDescription' - message_id: ID - valid_from: AwareDatetime = Field( - ..., - description='Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', - ) - actuators: List[FRBCActuatorDescription] = Field( - ..., description='Details of all Actuators.', max_length=10, min_length=1 - ) - storage: FRBCStorageDescription = Field(..., description='Details of the storage.') diff --git a/build/lib/s2python/ppbc/__init__.py b/build/lib/s2python/ppbc/__init__.py deleted file mode 100644 index 28e4e0e..0000000 --- a/build/lib/s2python/ppbc/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from s2python.ppbc.ppbc_schedule_instruction import PPBCScheduleInstruction diff --git a/build/lib/s2python/ppbc/ppbc_schedule_instruction.py b/build/lib/s2python/ppbc/ppbc_schedule_instruction.py deleted file mode 100644 index c794b78..0000000 --- a/build/lib/s2python/ppbc/ppbc_schedule_instruction.py +++ /dev/null @@ -1,33 +0,0 @@ -import uuid - -from s2python.generated.gen_s2 import ( - PPBCScheduleInstruction as GenPPBCScheduleInstruction, -) -from s2python.validate_values_mixin import ( - catch_and_convert_exceptions, - S2Message, -) - - -@catch_and_convert_exceptions -class PPBCScheduleInstruction( - GenPPBCScheduleInstruction, S2Message["PPBCScheduleInstruction"] -): - model_config = GenPPBCScheduleInstruction.model_config - model_config["validate_assignment"] = True - - id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["id"] # type: ignore[assignment] - - power_profile_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ - "power_profile_id" - ] # type: ignore[assignment] - - message_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields["message_id"] # type: ignore[assignment] - - sequence_container_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ - "sequence_container_id" - ] # type: ignore[assignment] - - power_sequence_id: uuid.UUID = GenPPBCScheduleInstruction.model_fields[ - "power_sequence_id" - ] # type: ignore[assignment] diff --git a/build/lib/s2python/py.typed b/build/lib/s2python/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/build/lib/s2python/reception_status_awaiter.py b/build/lib/s2python/reception_status_awaiter.py deleted file mode 100644 index 5c4bd42..0000000 --- a/build/lib/s2python/reception_status_awaiter.py +++ /dev/null @@ -1,60 +0,0 @@ -"""ReceptationStatusAwaiter class which notifies any coroutine waiting for a certain reception status message. - -Copied from -https://github.com/flexiblepower/s2-analyzer/blob/main/backend/s2_analyzer_backend/reception_status_awaiter.py under -Apache2 license on 31-08-2024. -""" - -import asyncio -import uuid -from typing import Dict - -from s2python.common import ReceptionStatus - - -class ReceptionStatusAwaiter: - received: Dict[uuid.UUID, ReceptionStatus] - awaiting: Dict[uuid.UUID, asyncio.Event] - - def __init__(self) -> None: - self.received = {} - self.awaiting = {} - - async def wait_for_reception_status( - self, message_id: uuid.UUID, timeout_reception_status: float - ) -> ReceptionStatus: - if message_id in self.received: - reception_status = self.received[message_id] - else: - if message_id in self.awaiting: - received_event = self.awaiting[message_id] - else: - received_event = asyncio.Event() - self.awaiting[message_id] = received_event - - await asyncio.wait_for(received_event.wait(), timeout_reception_status) - reception_status = self.received[message_id] - - if message_id in self.awaiting: - del self.awaiting[message_id] - - return reception_status - - async def receive_reception_status(self, reception_status: ReceptionStatus) -> None: - if not isinstance(reception_status, ReceptionStatus): - raise RuntimeError( - f"Expected a ReceptionStatus but received message {reception_status}" - ) - - if reception_status.subject_message_id in self.received: - raise RuntimeError( - f"ReceptationStatus for message_subject_id {reception_status.subject_message_id} has already " - f"been received!" - ) - - self.received[reception_status.subject_message_id] = reception_status - awaiting = self.awaiting.get(reception_status.subject_message_id) - - if awaiting: - awaiting.set() - del self.awaiting[reception_status.subject_message_id] diff --git a/build/lib/s2python/s2_connection.py b/build/lib/s2python/s2_connection.py deleted file mode 100644 index 188ecc7..0000000 --- a/build/lib/s2python/s2_connection.py +++ /dev/null @@ -1,526 +0,0 @@ -import asyncio -import json -import logging -import time -import threading -import uuid -from dataclasses import dataclass -from typing import Optional, List, Type, Dict, Callable, Awaitable, Union - -import websockets -from websockets.asyncio.client import ClientConnection as WSConnection, connect as ws_connect - -from s2python.common import ( - ReceptionStatusValues, - ReceptionStatus, - Handshake, - EnergyManagementRole, - Role, - HandshakeResponse, - ResourceManagerDetails, - Duration, - Currency, - SelectControlType, -) -from s2python.generated.gen_s2 import CommodityQuantity -from s2python.reception_status_awaiter import ReceptionStatusAwaiter -from s2python.s2_control_type import S2ControlType -from s2python.s2_parser import S2Parser -from s2python.s2_validation_error import S2ValidationError -from s2python.validate_values_mixin import S2Message -from s2python.version import S2_VERSION - -logger = logging.getLogger("s2python") - - -@dataclass -class AssetDetails: # pylint: disable=too-many-instance-attributes - resource_id: str - - provides_forecast: bool - provides_power_measurements: List[CommodityQuantity] - - instruction_processing_delay: Duration - roles: List[Role] - currency: Optional[Currency] = None - - name: Optional[str] = None - manufacturer: Optional[str] = None - model: Optional[str] = None - firmware_version: Optional[str] = None - serial_number: Optional[str] = None - - 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 - ], - currency=self.currency, - firmware_version=self.firmware_version, - instruction_processing_delay=self.instruction_processing_delay, - manufacturer=self.manufacturer, - message_id=uuid.uuid4(), - model=self.model, - name=self.name, - provides_forecast=self.provides_forecast, - provides_power_measurement_types=self.provides_power_measurements, - resource_id=self.resource_id, - roles=self.roles, - serial_number=self.serial_number, - ) - - -S2MessageHandler = Union[ - Callable[["S2Connection", S2Message, Callable[[], None]], None], - Callable[["S2Connection", S2Message, Awaitable[None]], Awaitable[None]], -] - - -class SendOkay: - status_is_send: threading.Event - connection: "S2Connection" - subject_message_id: uuid.UUID - - def __init__(self, connection: "S2Connection", subject_message_id: uuid.UUID): - self.status_is_send = threading.Event() - self.connection = connection - self.subject_message_id = subject_message_id - - async def run_async(self) -> None: - self.status_is_send.set() - - await self.connection.respond_with_reception_status( - subject_message_id=str(self.subject_message_id), - status=ReceptionStatusValues.OK, - diagnostic_label="Processed okay.", - ) - - def run_sync(self) -> None: - self.status_is_send.set() - - self.connection.respond_with_reception_status_sync( - subject_message_id=str(self.subject_message_id), - status=ReceptionStatusValues.OK, - diagnostic_label="Processed okay.", - ) - - async def ensure_send_async(self, type_msg: Type[S2Message]) -> None: - if not self.status_is_send.is_set(): - logger.warning( - "Handler for message %s %s did not call send_okay / function to send the ReceptionStatus. " - "Sending it now.", - type_msg, - self.subject_message_id, - ) - await self.run_async() - - def ensure_send_sync(self, type_msg: Type[S2Message]) -> None: - if not self.status_is_send.is_set(): - logger.warning( - "Handler for message %s %s did not call send_okay / function to send the ReceptionStatus. " - "Sending it now.", - type_msg, - self.subject_message_id, - ) - self.run_sync() - - -class MessageHandlers: - handlers: Dict[Type[S2Message], S2MessageHandler] - - def __init__(self) -> None: - self.handlers = {} - - async def handle_message(self, connection: "S2Connection", msg: S2Message) -> None: - """Handle the S2 message using the registered handler. - - :param connection: The S2 conncetion the `msg` is received from. - :param msg: The S2 message - """ - handler = self.handlers.get(type(msg)) - if handler is not None: - send_okay = SendOkay(connection, msg.message_id) # type: ignore[attr-defined] - - try: - if asyncio.iscoroutinefunction(handler): - await handler(connection, msg, send_okay.run_async()) # type: ignore[arg-type] - await send_okay.ensure_send_async(type(msg)) - else: - - def do_message() -> None: - handler(connection, msg, send_okay.run_sync) # type: ignore[arg-type] - send_okay.ensure_send_sync(type(msg)) - - eventloop = asyncio.get_event_loop() - await eventloop.run_in_executor(executor=None, func=do_message) - except Exception: - if not send_okay.status_is_send.is_set(): - await connection.respond_with_reception_status( - subject_message_id=str(msg.message_id), # type: ignore[attr-defined] - status=ReceptionStatusValues.PERMANENT_ERROR, - diagnostic_label=f"While processing message {msg.message_id} " # type: ignore[attr-defined] - f"an unrecoverable error occurred.", - ) - raise - else: - logger.warning( - "Received a message of type %s but no handler is registered. Ignoring the message.", - type(msg), - ) - - 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. - :param handler: The function (asynchronuous or normal) which should handle the S2 message. - """ - self.handlers[msg_type] = handler - - -class S2Connection: # pylint: disable=too-many-instance-attributes - url: str - reconnect: bool - reception_status_awaiter: ReceptionStatusAwaiter - ws: Optional[WSConnection] - s2_parser: S2Parser - control_types: List[S2ControlType] - role: EnergyManagementRole - asset_details: AssetDetails - - _thread: threading.Thread - - _handlers: MessageHandlers - _current_control_type: Optional[S2ControlType] - _received_messages: asyncio.Queue - - _eventloop: asyncio.AbstractEventLoop - _stop_event: asyncio.Event - _restart_connection_event: asyncio.Event - - def __init__( # pylint: disable=too-many-arguments - self, - url: str, - role: EnergyManagementRole, - control_types: List[S2ControlType], - asset_details: AssetDetails, - reconnect: bool = False, - ) -> None: - self.url = url - self.reconnect = reconnect - self.reception_status_awaiter = ReceptionStatusAwaiter() - self.ws = None - self.s2_parser = S2Parser() - - self._handlers = MessageHandlers() - self._current_control_type = None - - self._eventloop = asyncio.new_event_loop() - - self.control_types = control_types - self.role = role - self.asset_details = asset_details - - 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) - - def start_as_rm(self) -> None: - self._run_eventloop(self._run_as_rm()) - - def _run_eventloop(self, main_task: Awaitable[None]) -> None: - self._thread = threading.current_thread() - logger.debug("Starting eventloop") - try: - self._eventloop.run_until_complete(main_task) - except asyncio.CancelledError: - pass - logger.debug("S2 connection thread has stopped.") - - def stop(self) -> None: - """Stops the S2 connection. - - Note: Ensure this method is called from a different thread than the thread running the S2 connection. - Otherwise it will block waiting on the coroutine _do_stop to terminate successfully but it can't run - the coroutine. A `RuntimeError` will be raised to prevent the indefinite block. - """ - 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!" - ) - if self._eventloop.is_running(): - asyncio.run_coroutine_threadsafe(self._do_stop(), self._eventloop).result() - self._thread.join() - logger.info("Stopped the S2 connection.") - - async def _do_stop(self) -> None: - logger.info("Will stop the S2 connection.") - self._stop_event.set() - - async def _run_as_rm(self) -> None: - logger.debug("Connecting as S2 resource manager.") - - self._stop_event = asyncio.Event() - - first_run = True - - while (first_run or self.reconnect) and not self._stop_event.is_set(): - first_run = False - self._restart_connection_event = asyncio.Event() - await self._connect_and_run() - time.sleep(1) - - logger.debug("Finished S2 connection eventloop.") - - async def _connect_and_run(self) -> None: - self._received_messages = asyncio.Queue() - await self._connect_ws() - if self.ws: - - async def wait_till_stop() -> None: - await self._stop_event.wait() - - async def wait_till_connection_restart() -> None: - await self._restart_connection_event.wait() - - background_tasks = [ - self._eventloop.create_task(self._receive_messages()), - self._eventloop.create_task(wait_till_stop()), - self._eventloop.create_task(self._connect_as_rm()), - self._eventloop.create_task(wait_till_connection_restart()), - ] - - (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 - - for task in done: - try: - await task - except asyncio.CancelledError: - pass - except (websockets.ConnectionClosedError, websockets.ConnectionClosedOK): - logger.info("The other party closed the websocket connection.") - - for task in pending: - try: - task.cancel() - await task - except asyncio.CancelledError: - pass - - await self.ws.close() - await self.ws.wait_closed() - - async def _connect_ws(self) -> None: - try: - self.ws = await ws_connect(uri=self.url) - except (EOFError, OSError) as e: - logger.info("Could not connect due to: %s", str(e)) - - 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] - ) - ) - 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: - if not isinstance(message, Handshake): - logger.error( - "Handler for Handshake received a message of the wrong type: %s", type(message) - ) - return - - logger.debug( - "%s supports S2 protocol versions: %s", - message.role, - message.supported_protocol_versions, - ) - await send_okay - - async def handle_handshake_response_as_rm( - self, _: "S2Connection", message: S2Message, send_okay: Awaitable[None] - ) -> None: - if not isinstance(message, HandshakeResponse): - logger.error( - "Handler for HandshakeResponse received a message of the wrong type: %s", - type(message), - ) - return - - logger.debug("Received HandshakeResponse %s", message.to_json()) - - logger.debug("CEM selected to use version %s", message.selected_protocol_version) - await send_okay - logger.debug("Handshake complete. Sending first ResourceManagerDetails.") - - await self.send_msg_and_await_reception_status_async( - self.asset_details.to_resource_manager_details(self.control_types) - ) - - async def handle_select_control_type_as_rm( - self, _: "S2Connection", message: S2Message, send_okay: Awaitable[None] - ) -> None: - if not isinstance(message, SelectControlType): - logger.error( - "Handler for SelectControlType received a message of the wrong type: %s", - type(message), - ) - return - - await send_okay - - 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 - ) - - if self._current_control_type is not None: - 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) - self._current_control_type.register_handlers(self._handlers) - - async def _receive_messages(self) -> None: - """Receives all incoming messages in the form of a generator. - - Will also receive the ReceptionStatus messages but instead of yielding these messages, they are routed - 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." - ) - - logger.info("S2 connection has started to receive messages.") - - async for message in self.ws: - try: - s2_msg: S2Message = self.s2_parser.parse_as_any_message(message) - except json.JSONDecodeError: - await self._send_and_forget( - ReceptionStatus( - subject_message_id="00000000-0000-0000-0000-000000000000", - status=ReceptionStatusValues.INVALID_DATA, - diagnostic_label="Not valid json.", - ) - ) - except S2ValidationError as e: - json_msg = json.loads(message) - message_id = json_msg.get("message_id") - if message_id: - await self.respond_with_reception_status( - subject_message_id=message_id, - status=ReceptionStatusValues.INVALID_MESSAGE, - diagnostic_label=str(e), - ) - else: - await self.respond_with_reception_status( - subject_message_id="00000000-0000-0000-0000-000000000000", - status=ReceptionStatusValues.INVALID_DATA, - diagnostic_label="Message appears valid json but could not find a message_id field.", - ) - else: - logger.debug("Received message %s", s2_msg.to_json()) - - if isinstance(s2_msg, ReceptionStatus): - logger.debug( - "Message is a reception status for %s so registering in cache.", - s2_msg.subject_message_id, - ) - await self.reception_status_awaiter.receive_reception_status(s2_msg) - else: - await self._received_messages.put(s2_msg) - - 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." - ) - - json_msg = s2_msg.to_json() - logger.debug("Sending message %s", json_msg) - try: - await self.ws.send(json_msg) - except websockets.ConnectionClosedError as e: - logger.error("Unable to send message %s due to %s", s2_msg, str(e)) - self._restart_connection_event.set() - - async def respond_with_reception_status( - self, subject_message_id: str, status: ReceptionStatusValues, diagnostic_label: str - ) -> None: - 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, - status=status, - diagnostic_label=diagnostic_label, - ) - ) - - def respond_with_reception_status_sync( - self, subject_message_id: str, status: ReceptionStatusValues, diagnostic_label: str - ) -> None: - asyncio.run_coroutine_threadsafe( - 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 - ) -> ReceptionStatus: - await self._send_and_forget(s2_msg) - logger.debug( - "Waiting for ReceptionStatus for %s %s seconds", - s2_msg.message_id, # type: ignore[attr-defined] - timeout_reception_status, - ) - try: - reception_status = await self.reception_status_awaiter.wait_for_reception_status( - s2_msg.message_id, timeout_reception_status # type: ignore[attr-defined] - ) - except TimeoutError: - logger.error( - "Did not receive a reception status on time for %s", - s2_msg.message_id, # type: ignore[attr-defined] - ) - self._stop_event.set() - raise - - if reception_status.status != ReceptionStatusValues.OK and raise_on_error: - 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 - ) -> ReceptionStatus: - return asyncio.run_coroutine_threadsafe( - self.send_msg_and_await_reception_status_async( - s2_msg, timeout_reception_status, raise_on_error - ), - self._eventloop, - ).result() - - async def _handle_received_messages(self) -> None: - while True: - msg = await self._received_messages.get() - await self._handlers.handle_message(self, msg) diff --git a/build/lib/s2python/s2_control_type.py b/build/lib/s2python/s2_control_type.py deleted file mode 100644 index 547ead7..0000000 --- a/build/lib/s2python/s2_control_type.py +++ /dev/null @@ -1,76 +0,0 @@ -import abc -import typing - -from s2python.common import ControlType as ProtocolControlType -from s2python.frbc import FRBCInstruction -from s2python.ppbc import PPBCScheduleInstruction -from s2python.validate_values_mixin import S2Message - -if typing.TYPE_CHECKING: - from s2python.s2_connection import S2Connection, MessageHandlers - - -class S2ControlType(abc.ABC): - @abc.abstractmethod - def get_protocol_control_type(self) -> ProtocolControlType: ... - - @abc.abstractmethod - def register_handlers(self, handlers: "MessageHandlers") -> None: ... - - @abc.abstractmethod - def activate(self, conn: "S2Connection") -> None: ... - - @abc.abstractmethod - def deactivate(self, conn: "S2Connection") -> None: ... - - -class FRBCControlType(S2ControlType): - def get_protocol_control_type(self) -> ProtocolControlType: - return ProtocolControlType.FILL_RATE_BASED_CONTROL - - def register_handlers(self, handlers: "MessageHandlers") -> None: - handlers.register_handler(FRBCInstruction, 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: ... - - @abc.abstractmethod - def deactivate(self, conn: "S2Connection") -> None: ... - - -class PPBCControlType(S2ControlType): - def get_protocol_control_type(self) -> ProtocolControlType: - return ProtocolControlType.POWER_PROFILE_BASED_CONTROL - - def register_handlers(self, handlers: "MessageHandlers") -> None: - handlers.register_handler(PPBCScheduleInstruction, 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: ... - - @abc.abstractmethod - def deactivate(self, conn: "S2Connection") -> None: ... - - -class NoControlControlType(S2ControlType): - def get_protocol_control_type(self) -> ProtocolControlType: - return ProtocolControlType.NOT_CONTROLABLE - - def register_handlers(self, handlers: "MessageHandlers") -> None: - pass - - @abc.abstractmethod - def activate(self, conn: "S2Connection") -> None: ... - - @abc.abstractmethod - def deactivate(self, conn: "S2Connection") -> None: ... diff --git a/build/lib/s2python/s2_parser.py b/build/lib/s2python/s2_parser.py deleted file mode 100644 index e1a5c43..0000000 --- a/build/lib/s2python/s2_parser.py +++ /dev/null @@ -1,120 +0,0 @@ -import json -import logging -from typing import Optional, TypeVar, Union, Type, Dict - -from s2python.common import ( - Handshake, - HandshakeResponse, - InstructionStatusUpdate, - PowerForecast, - PowerMeasurement, - ReceptionStatus, - ResourceManagerDetails, - RevokeObject, - SelectControlType, - SessionRequest, -) -from s2python.frbc import ( - FRBCActuatorStatus, - FRBCFillLevelTargetProfile, - FRBCInstruction, - FRBCLeakageBehaviour, - FRBCStorageStatus, - FRBCSystemDescription, - FRBCTimerStatus, - FRBCUsageForecast, -) -from s2python.ppbc import PPBCScheduleInstruction - -from s2python.validate_values_mixin import S2Message -from s2python.s2_validation_error import S2ValidationError - - -LOGGER = logging.getLogger(__name__) -S2MessageType = str - -M = TypeVar("M", bound=S2Message) - - -# May be generated with development_utilities/generate_s2_message_type_to_class.py -TYPE_TO_MESSAGE_CLASS: Dict[str, Type[S2Message]] = { - "FRBC.ActuatorStatus": FRBCActuatorStatus, - "FRBC.FillLevelTargetProfile": FRBCFillLevelTargetProfile, - "FRBC.Instruction": FRBCInstruction, - "FRBC.LeakageBehaviour": FRBCLeakageBehaviour, - "FRBC.StorageStatus": FRBCStorageStatus, - "FRBC.SystemDescription": FRBCSystemDescription, - "FRBC.TimerStatus": FRBCTimerStatus, - "FRBC.UsageForecast": FRBCUsageForecast, - "PPBC.ScheduleInstruction": PPBCScheduleInstruction, - "Handshake": Handshake, - "HandshakeResponse": HandshakeResponse, - "InstructionStatusUpdate": InstructionStatusUpdate, - "PowerForecast": PowerForecast, - "PowerMeasurement": PowerMeasurement, - "ReceptionStatus": ReceptionStatus, - "ResourceManagerDetails": ResourceManagerDetails, - "RevokeObject": RevokeObject, - "SelectControlType": SelectControlType, - "SessionRequest": SessionRequest, -} - - -class S2Parser: - @staticmethod - def _parse_json_if_required(unparsed_message: Union[dict, str, bytes]) -> dict: - if isinstance(unparsed_message, (str, bytes)): - return json.loads(unparsed_message) - return unparsed_message - - @staticmethod - def parse_as_any_message(unparsed_message: Union[dict, str, bytes]) -> S2Message: - """Parse the message as any S2 python message regardless of message type. - - :param unparsed_message: The message as a JSON-formatted string or as a json-parsed dictionary. - :raises: S2ValidationError, json.JSONDecodeError - :return: The parsed S2 message if no errors were found. - """ - message_json = S2Parser._parse_json_if_required(unparsed_message) - message_type = S2Parser.parse_message_type(message_json) - - if message_type not in TYPE_TO_MESSAGE_CLASS: - raise S2ValidationError( - None, - message_json, - f"Unable to parse {message_type} as an S2 message. Type unknown.", - None, - ) - - 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: - """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. - :param as_message: The type of message that is expected within the `message` - :raises: S2ValidationError, json.JSONDecodeError - :return: The parsed S2 message if no errors were found. - """ - message_json = S2Parser._parse_json_if_required(unparsed_message) - return as_message.from_dict(message_json) - - @staticmethod - def parse_message_type( - unparsed_message: Union[dict, str, bytes], - ) -> Optional[S2MessageType]: - """Parse only the message type from the unparsed message. - - This is useful to call before `parse_as_message` to retrieve the message type and allows for strictly-typed - parsing. - - :param unparsed_message: The message as a JSON-formatted string or as a JSON-parsed dictionary. - :raises: json.JSONDecodeError - :return: The parsed S2 message type if no errors were found. - """ - message_json = S2Parser._parse_json_if_required(unparsed_message) - - return message_json.get("message_type") diff --git a/build/lib/s2python/s2_validation_error.py b/build/lib/s2python/s2_validation_error.py deleted file mode 100644 index 8ab7664..0000000 --- a/build/lib/s2python/s2_validation_error.py +++ /dev/null @@ -1,13 +0,0 @@ -from dataclasses import dataclass -from typing import Union, Type, Optional - -from pydantic import ValidationError -from pydantic.v1.error_wrappers import ValidationError as ValidationErrorV1 - - -@dataclass -class S2ValidationError(Exception): - class_: Optional[Type] - obj: object - msg: str - pydantic_validation_error: Union[ValidationErrorV1, ValidationError, TypeError, None] diff --git a/build/lib/s2python/utils.py b/build/lib/s2python/utils.py deleted file mode 100644 index b4f78ed..0000000 --- a/build/lib/s2python/utils.py +++ /dev/null @@ -1,8 +0,0 @@ -from typing import Generator, Tuple, List, TypeVar - -P = TypeVar("P") - - -def pairwise(arr: List[P]) -> Generator[Tuple[P, P], None, None]: - for i in range(max(len(arr) - 1, 0)): - yield arr[i], arr[i + 1] diff --git a/build/lib/s2python/validate_values_mixin.py b/build/lib/s2python/validate_values_mixin.py deleted file mode 100644 index 7d0d9d6..0000000 --- a/build/lib/s2python/validate_values_mixin.py +++ /dev/null @@ -1,70 +0,0 @@ -from typing import TypeVar, Generic, Type, Callable, Any, Union, AbstractSet, Mapping, List, Dict - -from pydantic import BaseModel, ValidationError # pylint: disable=no-name-in-module -from pydantic.v1.error_wrappers import display_errors # pylint: disable=no-name-in-module - -from s2python.s2_validation_error import S2ValidationError - -B_co = TypeVar("B_co", bound=BaseModel, covariant=True) - -IntStr = Union[int, str] -AbstractSetIntStr = AbstractSet[IntStr] -MappingIntStrAny = Mapping[IntStr, Any] - - -C = TypeVar("C", bound="BaseModel") - - -class S2Message(BaseModel, Generic[C]): - def to_json(self: C) -> str: - try: - return self.model_dump_json(by_alias=True, exclude_none=True) - except (ValidationError, TypeError) as e: - raise S2ValidationError( - type(self), self, "Pydantic raised a format validation error.", e - ) from e - - def to_dict(self: C) -> Dict: - return self.model_dump() - - @classmethod - def from_json(cls: Type[C], json_str: str) -> C: - gen_model: C = cls.model_validate_json(json_str) - return gen_model - - @classmethod - def from_dict(cls: Type[C], json_dict: dict) -> C: - gen_model: C = cls.model_validate(json_dict) - return gen_model - - -def convert_to_s2exception(f: Callable) -> Callable: - def inner(*args: List[Any], **kwargs: Dict[str, Any]) -> Any: - try: - return f(*args, **kwargs) - except ValidationError as e: - if isinstance(args[0], BaseModel): - class_type = type(args[0]) - args = args[1:] - else: - class_type = None - - raise S2ValidationError(class_type, args, display_errors(e.errors()), e) from e # type: ignore[arg-type] - except TypeError as e: - raise S2ValidationError(None, args, str(e), e) from e - - inner.__doc__ = f.__doc__ - inner.__annotations__ = f.__annotations__ - - return inner - - -def catch_and_convert_exceptions(input_class: Type[S2Message[B_co]]) -> Type[S2Message[B_co]]: - input_class.__init__ = convert_to_s2exception(input_class.__init__) # type: ignore[method-assign] - input_class.__setattr__ = convert_to_s2exception(input_class.__setattr__) # type: ignore[method-assign] - input_class.model_validate_json = convert_to_s2exception( # type: ignore[method-assign] - input_class.model_validate_json - ) - input_class.model_validate = convert_to_s2exception(input_class.model_validate) # type: ignore[method-assign] - - return input_class diff --git a/build/lib/s2python/version.py b/build/lib/s2python/version.py deleted file mode 100644 index 3789fe8..0000000 --- a/build/lib/s2python/version.py +++ /dev/null @@ -1,3 +0,0 @@ -VERSION = "0.2.0" - -S2_VERSION = "0.0.2-beta" diff --git a/development_utilities/gen_unit_test_template.py b/development_utilities/gen_unit_test_template.py index d246d88..9d91d49 100644 --- a/development_utilities/gen_unit_test_template.py +++ b/development_utilities/gen_unit_test_template.py @@ -190,7 +190,10 @@ def dump_test_data_as_constructor_field_for(test_data, field_type: Type) -> str: ) elif field_type is uuid.UUID: value = f'uuid.UUID("{test_data}")' + elif type(field_type).__name__ == "_LiteralGenericAlias": + value = field_type.__args__[0] else: + breakpoint() raise RuntimeError( f"Please implement dump test data for field type {field_type}" ) @@ -239,6 +242,8 @@ def dump_test_data_as_json_field_for(test_data, field_type: Type): value = test_data.isoformat() elif field_type is uuid.UUID: value = str(test_data) + elif type(field_type).__name__ == "_LiteralGenericAlias": + value = test_data else: raise RuntimeError( f"Please implement dump test data to json for field type {field_type}" diff --git a/src/s2python/generated/gen_s2.py b/src/s2python/generated/gen_s2.py index c7febd6..f665886 100644 --- a/src/s2python/generated/gen_s2.py +++ b/src/s2python/generated/gen_s2.py @@ -20,805 +20,805 @@ class Duration(RootModel[conint(ge=0)]): - root: conint(ge=0) = Field(..., description='Duration in milliseconds') + root: conint(ge=0) = Field(..., description="Duration in milliseconds") -class ID(RootModel[constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}')]): - root: constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') +class ID(RootModel[constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}")]): + root: constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}") = Field(..., description="UUID") class Currency(Enum): - AED = 'AED' - ANG = 'ANG' - AUD = 'AUD' - CHE = 'CHE' - CHF = 'CHF' - CHW = 'CHW' - EUR = 'EUR' - GBP = 'GBP' - LBP = 'LBP' - LKR = 'LKR' - LRD = 'LRD' - LSL = 'LSL' - LYD = 'LYD' - MAD = 'MAD' - MDL = 'MDL' - MGA = 'MGA' - MKD = 'MKD' - MMK = 'MMK' - MNT = 'MNT' - MOP = 'MOP' - MRO = 'MRO' - MUR = 'MUR' - MVR = 'MVR' - MWK = 'MWK' - MXN = 'MXN' - MXV = 'MXV' - MYR = 'MYR' - MZN = 'MZN' - NAD = 'NAD' - NGN = 'NGN' - NIO = 'NIO' - NOK = 'NOK' - NPR = 'NPR' - NZD = 'NZD' - OMR = 'OMR' - PAB = 'PAB' - PEN = 'PEN' - PGK = 'PGK' - PHP = 'PHP' - PKR = 'PKR' - PLN = 'PLN' - PYG = 'PYG' - QAR = 'QAR' - RON = 'RON' - RSD = 'RSD' - RUB = 'RUB' - RWF = 'RWF' - SAR = 'SAR' - SBD = 'SBD' - SCR = 'SCR' - SDG = 'SDG' - SEK = 'SEK' - SGD = 'SGD' - SHP = 'SHP' - SLL = 'SLL' - SOS = 'SOS' - SRD = 'SRD' - SSP = 'SSP' - STD = 'STD' - SYP = 'SYP' - SZL = 'SZL' - THB = 'THB' - TJS = 'TJS' - TMT = 'TMT' - TND = 'TND' - TOP = 'TOP' - TRY = 'TRY' - TTD = 'TTD' - TWD = 'TWD' - TZS = 'TZS' - UAH = 'UAH' - UGX = 'UGX' - USD = 'USD' - USN = 'USN' - UYI = 'UYI' - UYU = 'UYU' - UZS = 'UZS' - VEF = 'VEF' - VND = 'VND' - VUV = 'VUV' - WST = 'WST' - XAG = 'XAG' - XAU = 'XAU' - XBA = 'XBA' - XBB = 'XBB' - XBC = 'XBC' - XBD = 'XBD' - XCD = 'XCD' - XOF = 'XOF' - XPD = 'XPD' - XPF = 'XPF' - XPT = 'XPT' - XSU = 'XSU' - XTS = 'XTS' - XUA = 'XUA' - XXX = 'XXX' - YER = 'YER' - ZAR = 'ZAR' - ZMW = 'ZMW' - ZWL = 'ZWL' + AED = "AED" + ANG = "ANG" + AUD = "AUD" + CHE = "CHE" + CHF = "CHF" + CHW = "CHW" + EUR = "EUR" + GBP = "GBP" + LBP = "LBP" + LKR = "LKR" + LRD = "LRD" + LSL = "LSL" + LYD = "LYD" + MAD = "MAD" + MDL = "MDL" + MGA = "MGA" + MKD = "MKD" + MMK = "MMK" + MNT = "MNT" + MOP = "MOP" + MRO = "MRO" + MUR = "MUR" + MVR = "MVR" + MWK = "MWK" + MXN = "MXN" + MXV = "MXV" + MYR = "MYR" + MZN = "MZN" + NAD = "NAD" + NGN = "NGN" + NIO = "NIO" + NOK = "NOK" + NPR = "NPR" + NZD = "NZD" + OMR = "OMR" + PAB = "PAB" + PEN = "PEN" + PGK = "PGK" + PHP = "PHP" + PKR = "PKR" + PLN = "PLN" + PYG = "PYG" + QAR = "QAR" + RON = "RON" + RSD = "RSD" + RUB = "RUB" + RWF = "RWF" + SAR = "SAR" + SBD = "SBD" + SCR = "SCR" + SDG = "SDG" + SEK = "SEK" + SGD = "SGD" + SHP = "SHP" + SLL = "SLL" + SOS = "SOS" + SRD = "SRD" + SSP = "SSP" + STD = "STD" + SYP = "SYP" + SZL = "SZL" + THB = "THB" + TJS = "TJS" + TMT = "TMT" + TND = "TND" + TOP = "TOP" + TRY = "TRY" + TTD = "TTD" + TWD = "TWD" + TZS = "TZS" + UAH = "UAH" + UGX = "UGX" + USD = "USD" + USN = "USN" + UYI = "UYI" + UYU = "UYU" + UZS = "UZS" + VEF = "VEF" + VND = "VND" + VUV = "VUV" + WST = "WST" + XAG = "XAG" + XAU = "XAU" + XBA = "XBA" + XBB = "XBB" + XBC = "XBC" + XBD = "XBD" + XCD = "XCD" + XOF = "XOF" + XPD = "XPD" + XPF = "XPF" + XPT = "XPT" + XSU = "XSU" + XTS = "XTS" + XUA = "XUA" + XXX = "XXX" + YER = "YER" + ZAR = "ZAR" + ZMW = "ZMW" + ZWL = "ZWL" class SessionRequestType(Enum): - RECONNECT = 'RECONNECT' - TERMINATE = 'TERMINATE' + RECONNECT = "RECONNECT" + TERMINATE = "TERMINATE" class RevokableObjects(Enum): - PEBC_PowerConstraints = 'PEBC.PowerConstraints' - PEBC_EnergyConstraint = 'PEBC.EnergyConstraint' - PEBC_Instruction = 'PEBC.Instruction' - PPBC_PowerProfileDefinition = 'PPBC.PowerProfileDefinition' - PPBC_ScheduleInstruction = 'PPBC.ScheduleInstruction' - PPBC_StartInterruptionInstruction = 'PPBC.StartInterruptionInstruction' - PPBC_EndInterruptionInstruction = 'PPBC.EndInterruptionInstruction' - OMBC_SystemDescription = 'OMBC.SystemDescription' - OMBC_Instruction = 'OMBC.Instruction' - FRBC_SystemDescription = 'FRBC.SystemDescription' - FRBC_Instruction = 'FRBC.Instruction' - DDBC_SystemDescription = 'DDBC.SystemDescription' - DDBC_Instruction = 'DDBC.Instruction' + PEBC_PowerConstraints = "PEBC.PowerConstraints" + PEBC_EnergyConstraint = "PEBC.EnergyConstraint" + PEBC_Instruction = "PEBC.Instruction" + PPBC_PowerProfileDefinition = "PPBC.PowerProfileDefinition" + PPBC_ScheduleInstruction = "PPBC.ScheduleInstruction" + PPBC_StartInterruptionInstruction = "PPBC.StartInterruptionInstruction" + PPBC_EndInterruptionInstruction = "PPBC.EndInterruptionInstruction" + OMBC_SystemDescription = "OMBC.SystemDescription" + OMBC_Instruction = "OMBC.Instruction" + FRBC_SystemDescription = "FRBC.SystemDescription" + FRBC_Instruction = "FRBC.Instruction" + DDBC_SystemDescription = "DDBC.SystemDescription" + DDBC_Instruction = "DDBC.Instruction" class EnergyManagementRole(Enum): - CEM = 'CEM' - RM = 'RM' + CEM = "CEM" + RM = "RM" class ReceptionStatusValues(Enum): - INVALID_DATA = 'INVALID_DATA' - INVALID_MESSAGE = 'INVALID_MESSAGE' - INVALID_CONTENT = 'INVALID_CONTENT' - TEMPORARY_ERROR = 'TEMPORARY_ERROR' - PERMANENT_ERROR = 'PERMANENT_ERROR' - OK = 'OK' + INVALID_DATA = "INVALID_DATA" + INVALID_MESSAGE = "INVALID_MESSAGE" + INVALID_CONTENT = "INVALID_CONTENT" + TEMPORARY_ERROR = "TEMPORARY_ERROR" + PERMANENT_ERROR = "PERMANENT_ERROR" + OK = "OK" class NumberRange(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) start_of_range: float = Field( - ..., description='Number that defines the start of the range' + ..., description="Number that defines the start of the range" ) end_of_range: float = Field( - ..., description='Number that defines the end of the range' + ..., description="Number that defines the end of the range" ) class Transition(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', + description="ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.", ) from_: ID = Field( ..., - alias='from', - description='ID of the OperationMode (exact type differs per ControlType) that should be switched from.', + alias="from", + description="ID of the OperationMode (exact type differs per ControlType) that should be switched from.", ) to: ID = Field( ..., - description='ID of the OperationMode (exact type differs per ControlType) that will be switched to.', + description="ID of the OperationMode (exact type differs per ControlType) that will be switched to.", ) start_timers: List[ID] = Field( ..., - description='List of IDs of Timers that will be (re)started when this transition is initiated', + description="List of IDs of Timers that will be (re)started when this transition is initiated", max_length=1000, min_length=0, ) blocking_timers: List[ID] = Field( ..., - description='List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished', + description="List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished", max_length=1000, min_length=0, ) transition_costs: Optional[float] = Field( None, - description='Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.', + description="Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.", ) transition_duration: Optional[Duration] = Field( None, - description='Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.', + description="Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this Transition may only be used during an abnormal condition (see Clause )', + description="Indicates if this Transition may only be used during an abnormal condition (see Clause )", ) class Timer(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', + description="ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.", ) duration: Duration = Field( ..., - description='The time it takes for the Timer to finish after it has been started', + description="The time it takes for the Timer to finish after it has been started", ) class PEBCPowerEnvelopeElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='The duration of the element') + duration: Duration = Field(..., description="The duration of the element") upper_limit: float = Field( ..., - description='Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.', + description="Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.", ) lower_limit: float = Field( ..., - description='Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.', + description="Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.", ) class FRBCStorageDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.", ) fill_level_label: Optional[str] = Field( None, - description='Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.", ) provides_leakage_behaviour: bool = Field( ..., - description='Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.', + description="Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.", ) provides_fill_level_target_profile: bool = Field( ..., - description='Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.', + description="Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.", ) provides_usage_forecast: bool = Field( ..., - description='Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.', + description="Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.", ) fill_level_range: NumberRange = Field( ..., - description='The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ', + description="The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ", ) class FRBCLeakageBehaviourElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) fill_level_range: NumberRange = Field( ..., - description='The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.', + description="The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.", ) leakage_rate: float = Field( ..., - description='Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.', + description="Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.", ) class FRBCUsageForecastElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) duration: Duration = Field( - ..., description='Indicator for how long the given usage_rate is valid.' + ..., description="Indicator for how long the given usage_rate is valid." ) usage_rate_upper_limit: Optional[float] = Field( None, - description='The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_upper_95PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_upper_68PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_expected: float = Field( ..., - description='The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.', + description="The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_lower_68PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_lower_95PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) usage_rate_lower_limit: Optional[float] = Field( None, - description='The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', + description="The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", ) class FRBCFillLevelTargetProfileElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='The duration of the element.') + duration: Duration = Field(..., description="The duration of the element.") fill_level_range: NumberRange = Field( ..., - description='The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.', + description="The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.", ) class DDBCAverageDemandRateForecastElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='Duration of the element') + duration: Duration = Field(..., description="Duration of the element") demand_rate_upper_limit: Optional[float] = Field( None, - description='The upper limit of the range with a 100 % probability that the demand rate is within that range', + description="The upper limit of the range with a 100 % probability that the demand rate is within that range", ) demand_rate_upper_95PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 95 % probability that the demand rate is within that range', + description="The upper limit of the range with a 95 % probability that the demand rate is within that range", ) demand_rate_upper_68PPR: Optional[float] = Field( None, - description='The upper limit of the range with a 68 % probability that the demand rate is within that range', + description="The upper limit of the range with a 68 % probability that the demand rate is within that range", ) demand_rate_expected: float = Field( ..., - description='The most likely value for the demand rate; the expected increase or decrease of the fill_level per second', + description="The most likely value for the demand rate; the expected increase or decrease of the fill_level per second", ) demand_rate_lower_68PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 68 % probability that the demand rate is within that range', + description="The lower limit of the range with a 68 % probability that the demand rate is within that range", ) demand_rate_lower_95PPR: Optional[float] = Field( None, - description='The lower limit of the range with a 95 % probability that the demand rate is within that range', + description="The lower limit of the range with a 95 % probability that the demand rate is within that range", ) demand_rate_lower_limit: Optional[float] = Field( None, - description='The lower limit of the range with a 100 % probability that the demand rate is within that range', + description="The lower limit of the range with a 100 % probability that the demand rate is within that range", ) class RoleType(Enum): - ENERGY_PRODUCER = 'ENERGY_PRODUCER' - ENERGY_CONSUMER = 'ENERGY_CONSUMER' - ENERGY_STORAGE = 'ENERGY_STORAGE' + ENERGY_PRODUCER = "ENERGY_PRODUCER" + ENERGY_CONSUMER = "ENERGY_CONSUMER" + ENERGY_STORAGE = "ENERGY_STORAGE" class Commodity(Enum): - GAS = 'GAS' - HEAT = 'HEAT' - ELECTRICITY = 'ELECTRICITY' - OIL = 'OIL' + GAS = "GAS" + HEAT = "HEAT" + ELECTRICITY = "ELECTRICITY" + OIL = "OIL" class CommodityQuantity(Enum): - ELECTRIC_POWER_L1 = 'ELECTRIC.POWER.L1' - ELECTRIC_POWER_L2 = 'ELECTRIC.POWER.L2' - ELECTRIC_POWER_L3 = 'ELECTRIC.POWER.L3' - ELECTRIC_POWER_3_PHASE_SYMMETRIC = 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' - NATURAL_GAS_FLOW_RATE = 'NATURAL_GAS.FLOW_RATE' - HYDROGEN_FLOW_RATE = 'HYDROGEN.FLOW_RATE' - HEAT_TEMPERATURE = 'HEAT.TEMPERATURE' - HEAT_FLOW_RATE = 'HEAT.FLOW_RATE' - HEAT_THERMAL_POWER = 'HEAT.THERMAL_POWER' - OIL_FLOW_RATE = 'OIL.FLOW_RATE' + ELECTRIC_POWER_L1 = "ELECTRIC.POWER.L1" + ELECTRIC_POWER_L2 = "ELECTRIC.POWER.L2" + ELECTRIC_POWER_L3 = "ELECTRIC.POWER.L3" + ELECTRIC_POWER_3_PHASE_SYMMETRIC = "ELECTRIC.POWER.3_PHASE_SYMMETRIC" + NATURAL_GAS_FLOW_RATE = "NATURAL_GAS.FLOW_RATE" + HYDROGEN_FLOW_RATE = "HYDROGEN.FLOW_RATE" + HEAT_TEMPERATURE = "HEAT.TEMPERATURE" + HEAT_FLOW_RATE = "HEAT.FLOW_RATE" + HEAT_THERMAL_POWER = "HEAT.THERMAL_POWER" + OIL_FLOW_RATE = "OIL.FLOW_RATE" class InstructionStatus(Enum): - NEW = 'NEW' - ACCEPTED = 'ACCEPTED' - REJECTED = 'REJECTED' - REVOKED = 'REVOKED' - STARTED = 'STARTED' - SUCCEEDED = 'SUCCEEDED' - ABORTED = 'ABORTED' + NEW = "NEW" + ACCEPTED = "ACCEPTED" + REJECTED = "REJECTED" + REVOKED = "REVOKED" + STARTED = "STARTED" + SUCCEEDED = "SUCCEEDED" + ABORTED = "ABORTED" class ControlType(Enum): - POWER_ENVELOPE_BASED_CONTROL = 'POWER_ENVELOPE_BASED_CONTROL' - POWER_PROFILE_BASED_CONTROL = 'POWER_PROFILE_BASED_CONTROL' - OPERATION_MODE_BASED_CONTROL = 'OPERATION_MODE_BASED_CONTROL' - FILL_RATE_BASED_CONTROL = 'FILL_RATE_BASED_CONTROL' - DEMAND_DRIVEN_BASED_CONTROL = 'DEMAND_DRIVEN_BASED_CONTROL' - NOT_CONTROLABLE = 'NOT_CONTROLABLE' - NO_SELECTION = 'NO_SELECTION' + POWER_ENVELOPE_BASED_CONTROL = "POWER_ENVELOPE_BASED_CONTROL" + POWER_PROFILE_BASED_CONTROL = "POWER_PROFILE_BASED_CONTROL" + OPERATION_MODE_BASED_CONTROL = "OPERATION_MODE_BASED_CONTROL" + FILL_RATE_BASED_CONTROL = "FILL_RATE_BASED_CONTROL" + DEMAND_DRIVEN_BASED_CONTROL = "DEMAND_DRIVEN_BASED_CONTROL" + NOT_CONTROLABLE = "NOT_CONTROLABLE" + NO_SELECTION = "NO_SELECTION" class PEBCPowerEnvelopeLimitType(Enum): - UPPER_LIMIT = 'UPPER_LIMIT' - LOWER_LIMIT = 'LOWER_LIMIT' + UPPER_LIMIT = "UPPER_LIMIT" + LOWER_LIMIT = "LOWER_LIMIT" class PEBCPowerEnvelopeConsequenceType(Enum): - VANISH = 'VANISH' - DEFER = 'DEFER' + VANISH = "VANISH" + DEFER = "DEFER" class PPBCPowerSequenceStatus(Enum): - NOT_SCHEDULED = 'NOT_SCHEDULED' - SCHEDULED = 'SCHEDULED' - EXECUTING = 'EXECUTING' - INTERRUPTED = 'INTERRUPTED' - FINISHED = 'FINISHED' - ABORTED = 'ABORTED' + NOT_SCHEDULED = "NOT_SCHEDULED" + SCHEDULED = "SCHEDULED" + EXECUTING = "EXECUTING" + INTERRUPTED = "INTERRUPTED" + FINISHED = "FINISHED" + ABORTED = "ABORTED" class OMBCTimerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.TimerStatus'] = 'OMBC.TimerStatus' + message_type: Literal["OMBC.TimerStatus"] = "OMBC.TimerStatus" message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') + timer_id: ID = Field(..., description="The ID of the timer this message refers to") finished_at: AwareDatetime = Field( ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", ) class FRBCTimerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.TimerStatus'] = 'FRBC.TimerStatus' + message_type: Literal["FRBC.TimerStatus"] = "FRBC.TimerStatus" message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') + timer_id: ID = Field(..., description="The ID of the timer this message refers to") actuator_id: ID = Field( - ..., description='The ID of the actuator the timer belongs to' + ..., description="The ID of the actuator the timer belongs to" ) finished_at: AwareDatetime = Field( ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", ) class DDBCTimerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.TimerStatus'] = 'DDBC.TimerStatus' + message_type: Literal["DDBC.TimerStatus"] = "DDBC.TimerStatus" message_id: ID - timer_id: ID = Field(..., description='The ID of the timer this message refers to') + timer_id: ID = Field(..., description="The ID of the timer this message refers to") actuator_id: ID = Field( - ..., description='The ID of the actuator the timer belongs to' + ..., description="The ID of the actuator the timer belongs to" ) finished_at: AwareDatetime = Field( ..., - description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', + description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", ) class SelectControlType(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['SelectControlType'] = 'SelectControlType' + message_type: Literal["SelectControlType"] = "SelectControlType" message_id: ID control_type: ControlType = Field( ..., - description='The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails', + description="The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails", ) class SessionRequest(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['SessionRequest'] = 'SessionRequest' + message_type: Literal["SessionRequest"] = "SessionRequest" message_id: ID - request: SessionRequestType = Field(..., description='The type of request') + request: SessionRequestType = Field(..., description="The type of request") diagnostic_label: Optional[str] = Field( None, - description='Optional field for a human readible descirption for debugging purposes', + description="Optional field for a human readible descirption for debugging purposes", ) class RevokeObject(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['RevokeObject'] = 'RevokeObject' + message_type: Literal["RevokeObject"] = "RevokeObject" message_id: ID object_type: RevokableObjects = Field( - ..., description='The type of object that needs to be revoked' + ..., description="The type of object that needs to be revoked" ) - object_id: ID = Field(..., description='The ID of object that needs to be revoked') + object_id: ID = Field(..., description="The ID of object that needs to be revoked") class Handshake(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['Handshake'] = 'Handshake' + message_type: Literal["Handshake"] = "Handshake" message_id: ID role: EnergyManagementRole = Field( - ..., description='The role of the sender of this message' + ..., description="The role of the sender of this message" ) supported_protocol_versions: Optional[List[str]] = Field( None, - description='Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.', + description="Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.", min_length=1, ) class HandshakeResponse(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['HandshakeResponse'] = 'HandshakeResponse' + message_type: Literal["HandshakeResponse"] = "HandshakeResponse" message_id: ID selected_protocol_version: str = Field( - ..., description='The protocol version the CEM selected for this session' + ..., description="The protocol version the CEM selected for this session" ) class ReceptionStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['ReceptionStatus'] = 'ReceptionStatus' + message_type: Literal["ReceptionStatus"] = "ReceptionStatus" subject_message_id: ID = Field( - ..., description='The message this ReceptionStatus refers to' + ..., description="The message this ReceptionStatus refers to" ) status: ReceptionStatusValues = Field( - ..., description='Enumeration of status values' + ..., description="Enumeration of status values" ) diagnostic_label: Optional[str] = Field( None, - description='Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.', + description="Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.", ) class InstructionStatusUpdate(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['InstructionStatusUpdate'] = 'InstructionStatusUpdate' + message_type: Literal["InstructionStatusUpdate"] = "InstructionStatusUpdate" message_id: ID instruction_id: ID = Field( - ..., description='ID of this instruction (as provided by the CEM) ' + ..., description="ID of this instruction (as provided by the CEM) " ) status_type: InstructionStatus = Field( - ..., description='Present status of this instruction.' + ..., description="Present status of this instruction." ) timestamp: AwareDatetime = Field( - ..., description='Timestamp when status_type has changed the last time.' + ..., description="Timestamp when status_type has changed the last time." ) class PEBCEnergyConstraint(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PEBC.EnergyConstraint'] = 'PEBC.EnergyConstraint' + message_type: Literal["PEBC.EnergyConstraint"] = "PEBC.EnergyConstraint" message_id: ID id: ID = Field( ..., - description='Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) valid_from: AwareDatetime = Field( ..., - description='Moment this PEBC.EnergyConstraints information starts to be valid', + description="Moment this PEBC.EnergyConstraints information starts to be valid", ) valid_until: AwareDatetime = Field( ..., - description='Moment until this PEBC.EnergyConstraints information is valid.', + description="Moment until this PEBC.EnergyConstraints information is valid.", ) upper_average_power: float = Field( ..., - description='Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', + description="Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.", ) lower_average_power: float = Field( ..., - description='Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', + description="Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.", ) commodity_quantity: CommodityQuantity = Field( ..., - description='Type of power quantity which applies to upper_average_power and lower_average_power', + description="Type of power quantity which applies to upper_average_power and lower_average_power", ) class PPBCScheduleInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.ScheduleInstruction'] = 'PPBC.ScheduleInstruction' + message_type: Literal["PPBC.ScheduleInstruction"] = "PPBC.ScheduleInstruction" message_id: ID id: ID = Field( ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', + description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', + description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.", ) power_sequence_id: ID = Field( ..., - description='ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.', + description="ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class PPBCStartInterruptionInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.StartInterruptionInstruction'] = ( - 'PPBC.StartInterruptionInstruction' + message_type: Literal["PPBC.StartInterruptionInstruction"] = ( + "PPBC.StartInterruptionInstruction" ) message_id: ID id: ID = Field( ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.', + description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.', + description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.", ) power_sequence_id: ID = Field( - ..., description='ID of the PPBC.PowerSequence that the CEM wants to interrupt.' + ..., description="ID of the PPBC.PowerSequence that the CEM wants to interrupt." ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class PPBCEndInterruptionInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.EndInterruptionInstruction'] = ( - 'PPBC.EndInterruptionInstruction' + message_type: Literal["PPBC.EndInterruptionInstruction"] = ( + "PPBC.EndInterruptionInstruction" ) message_id: ID id: ID = Field( ..., - description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.', + description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.', + description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.", ) power_sequence_id: ID = Field( ..., - description='ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.', + description="ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class OMBCStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.Status'] = 'OMBC.Status' + message_type: Literal["OMBC.Status"] = "OMBC.Status" message_id: ID active_operation_mode_id: ID = Field( - ..., description='ID of the active OMBC.OperationMode.' + ..., description="ID of the active OMBC.OperationMode." ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', + description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.", ) previous_operation_mode_id: Optional[ID] = Field( None, - description='ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', + description="ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.", ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description='Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', + description="Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.", ) class OMBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.Instruction'] = 'OMBC.Instruction' + message_type: Literal["OMBC.Instruction"] = "OMBC.Instruction" message_id: ID id: ID = Field( ..., - description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) operation_mode_id: ID = Field( - ..., description='ID of the OMBC.OperationMode that should be activated' + ..., description="ID of the OMBC.OperationMode that should be activated" ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', + description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) class FRBCActuatorStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.ActuatorStatus'] = 'FRBC.ActuatorStatus' + message_type: Literal["FRBC.ActuatorStatus"] = "FRBC.ActuatorStatus" message_id: ID actuator_id: ID = Field( - ..., description='ID of the actuator this messages refers to' + ..., description="ID of the actuator this messages refers to" ) active_operation_mode_id: ID = Field( - ..., description='ID of the FRBC.OperationMode that is presently active.' + ..., description="ID of the FRBC.OperationMode that is presently active." ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.', + description="The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.", ) previous_operation_mode_id: Optional[ID] = Field( None, - description='ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', + description="ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.", ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description='Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', + description="Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.", ) class FRBCStorageStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.StorageStatus'] = 'FRBC.StorageStatus' + message_type: Literal["FRBC.StorageStatus"] = "FRBC.StorageStatus" message_id: ID present_fill_level: float = Field( - ..., description='Present fill level of the Storage' + ..., description="Present fill level of the Storage" ) class FRBCLeakageBehaviour(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.LeakageBehaviour'] = 'FRBC.LeakageBehaviour' + message_type: Literal["FRBC.LeakageBehaviour"] = "FRBC.LeakageBehaviour" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.", ) elements: List[FRBCLeakageBehaviourElement] = Field( ..., - description='List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.', + description="List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.", max_length=288, min_length=1, ) @@ -826,46 +826,46 @@ class FRBCLeakageBehaviour(BaseModel): class FRBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.Instruction'] = 'FRBC.Instruction' + message_type: Literal["FRBC.Instruction"] = "FRBC.Instruction" message_id: ID id: ID = Field( ..., - description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) actuator_id: ID = Field( - ..., description='ID of the actuator this instruction belongs to.' + ..., description="ID of the actuator this instruction belongs to." ) operation_mode: ID = Field( - ..., description='ID of the FRBC.OperationMode that should be activated.' + ..., description="ID of the FRBC.OperationMode that should be activated." ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', + description="The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition.', + description="Indicates if this is an instruction during an abnormal condition.", ) class FRBCUsageForecast(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.UsageForecast'] = 'FRBC.UsageForecast' + message_type: Literal["FRBC.UsageForecast"] = "FRBC.UsageForecast" message_id: ID start_time: AwareDatetime = Field( - ..., description='Time at which the FRBC.UsageForecast starts.' + ..., description="Time at which the FRBC.UsageForecast starts." ) elements: List[FRBCUsageForecastElement] = Field( ..., - description='Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.', + description="Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -873,16 +873,16 @@ class FRBCUsageForecast(BaseModel): class FRBCFillLevelTargetProfile(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.FillLevelTargetProfile'] = 'FRBC.FillLevelTargetProfile' + message_type: Literal["FRBC.FillLevelTargetProfile"] = "FRBC.FillLevelTargetProfile" message_id: ID start_time: AwareDatetime = Field( - ..., description='Time at which the FRBC.FillLevelTargetProfile starts.' + ..., description="Time at which the FRBC.FillLevelTargetProfile starts." ) elements: List[FRBCFillLevelTargetProfileElement] = Field( ..., - description='List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.', + description="List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -890,71 +890,71 @@ class FRBCFillLevelTargetProfile(BaseModel): class DDBCActuatorStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.ActuatorStatus'] = 'DDBC.ActuatorStatus' + message_type: Literal["DDBC.ActuatorStatus"] = "DDBC.ActuatorStatus" message_id: ID actuator_id: ID = Field( - ..., description='ID of the actuator this messages refers to' + ..., description="ID of the actuator this messages refers to" ) active_operation_mode_id: ID = Field( ..., - description='The operation mode that is presently active for this actuator.', + description="The operation mode that is presently active for this actuator.", ) operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.', + description="The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.", ) previous_operation_mode_id: Optional[ID] = Field( None, - description='ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', + description="ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.", ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description='Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', + description="Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.", ) class DDBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.Instruction'] = 'DDBC.Instruction' + message_type: Literal["DDBC.Instruction"] = "DDBC.Instruction" message_id: ID id: ID = Field( ..., - description='Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition', + description="Indicates if this is an instruction during an abnormal condition", ) actuator_id: ID = Field( - ..., description='ID of the actuator this Instruction belongs to.' + ..., description="ID of the actuator this Instruction belongs to." ) - operation_mode_id: ID = Field(..., description='ID of the DDBC.OperationMode') + operation_mode_id: ID = Field(..., description="ID of the DDBC.OperationMode") operation_mode_factor: float = Field( ..., - description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', + description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.", ) class DDBCAverageDemandRateForecast(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.AverageDemandRateForecast'] = ( - 'DDBC.AverageDemandRateForecast' + message_type: Literal["DDBC.AverageDemandRateForecast"] = ( + "DDBC.AverageDemandRateForecast" ) message_id: ID - start_time: AwareDatetime = Field(..., description='Start time of the profile.') + start_time: AwareDatetime = Field(..., description="Start time of the profile.") elements: List[DDBCAverageDemandRateForecastElement] = Field( ..., - description='Elements of the profile. Elements must be placed in chronological order.', + description="Elements of the profile. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -962,84 +962,84 @@ class DDBCAverageDemandRateForecast(BaseModel): class PowerValue(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the value refers to' + ..., description="The power quantity the value refers to" ) value: float = Field( ..., - description='Power value expressed in the unit associated with the CommodityQuantity', + description="Power value expressed in the unit associated with the CommodityQuantity", ) class PowerForecastValue(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) value_upper_limit: Optional[float] = Field( None, - description='The upper boundary of the range with 100 % certainty the power value is in it', + description="The upper boundary of the range with 100 % certainty the power value is in it", ) value_upper_95PPR: Optional[float] = Field( None, - description='The upper boundary of the range with 95 % certainty the power value is in it', + description="The upper boundary of the range with 95 % certainty the power value is in it", ) value_upper_68PPR: Optional[float] = Field( None, - description='The upper boundary of the range with 68 % certainty the power value is in it', + description="The upper boundary of the range with 68 % certainty the power value is in it", ) - value_expected: float = Field(..., description='The expected power value.') + value_expected: float = Field(..., description="The expected power value.") value_lower_68PPR: Optional[float] = Field( None, - description='The lower boundary of the range with 68 % certainty the power value is in it', + description="The lower boundary of the range with 68 % certainty the power value is in it", ) value_lower_95PPR: Optional[float] = Field( None, - description='The lower boundary of the range with 95 % certainty the power value is in it', + description="The lower boundary of the range with 95 % certainty the power value is in it", ) value_lower_limit: Optional[float] = Field( None, - description='The lower boundary of the range with 100 % certainty the power value is in it', + description="The lower boundary of the range with 100 % certainty the power value is in it", ) commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the value refers to' + ..., description="The power quantity the value refers to" ) class PowerRange(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) start_of_range: float = Field( - ..., description='Power value that defines the start of the range.' + ..., description="Power value that defines the start of the range." ) end_of_range: float = Field( - ..., description='Power value that defines the end of the range.' + ..., description="Power value that defines the end of the range." ) commodity_quantity: CommodityQuantity = Field( - ..., description='The power quantity the values refer to' + ..., description="The power quantity the values refer to" ) class Role(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) role: RoleType = Field( - ..., description='Role type of the Resource Manager for the given commodity' + ..., description="Role type of the Resource Manager for the given commodity" ) - commodity: Commodity = Field(..., description='Commodity the role refers to.') + commodity: Commodity = Field(..., description="Commodity the role refers to.") class PowerForecastElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - duration: Duration = Field(..., description='Duration of the PowerForecastElement') + duration: Duration = Field(..., description="Duration of the PowerForecastElement") power_values: List[PowerForecastValue] = Field( ..., - description='The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.', + description="The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.", max_length=10, min_length=1, ) @@ -1047,39 +1047,39 @@ class PowerForecastElement(BaseModel): class PEBCAllowedLimitRange(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) commodity_quantity: CommodityQuantity = Field( - ..., description='Type of power quantity this PEBC.AllowedLimitRange applies to' + ..., description="Type of power quantity this PEBC.AllowedLimitRange applies to" ) limit_type: PEBCPowerEnvelopeLimitType = Field( ..., - description='Indicates if this ranges applies to the upper limit or the lower limit', + description="Indicates if this ranges applies to the upper limit or the lower limit", ) range_boundary: NumberRange = Field( ..., - description='Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ', + description="Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition', + description="Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition", ) class PEBCPowerEnvelope(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) commodity_quantity: CommodityQuantity = Field( - ..., description='Type of power quantity this PEBC.PowerEnvelope applies to' + ..., description="Type of power quantity this PEBC.PowerEnvelope applies to" ) power_envelope_elements: List[PEBCPowerEnvelopeElement] = Field( ..., - description='The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.', + description="The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -1087,14 +1087,14 @@ class PEBCPowerEnvelope(BaseModel): class PPBCPowerSequenceElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) duration: Duration = Field( - ..., description='Duration of the PPBC.PowerSequenceElement.' + ..., description="Duration of the PPBC.PowerSequenceElement." ) power_values: List[PowerForecastValue] = Field( ..., - description='The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.', + description="The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.", max_length=10, min_length=1, ) @@ -1102,163 +1102,163 @@ class PPBCPowerSequenceElement(BaseModel): class PPBCPowerSequenceContainerStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) power_profile_id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ', + description="ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ", ) sequence_container_id: ID = Field( ..., - description='ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.', + description="ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.", ) selected_sequence_id: Optional[ID] = Field( None, - description='ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.', + description="ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.", ) progress: Optional[Duration] = Field( None, - description='Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.', + description="Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.", ) status: PPBCPowerSequenceStatus = Field( - ..., description='Status of the selected PPBC.PowerSequence' + ..., description="Status of the selected PPBC.PowerSequence" ) class OMBCOperationMode(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", ) power_ranges: List[PowerRange] = Field( ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", max_length=10, min_length=1, ) running_costs: Optional[NumberRange] = Field( None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this OMBC.OperationMode may only be used during an abnormal condition.', + description="Indicates if this OMBC.OperationMode may only be used during an abnormal condition.", ) class FRBCOperationModeElement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) fill_level_range: NumberRange = Field( ..., - description='The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.', + description="The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.", ) fill_rate: NumberRange = Field( ..., - description='Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ', + description="Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ", ) power_ranges: List[PowerRange] = Field( ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", max_length=10, min_length=1, ) running_costs: Optional[NumberRange] = Field( None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", ) class DDBCOperationMode(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) Id: ID = Field( ..., - description='ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.', + description="ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", ) power_ranges: List[PowerRange] = Field( ..., - description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', + description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", max_length=10, min_length=1, ) supply_range: NumberRange = Field( ..., - description='The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.', + description="The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.", ) running_costs: Optional[NumberRange] = Field( None, - description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', + description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this DDBC.OperationMode may only be used during an abnormal condition.', + description="Indicates if this DDBC.OperationMode may only be used during an abnormal condition.", ) class ResourceManagerDetails(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['ResourceManagerDetails'] = 'ResourceManagerDetails' + message_type: Literal["ResourceManagerDetails"] = "ResourceManagerDetails" message_id: ID resource_id: ID = Field( ..., - description='Identifier of the Resource Manager. Must be unique within the scope of the CEM.', + description="Identifier of the Resource Manager. Must be unique within the scope of the CEM.", ) - name: Optional[str] = Field(None, description='Human readable name given by user') + name: Optional[str] = Field(None, description="Human readable name given by user") roles: List[Role] = Field( ..., - description='Each Resource Manager provides one or more energy Roles', + description="Each Resource Manager provides one or more energy Roles", max_length=3, min_length=1, ) - manufacturer: Optional[str] = Field(None, description='Name of Manufacturer') + manufacturer: Optional[str] = Field(None, description="Name of Manufacturer") model: Optional[str] = Field( None, - description='Name of the model of the device (provided by the manufacturer)', + description="Name of the model of the device (provided by the manufacturer)", ) serial_number: Optional[str] = Field( - None, description='Serial number of the device (provided by the manufacturer)' + None, description="Serial number of the device (provided by the manufacturer)" ) firmware_version: Optional[str] = Field( None, - description='Version identifier of the firmware used in the device (provided by the manufacturer)', + description="Version identifier of the firmware used in the device (provided by the manufacturer)", ) instruction_processing_delay: Duration = Field( ..., - description='The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction', + description="The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction", ) available_control_types: List[ControlType] = Field( ..., - description='The control types supported by this Resource Manager.', + description="The control types supported by this Resource Manager.", max_length=5, min_length=1, ) currency: Optional[Currency] = Field( None, - description='Currency to be used for all information regarding costs. Mandatory if cost information is published.', + description="Currency to be used for all information regarding costs. Mandatory if cost information is published.", ) provides_forecast: bool = Field( ..., - description='Indicates whether the ResourceManager is able to provide PowerForecasts', + description="Indicates whether the ResourceManager is able to provide PowerForecasts", ) provides_power_measurement_types: List[CommodityQuantity] = Field( ..., - description='Array of all CommodityQuantities that this Resource Manager can provide measurements for. ', + description="Array of all CommodityQuantities that this Resource Manager can provide measurements for. ", max_length=10, min_length=1, ) @@ -1266,16 +1266,16 @@ class ResourceManagerDetails(BaseModel): class PowerMeasurement(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PowerMeasurement'] = 'PowerMeasurement' + message_type: Literal["PowerMeasurement"] = "PowerMeasurement" message_id: ID measurement_timestamp: AwareDatetime = Field( - ..., description='Timestamp when PowerValues were measured.' + ..., description="Timestamp when PowerValues were measured." ) values: List[PowerValue] = Field( ..., - description='Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).', + description="Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).", max_length=10, min_length=1, ) @@ -1283,16 +1283,16 @@ class PowerMeasurement(BaseModel): class PowerForecast(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PowerForecast'] = 'PowerForecast' + message_type: Literal["PowerForecast"] = "PowerForecast" message_id: ID start_time: AwareDatetime = Field( - ..., description='Start time of time period that is covered by the profile.' + ..., description="Start time of time period that is covered by the profile." ) elements: List[PowerForecastElement] = Field( ..., - description='Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.', + description="Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) @@ -1300,27 +1300,27 @@ class PowerForecast(BaseModel): class PEBCPowerConstraints(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PEBC.PowerConstraints'] = 'PEBC.PowerConstraints' + message_type: Literal["PEBC.PowerConstraints"] = "PEBC.PowerConstraints" message_id: ID id: ID = Field( ..., - description='Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) valid_from: AwareDatetime = Field( - ..., description='Moment this PEBC.PowerConstraints start to be valid' + ..., description="Moment this PEBC.PowerConstraints start to be valid" ) valid_until: Optional[AwareDatetime] = Field( None, - description='Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.', + description="Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.", ) consequence_type: PEBCPowerEnvelopeConsequenceType = Field( - ..., description='Type of consequence of limiting power' + ..., description="Type of consequence of limiting power" ) allowed_limit_ranges: List[PEBCAllowedLimitRange] = Field( ..., - description='The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.', + description="The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.", max_length=100, min_length=2, ) @@ -1328,29 +1328,29 @@ class PEBCPowerConstraints(BaseModel): class PEBCInstruction(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PEBC.Instruction'] = 'PEBC.Instruction' + message_type: Literal["PEBC.Instruction"] = "PEBC.Instruction" message_id: ID id: ID = Field( ..., - description='Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) execution_time: AwareDatetime = Field( ..., - description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', + description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", ) abnormal_condition: bool = Field( ..., - description='Indicates if this is an instruction during an abnormal condition.', + description="Indicates if this is an instruction during an abnormal condition.", ) power_constraints_id: ID = Field( ..., - description='Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.', + description="Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.", ) power_envelopes: List[PEBCPowerEnvelope] = Field( ..., - description='The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.', + description="The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.", max_length=10, min_length=1, ) @@ -1358,13 +1358,13 @@ class PEBCInstruction(BaseModel): class PPBCPowerProfileStatus(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.PowerProfileStatus'] = 'PPBC.PowerProfileStatus' + message_type: Literal["PPBC.PowerProfileStatus"] = "PPBC.PowerProfileStatus" message_id: ID sequence_container_status: List[PPBCPowerSequenceContainerStatus] = Field( ..., - description='Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.', + description="Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.", max_length=1000, min_length=1, ) @@ -1372,29 +1372,29 @@ class PPBCPowerProfileStatus(BaseModel): class OMBCSystemDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['OMBC.SystemDescription'] = 'OMBC.SystemDescription' + message_type: Literal["OMBC.SystemDescription"] = "OMBC.SystemDescription" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", ) operation_modes: List[OMBCOperationMode] = Field( ..., - description='OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.', + description="OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.", max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description='Possible transitions to switch from one OMBC.OperationMode to another.', + description="Possible transitions to switch from one OMBC.OperationMode to another.", max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description='Timers that control when certain transitions can be made.', + description="Timers that control when certain transitions can be made.", max_length=1000, min_length=0, ) @@ -1402,89 +1402,89 @@ class OMBCSystemDescription(BaseModel): class PPBCPowerSequence(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.', + description="ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.", ) elements: List[PPBCPowerSequenceElement] = Field( ..., - description='List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.', + description="List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.", max_length=288, min_length=1, ) is_interruptible: bool = Field( ..., - description='Indicates whether the option of pausing a sequence is available.', + description="Indicates whether the option of pausing a sequence is available.", ) max_pause_before: Optional[Duration] = Field( None, - description='The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one', + description="The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one", ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this PPBC.PowerSequence may only be used during an abnormal condition', + description="Indicates if this PPBC.PowerSequence may only be used during an abnormal condition", ) class FRBCOperationMode(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.', + description="ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", ) elements: List[FRBCOperationModeElement] = Field( ..., - description='List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.', + description="List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.", max_length=100, min_length=1, ) abnormal_condition_only: bool = Field( ..., - description='Indicates if this FRBC.OperationMode may only be used during an abnormal condition', + description="Indicates if this FRBC.OperationMode may only be used during an abnormal condition", ) class DDBCActuatorDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.", ) supported_commodites: List[Commodity] = Field( ..., - description='Commodities supported by the operation modes of this actuator. There shall be at least one commodity', + description="Commodities supported by the operation modes of this actuator. There shall be at least one commodity", max_length=4, min_length=1, ) operation_modes: List[DDBCOperationMode] = Field( ..., - description='List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.', + description="List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.", max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description='List of Transitions between Operation Modes. Shall contain at least one Transition.', + description="List of Transitions between Operation Modes. Shall contain at least one Transition.", max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description='List of Timers associated with Transitions for this Actuator. Can be empty.', + description="List of Timers associated with Transitions for this Actuator. Can be empty.", max_length=1000, min_length=0, ) @@ -1492,40 +1492,40 @@ class DDBCActuatorDescription(BaseModel): class DDBCSystemDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['DDBC.SystemDescription'] = 'DDBC.SystemDescription' + message_type: Literal["DDBC.SystemDescription"] = "DDBC.SystemDescription" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", ) actuators: List[DDBCActuatorDescription] = Field( ..., - description='List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.', + description="List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.", max_length=10, min_length=1, ) present_demand_rate: NumberRange = Field( - ..., description='Present demand rate that needs to be satisfied by the system' + ..., description="Present demand rate that needs to be satisfied by the system" ) provides_average_demand_rate_forecast: bool = Field( ..., - description='Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.', + description="Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.", ) class PPBCPowerSequenceContainer(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.', + description="ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.", ) power_sequences: List[PPBCPowerSequence] = Field( ..., - description='List of alternative Sequences where one could be chosen by the CEM', + description="List of alternative Sequences where one could be chosen by the CEM", max_length=288, min_length=1, ) @@ -1533,37 +1533,37 @@ class PPBCPowerSequenceContainer(BaseModel): class FRBCActuatorDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) id: ID = Field( ..., - description='ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) diagnostic_label: Optional[str] = Field( None, - description='Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', + description="Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.", ) supported_commodities: List[Commodity] = Field( ..., - description='List of all supported Commodities.', + description="List of all supported Commodities.", max_length=4, min_length=1, ) operation_modes: List[FRBCOperationMode] = Field( ..., - description='Provided FRBC.OperationModes associated with this actuator', + description="Provided FRBC.OperationModes associated with this actuator", max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description='Possible transitions between FRBC.OperationModes associated with this actuator.', + description="Possible transitions between FRBC.OperationModes associated with this actuator.", max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description='List of Timers associated with this actuator', + description="List of Timers associated with this actuator", max_length=1000, min_length=0, ) @@ -1571,25 +1571,25 @@ class FRBCActuatorDescription(BaseModel): class PPBCPowerProfileDefinition(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['PPBC.PowerProfileDefinition'] = 'PPBC.PowerProfileDefinition' + message_type: Literal["PPBC.PowerProfileDefinition"] = "PPBC.PowerProfileDefinition" message_id: ID id: ID = Field( ..., - description='ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', + description="ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", ) start_time: AwareDatetime = Field( ..., - description='Indicates the first possible time the first PPBC.PowerSequence could start', + description="Indicates the first possible time the first PPBC.PowerSequence could start", ) end_time: AwareDatetime = Field( ..., - description='Indicates when the last PPBC.PowerSequence shall be finished at the latest', + description="Indicates when the last PPBC.PowerSequence shall be finished at the latest", ) power_sequences_containers: List[PPBCPowerSequenceContainer] = Field( ..., - description='The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.', + description="The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.", max_length=1000, min_length=1, ) @@ -1597,15 +1597,15 @@ class PPBCPowerProfileDefinition(BaseModel): class FRBCSystemDescription(BaseModel): model_config = ConfigDict( - extra='forbid', + extra="forbid", ) - message_type: Literal['FRBC.SystemDescription'] = 'FRBC.SystemDescription' + message_type: Literal["FRBC.SystemDescription"] = "FRBC.SystemDescription" message_id: ID valid_from: AwareDatetime = Field( ..., - description='Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', + description="Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", ) actuators: List[FRBCActuatorDescription] = Field( - ..., description='Details of all Actuators.', max_length=10, min_length=1 + ..., description="Details of all Actuators.", max_length=10, min_length=1 ) - storage: FRBCStorageDescription = Field(..., description='Details of the storage.') + storage: FRBCStorageDescription = Field(..., description="Details of the storage.") diff --git a/src/s2python/ppbc/__init__.py b/src/s2python/ppbc/__init__.py index 8b3b676..1e0b4d3 100644 --- a/src/s2python/ppbc/__init__.py +++ b/src/s2python/ppbc/__init__.py @@ -1,2 +1,12 @@ from s2python.ppbc.ppbc_schedule_instruction import PPBCScheduleInstruction -from s2python.ppbc.ppbc_end_interruption_instruction import PPBCEndInterruptionInstruction +from s2python.ppbc.ppbc_end_interruption_instruction import ( + PPBCEndInterruptionInstruction, +) +from s2python.ppbc.ppbc_power_profile_definition import PPBCPowerProfileDefinition +from s2python.ppbc.ppbc_power_sequence_container import PPBCPowerSequenceContainer +from s2python.ppbc.ppbc_power_sequence import PPBCPowerSequence +from s2python.ppbc.ppbc_power_profile_status import PPBCPowerProfileStatus +from s2python.ppbc.ppbc_power_sequence_container_status import ( + PPBCPowerSequenceContainerStatus, +) +from s2python.ppbc.ppbc_power_sequence_element import PPBCPowerSequenceElement diff --git a/src/s2python/ppbc/ppbc_power_profile_definition.py b/src/s2python/ppbc/ppbc_power_profile_definition.py index 9e4c87a..b57ccc0 100644 --- a/src/s2python/ppbc/ppbc_power_profile_definition.py +++ b/src/s2python/ppbc/ppbc_power_profile_definition.py @@ -20,6 +20,7 @@ class PPBCPowerProfileDefinition( model_config = GenPPBCPowerProfileDefinition.model_config model_config["validate_assignment"] = True + message_id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["message_id"] id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["id"] power_sequences_containers: List[PPBCPowerSequenceContainer] = ( GenPPBCPowerProfileDefinition.model_fields["power_sequences_containers"] diff --git a/src/s2python/ppbc/ppbc_power_sequence.py b/src/s2python/ppbc/ppbc_power_sequence.py index 152170d..2c22cd9 100644 --- a/src/s2python/ppbc/ppbc_power_sequence.py +++ b/src/s2python/ppbc/ppbc_power_sequence.py @@ -15,9 +15,7 @@ @catch_and_convert_exceptions -class PPBCPowerSequenceContainer( - GenPPBCPowerSequence, S2Message["PPBCPowerSequenceContainer"] -): +class PPBCPowerSequence(GenPPBCPowerSequence, S2Message["PPBCPowerSequence"]): model_config = GenPPBCPowerSequence.model_config model_config["validate_assignment"] = True diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index bc213ff..2d0a50e 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -13,13 +13,15 @@ @catch_and_convert_exceptions -class PPBCPowerProfileDefinitionStatus( +class PPBCPowerSequenceContainerStatus( GenPPBCPowerSequenceContainerStatus, S2Message["PPBCPowerProfileDefinitionStatus"] ): model_config = GenPPBCPowerSequenceContainerStatus.model_config model_config["validate_assignment"] = True - power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields["id"] + power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ + "power_profile_id" + ] sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ "sequence_container_id" ] diff --git a/src/s2python/ppbc/ppbc_power_sequence_element.py b/src/s2python/ppbc/ppbc_power_sequence_element.py index 7763510..e239453 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_element.py +++ b/src/s2python/ppbc/ppbc_power_sequence_element.py @@ -24,5 +24,3 @@ class PPBCPowerSequenceElement( power_values: List[PowerForecastValue] = GenPPBCPowerSequenceElement.model_fields[ "power_values" ] - - \ No newline at end of file diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py new file mode 100644 index 0000000..46d3e11 --- /dev/null +++ b/tests/unit/frbc/frbc_actuator_description_test.py @@ -0,0 +1,122 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCActuatorDescriptionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "id": "5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9", + "diagnostic_label": "some-test-string8906", + "supported_commodities": [ + "GAS" + ], + "operation_modes": [ + { + "id": "512001f9-e720-40ce-88e2-06694bb2ca6d", + "diagnostic_label": "some-test-string1472", + "elements": [ + { + "fill_level_range": { + "start_of_range": 27494.741307134802, + "end_of_range": 58877.84844659244 + }, + "fill_rate": { + "start_of_range": 25438.73202815934, + "end_of_range": 32365.85082869052 + }, + "power_ranges": [ + { + "start_of_range": 810.7301251031068, + "end_of_range": 36176.5333835611, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 37284.94705878388, + "end_of_range": 61334.00977608085 + } + } + ], + "abnormal_condition_only": false + } + ], + "transitions": [ + { + "id": "5427f2cc-7d7d-4d30-bdc8-5f6594a86297", + "from_": "de2771e5-bac3-4385-8ee1-ad1500b2905f", + "to": "a169553d-cac4-4b29-9cce-9a9a17b5aebd", + "start_timers": [ + "26538354-e139-448d-9cb1-19da008bbd43" + ], + "blocking_timers": [ + "947ad518-3305-4be4-b847-a4b01a0bfd01" + ], + "transition_costs": 3119.39267710221, + "transition_duration": 21417, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "166fdf46-2695-412e-a014-dbd95dee1eb2", + "diagnostic_label": "some-test-string9309", + "duration": 16076 + } + ] +} + """ + + # Act + frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) + + # Assert + self.assertEqual(frbc_actuator_description.id, uuid.UUID("5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9")) + self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string8906") + self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) + self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("512001f9-e720-40ce-88e2-06694bb2ca6d"), diagnostic_label="some-test-string1472", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=27494.741307134802, end_of_range=58877.84844659244), fill_rate=NumberRange(start_of_range=25438.73202815934, end_of_range=32365.85082869052), power_ranges=[PowerRange(start_of_range=810.7301251031068, end_of_range=36176.5333835611, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=37284.94705878388, end_of_range=61334.00977608085))], abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("5427f2cc-7d7d-4d30-bdc8-5f6594a86297"), from_=uuid.UUID("de2771e5-bac3-4385-8ee1-ad1500b2905f"), to=uuid.UUID("a169553d-cac4-4b29-9cce-9a9a17b5aebd"), start_timers=[uuid.UUID("26538354-e139-448d-9cb1-19da008bbd43")], blocking_timers=[uuid.UUID("947ad518-3305-4be4-b847-a4b01a0bfd01")], transition_costs=3119.39267710221, transition_duration=Duration.from_timedelta(timedelta(milliseconds=21417)), abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("166fdf46-2695-412e-a014-dbd95dee1eb2"), diagnostic_label="some-test-string9309", duration=Duration.from_timedelta(timedelta(milliseconds=16076)))]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9"), diagnostic_label="some-test-string8906", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("512001f9-e720-40ce-88e2-06694bb2ca6d"), diagnostic_label="some-test-string1472", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=27494.741307134802, end_of_range=58877.84844659244), fill_rate=NumberRange(start_of_range=25438.73202815934, end_of_range=32365.85082869052), power_ranges=[PowerRange(start_of_range=810.7301251031068, end_of_range=36176.5333835611, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=37284.94705878388, end_of_range=61334.00977608085))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5427f2cc-7d7d-4d30-bdc8-5f6594a86297"), from_=uuid.UUID("de2771e5-bac3-4385-8ee1-ad1500b2905f"), to=uuid.UUID("a169553d-cac4-4b29-9cce-9a9a17b5aebd"), start_timers=[uuid.UUID("26538354-e139-448d-9cb1-19da008bbd43")], blocking_timers=[uuid.UUID("947ad518-3305-4be4-b847-a4b01a0bfd01")], transition_costs=3119.39267710221, transition_duration=Duration.from_timedelta(timedelta(milliseconds=21417)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("166fdf46-2695-412e-a014-dbd95dee1eb2"), diagnostic_label="some-test-string9309", duration=Duration.from_timedelta(timedelta(milliseconds=16076)))]) + + # Act + json_str = frbc_actuator_description.to_json() + + # Assert + expected_json = { 'diagnostic_label': 'some-test-string8906', + 'id': '5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string1472', + 'elements': [ { 'fill_level_range': { 'end_of_range': 58877.84844659244, + 'start_of_range': 27494.741307134802}, + 'fill_rate': { 'end_of_range': 32365.85082869052, + 'start_of_range': 25438.73202815934}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 36176.5333835611, + 'start_of_range': 810.7301251031068}], + 'running_costs': { 'end_of_range': 61334.00977608085, + 'start_of_range': 37284.94705878388}}], + 'id': '512001f9-e720-40ce-88e2-06694bb2ca6d'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string9309', + 'duration': 16076, + 'id': '166fdf46-2695-412e-a014-dbd95dee1eb2'}], + 'transitions': [ { 'abnormal_condition_only': False, + 'blocking_timers': [ '947ad518-3305-4be4-b847-a4b01a0bfd01'], + 'from_': 'de2771e5-bac3-4385-8ee1-ad1500b2905f', + 'id': '5427f2cc-7d7d-4d30-bdc8-5f6594a86297', + 'start_timers': [ '26538354-e139-448d-9cb1-19da008bbd43'], + 'to': 'a169553d-cac4-4b29-9cce-9a9a17b5aebd', + 'transition_costs': 3119.39267710221, + 'transition_duration': 21417}]} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py index b3bb5c8..819a923 100644 --- a/tests/unit/frbc/frbc_actuator_status_test.py +++ b/tests/unit/frbc/frbc_actuator_status_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,13 +13,13 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "active_operation_mode_id": "395dcbc5-5c7f-415e-8727-e48fc53761bc", - "actuator_id": "1cee425e-861b-417a-8208-bb6d53aafb00", - "message_id": "07f3d559-63c5-4369-a9e0-deed4195f651", "message_type": "FRBC.ActuatorStatus", - "operation_mode_factor": 6919.960475850124, - "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", - "transition_timestamp": "2020-01-02T07:56:46Z" + "message_id": "5d000419-7f2c-451a-b803-ebcc72b7d0bd", + "actuator_id": "9752ddc9-b04c-4902-a92c-3f181eafd270", + "active_operation_mode_id": "8d00a902-e202-4c6a-876a-4182bb83c8ac", + "operation_mode_factor": 39.51079942572766, + "previous_operation_mode_id": "767ff15a-e406-4ad3-8d74-0f470f9b0589", + "transition_timestamp": "2022-12-10T08:41:46+07:00" } """ @@ -26,68 +27,27 @@ def test__from_json__happy_path_full(self): frbc_actuator_status = FRBCActuatorStatus.from_json(json_str) # Assert - self.assertEqual( - frbc_actuator_status.active_operation_mode_id, - uuid.UUID("395dcbc5-5c7f-415e-8727-e48fc53761bc"), - ) - self.assertEqual( - frbc_actuator_status.actuator_id, - uuid.UUID("1cee425e-861b-417a-8208-bb6d53aafb00"), - ) - self.assertEqual( - frbc_actuator_status.message_id, - uuid.UUID("07f3d559-63c5-4369-a9e0-deed4195f651"), - ) - self.assertEqual(frbc_actuator_status.message_type, "FRBC.ActuatorStatus") - self.assertEqual(frbc_actuator_status.operation_mode_factor, 6919.960475850124) - self.assertEqual( - frbc_actuator_status.previous_operation_mode_id, - uuid.UUID("2ed8f7de-cbaa-4cab-9d25-6792317aa284"), - ) - self.assertEqual( - frbc_actuator_status.transition_timestamp, - datetime( - year=2020, - month=1, - day=2, - hour=7, - minute=56, - second=46, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_actuator_status.message_type, FRBC.ActuatorStatus) + self.assertEqual(frbc_actuator_status.message_id, uuid.UUID("5d000419-7f2c-451a-b803-ebcc72b7d0bd")) + self.assertEqual(frbc_actuator_status.actuator_id, uuid.UUID("9752ddc9-b04c-4902-a92c-3f181eafd270")) + self.assertEqual(frbc_actuator_status.active_operation_mode_id, uuid.UUID("8d00a902-e202-4c6a-876a-4182bb83c8ac")) + self.assertEqual(frbc_actuator_status.operation_mode_factor, 39.51079942572766) + self.assertEqual(frbc_actuator_status.previous_operation_mode_id, uuid.UUID("767ff15a-e406-4ad3-8d74-0f470f9b0589")) + self.assertEqual(frbc_actuator_status.transition_timestamp, datetime(year=2022, month=12, day=10, hour=8, minute=41, second=46, tzinfo=offset(offset=timedelta(seconds=25200.0)))) def test__to_json__happy_path_full(self): # Arrange - frbc_actuator_status = FRBCActuatorStatus( - active_operation_mode_id=uuid.UUID("395dcbc5-5c7f-415e-8727-e48fc53761bc"), - actuator_id=uuid.UUID("1cee425e-861b-417a-8208-bb6d53aafb00"), - 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"), - transition_timestamp=datetime( - year=2020, - month=1, - day=2, - hour=7, - minute=56, - second=46, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_actuator_status = FRBCActuatorStatus(message_type=FRBC.ActuatorStatus, message_id=uuid.UUID("5d000419-7f2c-451a-b803-ebcc72b7d0bd"), actuator_id=uuid.UUID("9752ddc9-b04c-4902-a92c-3f181eafd270"), active_operation_mode_id=uuid.UUID("8d00a902-e202-4c6a-876a-4182bb83c8ac"), operation_mode_factor=39.51079942572766, previous_operation_mode_id=uuid.UUID("767ff15a-e406-4ad3-8d74-0f470f9b0589"), transition_timestamp=datetime(year=2022, month=12, day=10, hour=8, minute=41, second=46, tzinfo=offset(offset=timedelta(seconds=25200.0)))) # Act json_str = frbc_actuator_status.to_json() # Assert - expected_json = { - "active_operation_mode_id": "395dcbc5-5c7f-415e-8727-e48fc53761bc", - "actuator_id": "1cee425e-861b-417a-8208-bb6d53aafb00", - "message_id": "07f3d559-63c5-4369-a9e0-deed4195f651", - "message_type": "FRBC.ActuatorStatus", - "operation_mode_factor": 6919.960475850124, - "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", - "transition_timestamp": "2020-01-02T07:56:46Z", - } + expected_json = { 'active_operation_mode_id': '8d00a902-e202-4c6a-876a-4182bb83c8ac', + 'actuator_id': '9752ddc9-b04c-4902-a92c-3f181eafd270', + 'message_id': '5d000419-7f2c-451a-b803-ebcc72b7d0bd', + 'message_type': 'FRBC.ActuatorStatus', + 'operation_mode_factor': 39.51079942572766, + 'previous_operation_mode_id': '767ff15a-e406-4ad3-8d74-0f470f9b0589', + 'transition_timestamp': '2022-12-10T08:41:46+07:00'} self.assertEqual(json.loads(json_str), expected_json) 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..5f72002 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 @@ -1,10 +1,11 @@ -from datetime import timedelta + +from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase +import uuid from s2python.common import * from s2python.frbc import * -from s2python.s2_validation_error import S2ValidationError class FRBCFillLevelTargetProfileElementTest(TestCase): @@ -12,50 +13,30 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "duration": 12950, + "duration": 16041, "fill_level_range": { - "end_of_range": 8176, - "start_of_range": 6207 + "start_of_range": 38789.06538190935, + "end_of_range": 45889.620464907246 } } """ # Act - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json( - json_str - ) + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json(json_str) # Assert - self.assertEqual( - frbc_fill_level_target_profile_element.duration, - Duration.from_timedelta(timedelta(milliseconds=12950)), - ) - self.assertEqual( - frbc_fill_level_target_profile_element.fill_level_range, - NumberRange(end_of_range=8176.0, start_of_range=6207.0), - ) + self.assertEqual(frbc_fill_level_target_profile_element.duration, Duration.from_timedelta(timedelta(milliseconds=16041))) + self.assertEqual(frbc_fill_level_target_profile_element.fill_level_range, NumberRange(start_of_range=38789.06538190935, end_of_range=45889.620464907246)) def test__to_json__happy_path_full(self): # Arrange - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=12950)), - fill_level_range=NumberRange(end_of_range=8176, start_of_range=6207), - ) + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=16041)), fill_level_range=NumberRange(start_of_range=38789.06538190935, end_of_range=45889.620464907246)) # Act json_str = frbc_fill_level_target_profile_element.to_json() # Assert - expected_json = { - "duration": 12950, - "fill_level_range": {"end_of_range": 8176, "start_of_range": 6207}, - } + expected_json = { 'duration': 16041, + 'fill_level_range': { 'end_of_range': 45889.620464907246, + 'start_of_range': 38789.06538190935}} self.assertEqual(json.loads(json_str), expected_json) - - def test__init__fill_level_range_end_is_smaller_than_start(self): - # Arrange / Act / Assert - with self.assertRaises(S2ValidationError): - FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=12950)), - fill_level_range=NumberRange(end_of_range=6000, start_of_range=8176), - ) 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..5142704 100644 --- a/tests/unit/frbc/frbc_fill_level_target_profile_test.py +++ b/tests/unit/frbc/frbc_fill_level_target_profile_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,18 +13,18 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.FillLevelTargetProfile", + "message_id": "698ea780-c918-4ea8-aef2-885a5c0228ad", + "start_time": "2020-05-16T18:38:12+08:00", "elements": [ { - "duration": 4704, + "duration": 30067, "fill_level_range": { - "end_of_range": 10800.98606857073545, - "start_of_range": 6891.19014440217 + "start_of_range": 18107.25728114559, + "end_of_range": 51078.86550304321 } } - ], - "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", - "message_type": "FRBC.FillLevelTargetProfile", - "start_time": "2021-04-17T00:19:20Z" + ] } """ @@ -31,77 +32,23 @@ def test__from_json__happy_path_full(self): frbc_fill_level_target_profile = FRBCFillLevelTargetProfile.from_json(json_str) # Assert - self.assertEqual( - frbc_fill_level_target_profile.elements, - [ - FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=4704)), - fill_level_range=NumberRange( - end_of_range=10800.98606857073545, - start_of_range=6891.19014440217, - ), - ) - ], - ) - self.assertEqual( - 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.start_time, - datetime( - year=2021, - month=4, - day=17, - hour=0, - minute=19, - second=20, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_fill_level_target_profile.message_type, FRBC.FillLevelTargetProfile) + self.assertEqual(frbc_fill_level_target_profile.message_id, uuid.UUID("698ea780-c918-4ea8-aef2-885a5c0228ad")) + self.assertEqual(frbc_fill_level_target_profile.start_time, datetime(year=2020, month=5, day=16, hour=18, minute=38, second=12, tzinfo=offset(offset=timedelta(seconds=28800.0)))) + self.assertEqual(frbc_fill_level_target_profile.elements, [FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=30067)), fill_level_range=NumberRange(start_of_range=18107.25728114559, end_of_range=51078.86550304321))]) def test__to_json__happy_path_full(self): # Arrange - frbc_fill_level_target_profile = FRBCFillLevelTargetProfile( - elements=[ - FRBCFillLevelTargetProfileElement( - duration=Duration.from_timedelta(timedelta(milliseconds=4704)), - fill_level_range=NumberRange( - end_of_range=10800.98606857073545, - start_of_range=6891.19014440217, - ), - ) - ], - message_id=uuid.UUID("04a6c8af-ca8d-420c-9c11-e96a70fe82b1"), - message_type="FRBC.FillLevelTargetProfile", - start_time=datetime( - year=2021, - month=4, - day=17, - hour=0, - minute=19, - second=20, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_fill_level_target_profile = FRBCFillLevelTargetProfile(message_type=FRBC.FillLevelTargetProfile, message_id=uuid.UUID("698ea780-c918-4ea8-aef2-885a5c0228ad"), start_time=datetime(year=2020, month=5, day=16, hour=18, minute=38, second=12, tzinfo=offset(offset=timedelta(seconds=28800.0))), elements=[FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=30067)), fill_level_range=NumberRange(start_of_range=18107.25728114559, end_of_range=51078.86550304321))]) # Act json_str = frbc_fill_level_target_profile.to_json() # Assert - expected_json = { - "elements": [ - { - "duration": 4704, - "fill_level_range": { - "end_of_range": 10800.98606857073545, - "start_of_range": 6891.19014440217, - }, - } - ], - "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", - "message_type": "FRBC.FillLevelTargetProfile", - "start_time": "2021-04-17T00:19:20Z", - } + expected_json = { 'elements': [ { 'duration': 30067, + 'fill_level_range': { 'end_of_range': 51078.86550304321, + 'start_of_range': 18107.25728114559}}], + 'message_id': '698ea780-c918-4ea8-aef2-885a5c0228ad', + 'message_type': 'FRBC.FillLevelTargetProfile', + 'start_time': '2020-05-16T18:38:12+08:00'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_instruction_test.py b/tests/unit/frbc/frbc_instruction_test.py index 901a711..9f552e6 100644 --- a/tests/unit/frbc/frbc_instruction_test.py +++ b/tests/unit/frbc/frbc_instruction_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,14 +13,14 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "abnormal_condition": true, - "actuator_id": "db7855dd-05c4-4ba8-81e2-d10001c5bc3f", - "execution_time": "2023-04-11T16:46:33+01:00", - "id": "9ffd68cd-b0e2-44a6-aded-4dce6c18247e", - "message_id": "bcb3e1da-e797-4951-86be-5e5d9136c63f", "message_type": "FRBC.Instruction", - "operation_mode": "e7bf29a7-4ebc-49c1-a1fb-20725f450c91", - "operation_mode_factor": 2303.58902271682 + "message_id": "185b511f-725d-42c8-a1db-523cda5c45a9", + "id": "8db97bd0-258e-4a8b-92be-40e10b1c7a2c", + "actuator_id": "cb06550c-e8a2-418d-8885-24a530dde4c4", + "operation_mode": "4a687962-da90-4379-9190-5d1d68f9dd18", + "operation_mode_factor": 4456.156981790672, + "execution_time": "2020-11-11T07:59:54-11:00", + "abnormal_condition": true } """ @@ -27,70 +28,29 @@ def test__from_json__happy_path_full(self): frbc_instruction = FRBCInstruction.from_json(json_str) # Assert + self.assertEqual(frbc_instruction.message_type, FRBC.Instruction) + self.assertEqual(frbc_instruction.message_id, uuid.UUID("185b511f-725d-42c8-a1db-523cda5c45a9")) + self.assertEqual(frbc_instruction.id, uuid.UUID("8db97bd0-258e-4a8b-92be-40e10b1c7a2c")) + self.assertEqual(frbc_instruction.actuator_id, uuid.UUID("cb06550c-e8a2-418d-8885-24a530dde4c4")) + self.assertEqual(frbc_instruction.operation_mode, uuid.UUID("4a687962-da90-4379-9190-5d1d68f9dd18")) + self.assertEqual(frbc_instruction.operation_mode_factor, 4456.156981790672) + self.assertEqual(frbc_instruction.execution_time, datetime(year=2020, month=11, day=11, hour=7, minute=59, second=54, tzinfo=offset(offset=timedelta(seconds=-39600.0)))) self.assertEqual(frbc_instruction.abnormal_condition, True) - self.assertEqual( - frbc_instruction.actuator_id, - uuid.UUID("db7855dd-05c4-4ba8-81e2-d10001c5bc3f"), - ) - self.assertEqual( - frbc_instruction.execution_time, - datetime( - year=2023, - month=4, - day=11, - hour=16, - minute=46, - second=33, - tzinfo=offset(offset=timedelta(seconds=3600.0)), - ), - ) - self.assertEqual( - frbc_instruction.id, uuid.UUID("9ffd68cd-b0e2-44a6-aded-4dce6c18247e") - ) - self.assertEqual( - frbc_instruction.message_id, - uuid.UUID("bcb3e1da-e797-4951-86be-5e5d9136c63f"), - ) - self.assertEqual(frbc_instruction.message_type, "FRBC.Instruction") - self.assertEqual( - frbc_instruction.operation_mode, - uuid.UUID("e7bf29a7-4ebc-49c1-a1fb-20725f450c91"), - ) - self.assertEqual(frbc_instruction.operation_mode_factor, 2303.58902271682) def test__to_json__happy_path_full(self): # Arrange - frbc_instruction = FRBCInstruction( - abnormal_condition=True, - actuator_id=uuid.UUID("db7855dd-05c4-4ba8-81e2-d10001c5bc3f"), - execution_time=datetime( - year=2023, - month=4, - day=11, - hour=16, - minute=46, - second=33, - tzinfo=offset(offset=timedelta(seconds=3600.0)), - ), - id=uuid.UUID("9ffd68cd-b0e2-44a6-aded-4dce6c18247e"), - message_id=uuid.UUID("bcb3e1da-e797-4951-86be-5e5d9136c63f"), - message_type="FRBC.Instruction", - operation_mode=uuid.UUID("e7bf29a7-4ebc-49c1-a1fb-20725f450c91"), - operation_mode_factor=2303.58902271682, - ) + frbc_instruction = FRBCInstruction(message_type=FRBC.Instruction, message_id=uuid.UUID("185b511f-725d-42c8-a1db-523cda5c45a9"), id=uuid.UUID("8db97bd0-258e-4a8b-92be-40e10b1c7a2c"), actuator_id=uuid.UUID("cb06550c-e8a2-418d-8885-24a530dde4c4"), operation_mode=uuid.UUID("4a687962-da90-4379-9190-5d1d68f9dd18"), operation_mode_factor=4456.156981790672, execution_time=datetime(year=2020, month=11, day=11, hour=7, minute=59, second=54, tzinfo=offset(offset=timedelta(seconds=-39600.0))), abnormal_condition=True) # Act json_str = frbc_instruction.to_json() # Assert - expected_json = { - "abnormal_condition": True, - "actuator_id": "db7855dd-05c4-4ba8-81e2-d10001c5bc3f", - "execution_time": "2023-04-11T16:46:33+01:00", - "id": "9ffd68cd-b0e2-44a6-aded-4dce6c18247e", - "message_id": "bcb3e1da-e797-4951-86be-5e5d9136c63f", - "message_type": "FRBC.Instruction", - "operation_mode": "e7bf29a7-4ebc-49c1-a1fb-20725f450c91", - "operation_mode_factor": 2303.58902271682, - } + expected_json = { 'abnormal_condition': True, + 'actuator_id': 'cb06550c-e8a2-418d-8885-24a530dde4c4', + 'execution_time': '2020-11-11T07:59:54-11:00', + 'id': '8db97bd0-258e-4a8b-92be-40e10b1c7a2c', + 'message_id': '185b511f-725d-42c8-a1db-523cda5c45a9', + 'message_type': 'FRBC.Instruction', + 'operation_mode': '4a687962-da90-4379-9190-5d1d68f9dd18', + 'operation_mode_factor': 4456.156981790672} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py index 79f480a..e900d5d 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -5,7 +6,6 @@ from s2python.common import * from s2python.frbc import * -from s2python.s2_validation_error import S2ValidationError class FRBCLeakageBehaviourElementTest(TestCase): @@ -14,10 +14,10 @@ def test__from_json__happy_path_full(self): json_str = """ { "fill_level_range": { - "end_of_range": 40192.498918818455, - "start_of_range": 29234.82582981918 + "start_of_range": 28885.23485069778, + "end_of_range": 40074.88428029624 }, - "leakage_rate": 1170.4041485129987 + "leakage_rate": 6677.574789126709 } """ @@ -25,40 +25,18 @@ def test__from_json__happy_path_full(self): frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement.from_json(json_str) # Assert - self.assertEqual( - frbc_leakage_behaviour_element.fill_level_range, - 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.fill_level_range, NumberRange(start_of_range=28885.23485069778, end_of_range=40074.88428029624)) + self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 6677.574789126709) def test__to_json__happy_path_full(self): # Arrange - frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=40192.498918818455, start_of_range=29234.82582981918 - ), - leakage_rate=1170.4041485129987, - ) + frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=28885.23485069778, end_of_range=40074.88428029624), leakage_rate=6677.574789126709) # Act json_str = frbc_leakage_behaviour_element.to_json() # Assert - expected_json = { - "fill_level_range": { - "end_of_range": 40192.498918818455, - "start_of_range": 29234.82582981918, - }, - "leakage_rate": 1170.4041485129987, - } + expected_json = { 'fill_level_range': { 'end_of_range': 40074.88428029624, + 'start_of_range': 28885.23485069778}, + 'leakage_rate': 6677.574789126709} self.assertEqual(json.loads(json_str), expected_json) - - def test__init__fill_level_range_end_is_smaller_than_start(self): - # Arrange / Act / Assert - with self.assertRaises(S2ValidationError): - FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=29234.82582981918, start_of_range=40192.498918818455 - ), - leakage_rate=1170.4041485129987, - ) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_test.py b/tests/unit/frbc/frbc_leakage_behaviour_test.py index ad290d1..ac7155c 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,18 +13,18 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.LeakageBehaviour", + "message_id": "f681fa18-589b-4081-a85b-cdb608795248", + "valid_from": "2023-09-26T17:14:46+13:00", "elements": [ { "fill_level_range": { - "end_of_range": 31155.931914859895, - "start_of_range": 5727.722922773178 + "start_of_range": 5349.224337716178, + "end_of_range": 26642.603531976765 }, - "leakage_rate": 1225.9695121338086 + "leakage_rate": 3062.489155126407 } - ], - "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", - "message_type": "FRBC.LeakageBehaviour", - "valid_from": "2022-05-26T15:02:32Z" + ] } """ @@ -31,77 +32,23 @@ def test__from_json__happy_path_full(self): frbc_leakage_behaviour = FRBCLeakageBehaviour.from_json(json_str) # Assert - self.assertEqual( - frbc_leakage_behaviour.elements, - [ - FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=31155.931914859895, - start_of_range=5727.722922773178, - ), - leakage_rate=1225.9695121338086, - ) - ], - ) - self.assertEqual( - frbc_leakage_behaviour.message_id, - uuid.UUID("b3e9604a-1127-4ecc-9f9e-336047fde285"), - ) - self.assertEqual(frbc_leakage_behaviour.message_type, "FRBC.LeakageBehaviour") - self.assertEqual( - frbc_leakage_behaviour.valid_from, - datetime( - year=2022, - month=5, - day=26, - hour=15, - minute=2, - second=32, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_leakage_behaviour.message_type, FRBC.LeakageBehaviour) + self.assertEqual(frbc_leakage_behaviour.message_id, uuid.UUID("f681fa18-589b-4081-a85b-cdb608795248")) + self.assertEqual(frbc_leakage_behaviour.valid_from, datetime(year=2023, month=9, day=26, hour=17, minute=14, second=46, tzinfo=offset(offset=timedelta(seconds=46800.0)))) + self.assertEqual(frbc_leakage_behaviour.elements, [FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=5349.224337716178, end_of_range=26642.603531976765), leakage_rate=3062.489155126407)]) def test__to_json__happy_path_full(self): # Arrange - frbc_leakage_behaviour = FRBCLeakageBehaviour( - elements=[ - FRBCLeakageBehaviourElement( - fill_level_range=NumberRange( - end_of_range=31155.931914859895, - start_of_range=5727.722922773178, - ), - leakage_rate=1225.9695121338086, - ) - ], - message_id=uuid.UUID("b3e9604a-1127-4ecc-9f9e-336047fde285"), - message_type="FRBC.LeakageBehaviour", - valid_from=datetime( - year=2022, - month=5, - day=26, - hour=15, - minute=2, - second=32, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_leakage_behaviour = FRBCLeakageBehaviour(message_type=FRBC.LeakageBehaviour, message_id=uuid.UUID("f681fa18-589b-4081-a85b-cdb608795248"), valid_from=datetime(year=2023, month=9, day=26, hour=17, minute=14, second=46, tzinfo=offset(offset=timedelta(seconds=46800.0))), elements=[FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=5349.224337716178, end_of_range=26642.603531976765), leakage_rate=3062.489155126407)]) # Act json_str = frbc_leakage_behaviour.to_json() # Assert - expected_json = { - "elements": [ - { - "fill_level_range": { - "end_of_range": 31155.931914859895, - "start_of_range": 5727.722922773178, - }, - "leakage_rate": 1225.9695121338086, - } - ], - "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", - "message_type": "FRBC.LeakageBehaviour", - "valid_from": "2022-05-26T15:02:32Z", - } + expected_json = { 'elements': [ { 'fill_level_range': { 'end_of_range': 26642.603531976765, + 'start_of_range': 5349.224337716178}, + 'leakage_rate': 3062.489155126407}], + 'message_id': 'f681fa18-589b-4081-a85b-cdb608795248', + 'message_type': 'FRBC.LeakageBehaviour', + 'valid_from': '2023-09-26T17:14:46+13:00'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_element_test.py b/tests/unit/frbc/frbc_operation_mode_element_test.py index d2f3455..2c8bcba 100644 --- a/tests/unit/frbc/frbc_operation_mode_element_test.py +++ b/tests/unit/frbc/frbc_operation_mode_element_test.py @@ -1,11 +1,11 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase import uuid -from s2python.common import NumberRange, PowerRange -from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement -from s2python.generated.gen_s2 import CommodityQuantity +from s2python.common import * +from s2python.frbc import * class FRBCOperationModeElementTest(TestCase): @@ -14,23 +14,23 @@ def test__from_json__happy_path_full(self): json_str = """ { "fill_level_range": { - "end_of_range": 51798.05122344172, - "start_of_range": 12901.48976850875 + "start_of_range": 2839.6809937410153, + "end_of_range": 23657.59074127252 }, "fill_rate": { - "end_of_range": 35734.54630113551, - "start_of_range": 10740.443924585083 + "start_of_range": 31115.515599679075, + "end_of_range": 34674.7451119136 }, "power_ranges": [ { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 69093.48993128976, - "start_of_range": 34859.59303603876 + "start_of_range": 28918.644493729425, + "end_of_range": 62283.65396785374, + "commodity_quantity": "ELECTRIC.POWER.L1" } ], "running_costs": { - "end_of_range": 47869.03540464825, - "start_of_range": 19009.60894672492 + "start_of_range": 27450.2722984119, + "end_of_range": 63683.41689038279 } } """ @@ -39,79 +39,26 @@ def test__from_json__happy_path_full(self): frbc_operation_mode_element = FRBCOperationModeElement.from_json(json_str) # Assert - self.assertEqual( - frbc_operation_mode_element.fill_level_range, - NumberRange( - end_of_range=51798.05122344172, start_of_range=12901.48976850875 - ), - ) - self.assertEqual( - frbc_operation_mode_element.fill_rate, - NumberRange( - end_of_range=35734.54630113551, start_of_range=10740.443924585083 - ), - ) - self.assertEqual( - frbc_operation_mode_element.power_ranges, - [ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=69093.48993128976, - start_of_range=34859.59303603876, - ) - ], - ) - self.assertEqual( - frbc_operation_mode_element.running_costs, - NumberRange( - end_of_range=47869.03540464825, start_of_range=19009.60894672492 - ), - ) + self.assertEqual(frbc_operation_mode_element.fill_level_range, NumberRange(start_of_range=2839.6809937410153, end_of_range=23657.59074127252)) + self.assertEqual(frbc_operation_mode_element.fill_rate, NumberRange(start_of_range=31115.515599679075, end_of_range=34674.7451119136)) + self.assertEqual(frbc_operation_mode_element.power_ranges, [PowerRange(start_of_range=28918.644493729425, end_of_range=62283.65396785374, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)]) + self.assertEqual(frbc_operation_mode_element.running_costs, NumberRange(start_of_range=27450.2722984119, end_of_range=63683.41689038279)) def test__to_json__happy_path_full(self): # Arrange - frbc_operation_mode_element = FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=51798.05122344172, start_of_range=12901.48976850875 - ), - fill_rate=NumberRange( - end_of_range=35734.54630113551, start_of_range=10740.443924585083 - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=69093.48993128976, - start_of_range=34859.59303603876, - ) - ], - running_costs=NumberRange( - end_of_range=47869.03540464825, start_of_range=19009.60894672492 - ), - ) + frbc_operation_mode_element = FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=2839.6809937410153, end_of_range=23657.59074127252), fill_rate=NumberRange(start_of_range=31115.515599679075, end_of_range=34674.7451119136), power_ranges=[PowerRange(start_of_range=28918.644493729425, end_of_range=62283.65396785374, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=27450.2722984119, end_of_range=63683.41689038279)) # Act json_str = frbc_operation_mode_element.to_json() # Assert - expected_json = { - "fill_level_range": { - "end_of_range": 51798.05122344172, - "start_of_range": 12901.48976850875, - }, - "fill_rate": { - "end_of_range": 35734.54630113551, - "start_of_range": 10740.443924585083, - }, - "power_ranges": [ - { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 69093.48993128976, - "start_of_range": 34859.59303603876, - } - ], - "running_costs": { - "end_of_range": 47869.03540464825, - "start_of_range": 19009.60894672492, - }, - } + expected_json = { 'fill_level_range': { 'end_of_range': 23657.59074127252, + 'start_of_range': 2839.6809937410153}, + 'fill_rate': { 'end_of_range': 34674.7451119136, + 'start_of_range': 31115.515599679075}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 62283.65396785374, + 'start_of_range': 28918.644493729425}], + 'running_costs': { 'end_of_range': 63683.41689038279, + 'start_of_range': 27450.2722984119}} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_test.py b/tests/unit/frbc/frbc_operation_mode_test.py index 0d97f27..446de12 100644 --- a/tests/unit/frbc/frbc_operation_mode_test.py +++ b/tests/unit/frbc/frbc_operation_mode_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,32 +13,32 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "abnormal_condition_only": true, - "diagnostic_label": "some-test-string7557", + "id": "44ea8c08-6aca-4b93-8434-ede68200dc69", + "diagnostic_label": "some-test-string6411", "elements": [ { "fill_level_range": { - "end_of_range": 34304.92092046668, - "start_of_range": 17579.18236077446 + "start_of_range": 13185.562172385307, + "end_of_range": 28351.769654896747 }, "fill_rate": { - "end_of_range": 41719.931165871916, - "start_of_range": 10542.600445486576 + "start_of_range": 25266.999524961477, + "end_of_range": 34750.38438764264 }, "power_ranges": [ { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 44983.5145552435, - "start_of_range": 29337.138579372047 + "start_of_range": 14986.569871211224, + "end_of_range": 24935.417325009203, + "commodity_quantity": "ELECTRIC.POWER.L1" } ], "running_costs": { - "end_of_range": 62835.00070350196, - "start_of_range": 33318.34845926906 + "start_of_range": 18871.059938463823, + "end_of_range": 30857.989097156864 } } ], - "id": "b1255236-475c-4dc7-a728-afb620a99ec8" + "abnormal_condition_only": false } """ @@ -45,95 +46,29 @@ def test__from_json__happy_path_full(self): frbc_operation_mode = FRBCOperationMode.from_json(json_str) # Assert - self.assertEqual(frbc_operation_mode.abnormal_condition_only, True) - self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string7557") - self.assertEqual( - frbc_operation_mode.elements, - [ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=34304.92092046668, start_of_range=17579.18236077446 - ), - fill_rate=NumberRange( - end_of_range=41719.931165871916, - start_of_range=10542.600445486576, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=44983.5145552435, - start_of_range=29337.138579372047, - ) - ], - running_costs=NumberRange( - end_of_range=62835.00070350196, start_of_range=33318.34845926906 - ), - ) - ], - ) - self.assertEqual( - frbc_operation_mode.id, uuid.UUID("b1255236-475c-4dc7-a728-afb620a99ec8") - ) + self.assertEqual(frbc_operation_mode.id, uuid.UUID("44ea8c08-6aca-4b93-8434-ede68200dc69")) + self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string6411") + self.assertEqual(frbc_operation_mode.elements, [FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=13185.562172385307, end_of_range=28351.769654896747), fill_rate=NumberRange(start_of_range=25266.999524961477, end_of_range=34750.38438764264), power_ranges=[PowerRange(start_of_range=14986.569871211224, end_of_range=24935.417325009203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=18871.059938463823, end_of_range=30857.989097156864))]) + self.assertEqual(frbc_operation_mode.abnormal_condition_only, False) def test__to_json__happy_path_full(self): # Arrange - frbc_operation_mode = FRBCOperationMode( - abnormal_condition_only=True, - diagnostic_label="some-test-string7557", - elements=[ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=34304.92092046668, start_of_range=17579.18236077446 - ), - fill_rate=NumberRange( - end_of_range=41719.931165871916, - start_of_range=10542.600445486576, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=44983.5145552435, - start_of_range=29337.138579372047, - ) - ], - running_costs=NumberRange( - end_of_range=62835.00070350196, start_of_range=33318.34845926906 - ), - ) - ], - id=uuid.UUID("b1255236-475c-4dc7-a728-afb620a99ec8"), - ) + frbc_operation_mode = FRBCOperationMode(id=uuid.UUID("44ea8c08-6aca-4b93-8434-ede68200dc69"), diagnostic_label="some-test-string6411", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=13185.562172385307, end_of_range=28351.769654896747), fill_rate=NumberRange(start_of_range=25266.999524961477, end_of_range=34750.38438764264), power_ranges=[PowerRange(start_of_range=14986.569871211224, end_of_range=24935.417325009203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=18871.059938463823, end_of_range=30857.989097156864))], abnormal_condition_only=False) # Act json_str = frbc_operation_mode.to_json() # Assert - expected_json = { - "abnormal_condition_only": True, - "diagnostic_label": "some-test-string7557", - "elements": [ - { - "fill_level_range": { - "end_of_range": 34304.92092046668, - "start_of_range": 17579.18236077446, - }, - "fill_rate": { - "end_of_range": 41719.931165871916, - "start_of_range": 10542.600445486576, - }, - "power_ranges": [ - { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 44983.5145552435, - "start_of_range": 29337.138579372047, - } - ], - "running_costs": { - "end_of_range": 62835.00070350196, - "start_of_range": 33318.34845926906, - }, - } - ], - "id": "b1255236-475c-4dc7-a728-afb620a99ec8", - } + expected_json = { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string6411', + 'elements': [ { 'fill_level_range': { 'end_of_range': 28351.769654896747, + 'start_of_range': 13185.562172385307}, + 'fill_rate': { 'end_of_range': 34750.38438764264, + 'start_of_range': 25266.999524961477}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 24935.417325009203, + 'start_of_range': 14986.569871211224}], + 'running_costs': { 'end_of_range': 30857.989097156864, + 'start_of_range': 18871.059938463823}}], + 'id': '44ea8c08-6aca-4b93-8434-ede68200dc69'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_description_test.py b/tests/unit/frbc/frbc_storage_description_test.py index a1e8e2e..948202c 100644 --- a/tests/unit/frbc/frbc_storage_description_test.py +++ b/tests/unit/frbc/frbc_storage_description_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,15 +13,15 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "diagnostic_label": "some-test-string3063", - "fill_level_label": "some-test-string2323", - "fill_level_range": { - "end_of_range": 14555.806367871957, - "start_of_range": 10409.397377840089 - }, + "diagnostic_label": "some-test-string6024", + "fill_level_label": "some-test-string4194", + "provides_leakage_behaviour": true, "provides_fill_level_target_profile": true, - "provides_leakage_behaviour": false, - "provides_usage_forecast": false + "provides_usage_forecast": false, + "fill_level_range": { + "start_of_range": 4284.806107128117, + "end_of_range": 15952.434979774753 + } } """ @@ -28,50 +29,26 @@ def test__from_json__happy_path_full(self): frbc_storage_description = FRBCStorageDescription.from_json(json_str) # Assert - self.assertEqual( - frbc_storage_description.diagnostic_label, "some-test-string3063" - ) - self.assertEqual( - frbc_storage_description.fill_level_label, "some-test-string2323" - ) - self.assertEqual( - frbc_storage_description.fill_level_range, - NumberRange( - end_of_range=14555.806367871957, start_of_range=10409.397377840089 - ), - ) - self.assertEqual( - frbc_storage_description.provides_fill_level_target_profile, True - ) - self.assertEqual(frbc_storage_description.provides_leakage_behaviour, False) + self.assertEqual(frbc_storage_description.diagnostic_label, "some-test-string6024") + self.assertEqual(frbc_storage_description.fill_level_label, "some-test-string4194") + self.assertEqual(frbc_storage_description.provides_leakage_behaviour, True) + self.assertEqual(frbc_storage_description.provides_fill_level_target_profile, True) self.assertEqual(frbc_storage_description.provides_usage_forecast, False) + self.assertEqual(frbc_storage_description.fill_level_range, NumberRange(start_of_range=4284.806107128117, end_of_range=15952.434979774753)) def test__to_json__happy_path_full(self): # Arrange - frbc_storage_description = FRBCStorageDescription( - diagnostic_label="some-test-string3063", - fill_level_label="some-test-string2323", - fill_level_range=NumberRange( - end_of_range=14555.806367871957, start_of_range=10409.397377840089 - ), - provides_fill_level_target_profile=True, - provides_leakage_behaviour=False, - provides_usage_forecast=False, - ) + frbc_storage_description = FRBCStorageDescription(diagnostic_label="some-test-string6024", fill_level_label="some-test-string4194", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=False, fill_level_range=NumberRange(start_of_range=4284.806107128117, end_of_range=15952.434979774753)) # Act json_str = frbc_storage_description.to_json() # Assert - expected_json = { - "diagnostic_label": "some-test-string3063", - "fill_level_label": "some-test-string2323", - "fill_level_range": { - "end_of_range": 14555.806367871957, - "start_of_range": 10409.397377840089, - }, - "provides_fill_level_target_profile": True, - "provides_leakage_behaviour": False, - "provides_usage_forecast": False, - } + expected_json = { 'diagnostic_label': 'some-test-string6024', + 'fill_level_label': 'some-test-string4194', + 'fill_level_range': { 'end_of_range': 15952.434979774753, + 'start_of_range': 4284.806107128117}, + 'provides_fill_level_target_profile': True, + 'provides_leakage_behaviour': True, + 'provides_usage_forecast': False} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_status_test.py b/tests/unit/frbc/frbc_storage_status_test.py index c2b99ab..2d819cf 100644 --- a/tests/unit/frbc/frbc_storage_status_test.py +++ b/tests/unit/frbc/frbc_storage_status_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,9 +13,9 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "message_id": "6bad8186-9ebf-4647-ac45-1c6856511a2f", "message_type": "FRBC.StorageStatus", - "present_fill_level": 2443.939298819414 + "message_id": "84266c85-e666-49b3-a4e5-a1feff77df6e", + "present_fill_level": 8890.677190972274 } """ @@ -22,28 +23,19 @@ def test__from_json__happy_path_full(self): frbc_storage_status = FRBCStorageStatus.from_json(json_str) # Assert - self.assertEqual( - frbc_storage_status.message_id, - uuid.UUID("6bad8186-9ebf-4647-ac45-1c6856511a2f"), - ) - self.assertEqual(frbc_storage_status.message_type, "FRBC.StorageStatus") - self.assertEqual(frbc_storage_status.present_fill_level, 2443.939298819414) + self.assertEqual(frbc_storage_status.message_type, FRBC.StorageStatus) + self.assertEqual(frbc_storage_status.message_id, uuid.UUID("84266c85-e666-49b3-a4e5-a1feff77df6e")) + self.assertEqual(frbc_storage_status.present_fill_level, 8890.677190972274) def test__to_json__happy_path_full(self): # Arrange - frbc_storage_status = FRBCStorageStatus( - message_id=uuid.UUID("6bad8186-9ebf-4647-ac45-1c6856511a2f"), - message_type="FRBC.StorageStatus", - present_fill_level=2443.939298819414, - ) + frbc_storage_status = FRBCStorageStatus(message_type=FRBC.StorageStatus, message_id=uuid.UUID("84266c85-e666-49b3-a4e5-a1feff77df6e"), present_fill_level=8890.677190972274) # Act json_str = frbc_storage_status.to_json() # Assert - expected_json = { - "message_id": "6bad8186-9ebf-4647-ac45-1c6856511a2f", - "message_type": "FRBC.StorageStatus", - "present_fill_level": 2443.939298819414, - } + expected_json = { 'message_id': '84266c85-e666-49b3-a4e5-a1feff77df6e', + 'message_type': 'FRBC.StorageStatus', + 'present_fill_level': 8890.677190972274} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py index 9950ea7..f943a1a 100644 --- a/tests/unit/frbc/frbc_system_description_test.py +++ b/tests/unit/frbc/frbc_system_description_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,82 +13,82 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.SystemDescription", + "message_id": "873112de-a6d8-4123-a523-fc908e9a0310", + "valid_from": "2021-07-20T04:50:11+12:00", "actuators": [ { - "diagnostic_label": "some-test-string2728", - "id": "a1061148-f19e-4b1b-8fe3-b506583ce61e", + "id": "9d5243b4-f92c-42cd-9d6b-00e3e5953dd4", + "diagnostic_label": "some-test-string2832", + "supported_commodities": [ + "GAS" + ], "operation_modes": [ { - "abnormal_condition_only": false, - "diagnostic_label": "some-test-string2930", + "id": "4526bc26-cdd8-4cd6-ae35-dfb2a6751375", + "diagnostic_label": "some-test-string2393", "elements": [ { "fill_level_range": { - "end_of_range": 36932.65171036228, - "start_of_range": 12649.272766336762 + "start_of_range": 22530.41813108864, + "end_of_range": 50141.518189827795 }, "fill_rate": { - "end_of_range": 34553.16163528188, - "start_of_range": 14377.963894945604 + "start_of_range": 10937.461391570358, + "end_of_range": 22111.530603779658 }, "power_ranges": [ { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 46924.65023353163, - "start_of_range": 11888.235871902496 + "start_of_range": 3372.255894277375, + "end_of_range": 11155.631994233203, + "commodity_quantity": "ELECTRIC.POWER.L1" } ], "running_costs": { - "end_of_range": 42897.60731684277, - "start_of_range": 33997.56376994998 + "start_of_range": 31076.328103540887, + "end_of_range": 38380.84063645549 } } ], - "id": "2795136c-eb30-4f8a-bdaa-61feba1e71b6" - } - ], - "supported_commodities": [ - "ELECTRICITY" - ], - "timers": [ - { - "diagnostic_label": "some-test-string4315", - "duration": 14099, - "id": "e1ff9e58-935b-4765-92e3-5e7679f73eb6" + "abnormal_condition_only": false } ], "transitions": [ { - "abnormal_condition_only": true, - "blocking_timers": [ - "e1ff9e58-935b-4765-92e3-5e7679f73eb6" - ], - "from": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "id": "c32cc1d3-4722-41e3-a8de-55307c723611", + "id": "5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e", + "from_": "cac664cf-bec3-49df-aeb4-d5dca13f3f0b", + "to": "61605548-7d68-4ceb-bbe2-b4d79d385b11", "start_timers": [ - "e1ff9e58-935b-4765-92e3-5e7679f73eb6" + "d2436620-dce4-43fe-a59d-a7bc3045a7cd" ], - "to": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "transition_costs": 1018.4228054114793, - "transition_duration": 11988 + "blocking_timers": [ + "2f690536-73d5-4441-90b4-a28fa60e962f" + ], + "transition_costs": 2339.5028721662775, + "transition_duration": 35219, + "abnormal_condition_only": true + } + ], + "timers": [ + { + "id": "0c8a46de-b2bf-4adc-9052-0d2aa5dade66", + "diagnostic_label": "some-test-string8913", + "duration": 20103 } ] } ], - "message_id": "97256813-de70-4640-a992-9ae0b2d8e4d1", - "message_type": "FRBC.SystemDescription", "storage": { - "diagnostic_label": "some-test-string8418", - "fill_level_label": "some-test-string9512", - "fill_level_range": { - "end_of_range": 20876.752745956997, - "start_of_range": 18324.0229135081 - }, - "provides_fill_level_target_profile": false, + "diagnostic_label": "some-test-string816", + "fill_level_label": "some-test-string5484", "provides_leakage_behaviour": true, - "provides_usage_forecast": false - }, - "valid_from": "2020-10-07T06:30:55Z" + "provides_fill_level_target_profile": true, + "provides_usage_forecast": true, + "fill_level_range": { + "start_of_range": 16537.091716121224, + "end_of_range": 19907.512143139258 + } + } } """ @@ -95,257 +96,54 @@ def test__from_json__happy_path_full(self): frbc_system_description = FRBCSystemDescription.from_json(json_str) # Assert - # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. - # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to - # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 - transition = Transition( - **{ - "id": uuid.UUID("c32cc1d3-4722-41e3-a8de-55307c723611"), - "from": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "to": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "start_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "blocking_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "transition_costs": 1018.4228054114793, - "transition_duration": Duration.from_milliseconds(11988), - "abnormal_condition_only": True, - } - ) - - self.assertEqual( - frbc_system_description.actuators, - [ - FRBCActuatorDescription( - diagnostic_label="some-test-string2728", - id=uuid.UUID("a1061148-f19e-4b1b-8fe3-b506583ce61e"), - operation_modes=[ - FRBCOperationMode( - abnormal_condition_only=False, - diagnostic_label="some-test-string2930", - elements=[ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=36932.65171036228, - start_of_range=12649.272766336762, - ), - fill_rate=NumberRange( - end_of_range=34553.16163528188, - start_of_range=14377.963894945604, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=46924.65023353163, - start_of_range=11888.235871902496, - ) - ], - running_costs=NumberRange( - end_of_range=42897.60731684277, - start_of_range=33997.56376994998, - ), - ) - ], - id=uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - ) - ], - supported_commodities=[Commodity.ELECTRICITY], - timers=[ - Timer( - diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta(timedelta(milliseconds=14099)), - id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), - ) - ], - transitions=[transition], - ) - ], - ) - self.assertEqual( - frbc_system_description.message_id, - uuid.UUID("97256813-de70-4640-a992-9ae0b2d8e4d1"), - ) - self.assertEqual(frbc_system_description.message_type, "FRBC.SystemDescription") - self.assertEqual( - frbc_system_description.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), - provides_fill_level_target_profile=False, - provides_leakage_behaviour=True, - provides_usage_forecast=False, - ), - ) - self.assertEqual( - frbc_system_description.valid_from, - datetime( - year=2020, - month=10, - day=7, - hour=6, - minute=30, - second=55, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + self.assertEqual(frbc_system_description.message_type, FRBC.SystemDescription) + self.assertEqual(frbc_system_description.message_id, uuid.UUID("873112de-a6d8-4123-a523-fc908e9a0310")) + self.assertEqual(frbc_system_description.valid_from, datetime(year=2021, month=7, day=20, hour=4, minute=50, second=11, tzinfo=offset(offset=timedelta(seconds=43200.0)))) + self.assertEqual(frbc_system_description.actuators, [FRBCActuatorDescription(id=uuid.UUID("9d5243b4-f92c-42cd-9d6b-00e3e5953dd4"), diagnostic_label="some-test-string2832", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("4526bc26-cdd8-4cd6-ae35-dfb2a6751375"), diagnostic_label="some-test-string2393", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=22530.41813108864, end_of_range=50141.518189827795), fill_rate=NumberRange(start_of_range=10937.461391570358, end_of_range=22111.530603779658), power_ranges=[PowerRange(start_of_range=3372.255894277375, end_of_range=11155.631994233203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=31076.328103540887, end_of_range=38380.84063645549))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e"), from_=uuid.UUID("cac664cf-bec3-49df-aeb4-d5dca13f3f0b"), to=uuid.UUID("61605548-7d68-4ceb-bbe2-b4d79d385b11"), start_timers=[uuid.UUID("d2436620-dce4-43fe-a59d-a7bc3045a7cd")], blocking_timers=[uuid.UUID("2f690536-73d5-4441-90b4-a28fa60e962f")], transition_costs=2339.5028721662775, transition_duration=Duration.from_timedelta(timedelta(milliseconds=35219)), abnormal_condition_only=True)], timers=[Timer(id=uuid.UUID("0c8a46de-b2bf-4adc-9052-0d2aa5dade66"), diagnostic_label="some-test-string8913", duration=Duration.from_timedelta(timedelta(milliseconds=20103)))])]) + self.assertEqual(frbc_system_description.storage, FRBCStorageDescription(diagnostic_label="some-test-string816", fill_level_label="some-test-string5484", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=16537.091716121224, end_of_range=19907.512143139258))) def test__to_json__happy_path_full(self): # Arrange - # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. - # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to - # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 - transition = Transition( - **{ - "id": uuid.UUID("c32cc1d3-4722-41e3-a8de-55307c723611"), - "from": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "to": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - "start_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "blocking_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], - "transition_costs": 1018.4228054114793, - "transition_duration": Duration.from_milliseconds(11988), - "abnormal_condition_only": True, - } - ) - frbc_system_description = FRBCSystemDescription( - actuators=[ - FRBCActuatorDescription( - diagnostic_label="some-test-string2728", - id=uuid.UUID("a1061148-f19e-4b1b-8fe3-b506583ce61e"), - operation_modes=[ - FRBCOperationMode( - abnormal_condition_only=False, - diagnostic_label="some-test-string2930", - elements=[ - FRBCOperationModeElement( - fill_level_range=NumberRange( - end_of_range=36932.65171036228, - start_of_range=12649.272766336762, - ), - fill_rate=NumberRange( - end_of_range=34553.16163528188, - start_of_range=14377.963894945604, - ), - power_ranges=[ - PowerRange( - commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, - end_of_range=46924.65023353163, - start_of_range=11888.235871902496, - ) - ], - running_costs=NumberRange( - end_of_range=42897.60731684277, - start_of_range=33997.56376994998, - ), - ) - ], - id=uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), - ) - ], - supported_commodities=[Commodity.ELECTRICITY], - timers=[ - Timer( - diagnostic_label="some-test-string4315", - duration=Duration.from_timedelta(timedelta(milliseconds=14099)), - id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), - ) - ], - transitions=[transition], - ) - ], - message_id=uuid.UUID("97256813-de70-4640-a992-9ae0b2d8e4d1"), - message_type="FRBC.SystemDescription", - 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), - provides_fill_level_target_profile=False, - provides_leakage_behaviour=True, - provides_usage_forecast=False, - ), - valid_from=datetime( - year=2020, - month=10, - day=7, - hour=6, - minute=30, - second=55, - tzinfo=offset(offset=timedelta(seconds=0.0)), - ), - ) + frbc_system_description = FRBCSystemDescription(message_type=FRBC.SystemDescription, message_id=uuid.UUID("873112de-a6d8-4123-a523-fc908e9a0310"), valid_from=datetime(year=2021, month=7, day=20, hour=4, minute=50, second=11, tzinfo=offset(offset=timedelta(seconds=43200.0))), actuators=[FRBCActuatorDescription(id=uuid.UUID("9d5243b4-f92c-42cd-9d6b-00e3e5953dd4"), diagnostic_label="some-test-string2832", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("4526bc26-cdd8-4cd6-ae35-dfb2a6751375"), diagnostic_label="some-test-string2393", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=22530.41813108864, end_of_range=50141.518189827795), fill_rate=NumberRange(start_of_range=10937.461391570358, end_of_range=22111.530603779658), power_ranges=[PowerRange(start_of_range=3372.255894277375, end_of_range=11155.631994233203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=31076.328103540887, end_of_range=38380.84063645549))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e"), from_=uuid.UUID("cac664cf-bec3-49df-aeb4-d5dca13f3f0b"), to=uuid.UUID("61605548-7d68-4ceb-bbe2-b4d79d385b11"), start_timers=[uuid.UUID("d2436620-dce4-43fe-a59d-a7bc3045a7cd")], blocking_timers=[uuid.UUID("2f690536-73d5-4441-90b4-a28fa60e962f")], transition_costs=2339.5028721662775, transition_duration=Duration.from_timedelta(timedelta(milliseconds=35219)), abnormal_condition_only=True)], timers=[Timer(id=uuid.UUID("0c8a46de-b2bf-4adc-9052-0d2aa5dade66"), diagnostic_label="some-test-string8913", duration=Duration.from_timedelta(timedelta(milliseconds=20103)))])], storage=FRBCStorageDescription(diagnostic_label="some-test-string816", fill_level_label="some-test-string5484", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=16537.091716121224, end_of_range=19907.512143139258))) # Act json_str = frbc_system_description.to_json() # Assert - expected_json = { - "actuators": [ - { - "diagnostic_label": "some-test-string2728", - "id": "a1061148-f19e-4b1b-8fe3-b506583ce61e", - "operation_modes": [ - { - "abnormal_condition_only": False, - "diagnostic_label": "some-test-string2930", - "elements": [ - { - "fill_level_range": { - "end_of_range": 36932.65171036228, - "start_of_range": 12649.272766336762, - }, - "fill_rate": { - "end_of_range": 34553.16163528188, - "start_of_range": 14377.963894945604, - }, - "power_ranges": [ - { - "commodity_quantity": "ELECTRIC.POWER.L1", - "end_of_range": 46924.65023353163, - "start_of_range": 11888.235871902496, - } - ], - "running_costs": { - "end_of_range": 42897.60731684277, - "start_of_range": 33997.56376994998, - }, - } - ], - "id": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - } - ], - "supported_commodities": ["ELECTRICITY"], - "timers": [ - { - "diagnostic_label": "some-test-string4315", - "duration": 14099, - "id": "e1ff9e58-935b-4765-92e3-5e7679f73eb6", - } - ], - "transitions": [ - { - "abnormal_condition_only": True, - "blocking_timers": ["e1ff9e58-935b-4765-92e3-5e7679f73eb6"], - "from": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "id": "c32cc1d3-4722-41e3-a8de-55307c723611", - "start_timers": ["e1ff9e58-935b-4765-92e3-5e7679f73eb6"], - "to": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", - "transition_costs": 1018.4228054114793, - "transition_duration": 11988, - } - ], - } - ], - "message_id": "97256813-de70-4640-a992-9ae0b2d8e4d1", - "message_type": "FRBC.SystemDescription", - "storage": { - "diagnostic_label": "some-test-string8418", - "fill_level_label": "some-test-string9512", - "fill_level_range": { - "end_of_range": 20876.752745956997, - "start_of_range": 18324.0229135081, - }, - "provides_fill_level_target_profile": False, - "provides_leakage_behaviour": True, - "provides_usage_forecast": False, - }, - "valid_from": "2020-10-07T06:30:55Z", - } + expected_json = { 'actuators': [ { 'diagnostic_label': 'some-test-string2832', + 'id': '9d5243b4-f92c-42cd-9d6b-00e3e5953dd4', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string2393', + 'elements': [ { 'fill_level_range': { 'end_of_range': 50141.518189827795, + 'start_of_range': 22530.41813108864}, + 'fill_rate': { 'end_of_range': 22111.530603779658, + 'start_of_range': 10937.461391570358}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 11155.631994233203, + 'start_of_range': 3372.255894277375}], + 'running_costs': { 'end_of_range': 38380.84063645549, + 'start_of_range': 31076.328103540887}}], + 'id': '4526bc26-cdd8-4cd6-ae35-dfb2a6751375'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string8913', + 'duration': 20103, + 'id': '0c8a46de-b2bf-4adc-9052-0d2aa5dade66'}], + 'transitions': [ { 'abnormal_condition_only': True, + 'blocking_timers': [ '2f690536-73d5-4441-90b4-a28fa60e962f'], + 'from_': 'cac664cf-bec3-49df-aeb4-d5dca13f3f0b', + 'id': '5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e', + 'start_timers': [ 'd2436620-dce4-43fe-a59d-a7bc3045a7cd'], + 'to': '61605548-7d68-4ceb-bbe2-b4d79d385b11', + 'transition_costs': 2339.5028721662775, + 'transition_duration': 35219}]}], + 'message_id': '873112de-a6d8-4123-a523-fc908e9a0310', + 'message_type': 'FRBC.SystemDescription', + 'storage': { 'diagnostic_label': 'some-test-string816', + 'fill_level_label': 'some-test-string5484', + 'fill_level_range': { 'end_of_range': 19907.512143139258, + 'start_of_range': 16537.091716121224}, + 'provides_fill_level_target_profile': True, + 'provides_leakage_behaviour': True, + 'provides_usage_forecast': True}, + 'valid_from': '2021-07-20T04:50:11+12:00'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_timer_status_test.py b/tests/unit/frbc/frbc_timer_status_test.py index 74e2924..e99176d 100644 --- a/tests/unit/frbc/frbc_timer_status_test.py +++ b/tests/unit/frbc/frbc_timer_status_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,11 +13,11 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "actuator_id": "f2e1f540-0235-429f-a45c-4d5cbe65d33f", - "finished_at": "2020-11-03T12:57:27+02:00", - "message_id": "57240f00-0b91-49bb-a4b0-2107d062faec", "message_type": "FRBC.TimerStatus", - "timer_id": "bcb8e64f-ea4c-4b92-b4cb-20026a13d663" + "message_id": "19c25c4f-1dd6-4a74-9e1c-54c09025844a", + "timer_id": "329b57fb-c4e8-41f4-922f-f27eb32214d5", + "actuator_id": "c24e5b81-0047-41db-9dba-1840c0a1aaca", + "finished_at": "2021-08-25T09:46:58-09:00" } """ @@ -24,59 +25,23 @@ def test__from_json__happy_path_full(self): frbc_timer_status = FRBCTimerStatus.from_json(json_str) # Assert - self.assertEqual( - frbc_timer_status.actuator_id, - uuid.UUID("f2e1f540-0235-429f-a45c-4d5cbe65d33f"), - ) - self.assertEqual( - frbc_timer_status.finished_at, - datetime( - year=2020, - month=11, - day=3, - hour=12, - minute=57, - second=27, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - ) - self.assertEqual( - frbc_timer_status.message_id, - uuid.UUID("57240f00-0b91-49bb-a4b0-2107d062faec"), - ) - self.assertEqual(frbc_timer_status.message_type, "FRBC.TimerStatus") - self.assertEqual( - frbc_timer_status.timer_id, - uuid.UUID("bcb8e64f-ea4c-4b92-b4cb-20026a13d663"), - ) + self.assertEqual(frbc_timer_status.message_type, FRBC.TimerStatus) + self.assertEqual(frbc_timer_status.message_id, uuid.UUID("19c25c4f-1dd6-4a74-9e1c-54c09025844a")) + self.assertEqual(frbc_timer_status.timer_id, uuid.UUID("329b57fb-c4e8-41f4-922f-f27eb32214d5")) + self.assertEqual(frbc_timer_status.actuator_id, uuid.UUID("c24e5b81-0047-41db-9dba-1840c0a1aaca")) + self.assertEqual(frbc_timer_status.finished_at, datetime(year=2021, month=8, day=25, hour=9, minute=46, second=58, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) def test__to_json__happy_path_full(self): # Arrange - frbc_timer_status = FRBCTimerStatus( - actuator_id=uuid.UUID("f2e1f540-0235-429f-a45c-4d5cbe65d33f"), - finished_at=datetime( - year=2020, - month=11, - day=3, - hour=12, - minute=57, - second=27, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - message_id=uuid.UUID("57240f00-0b91-49bb-a4b0-2107d062faec"), - message_type="FRBC.TimerStatus", - timer_id=uuid.UUID("bcb8e64f-ea4c-4b92-b4cb-20026a13d663"), - ) + frbc_timer_status = FRBCTimerStatus(message_type=FRBC.TimerStatus, message_id=uuid.UUID("19c25c4f-1dd6-4a74-9e1c-54c09025844a"), timer_id=uuid.UUID("329b57fb-c4e8-41f4-922f-f27eb32214d5"), actuator_id=uuid.UUID("c24e5b81-0047-41db-9dba-1840c0a1aaca"), finished_at=datetime(year=2021, month=8, day=25, hour=9, minute=46, second=58, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) # Act json_str = frbc_timer_status.to_json() # Assert - expected_json = { - "actuator_id": "f2e1f540-0235-429f-a45c-4d5cbe65d33f", - "finished_at": "2020-11-03T12:57:27+02:00", - "message_id": "57240f00-0b91-49bb-a4b0-2107d062faec", - "message_type": "FRBC.TimerStatus", - "timer_id": "bcb8e64f-ea4c-4b92-b4cb-20026a13d663", - } + expected_json = { 'actuator_id': 'c24e5b81-0047-41db-9dba-1840c0a1aaca', + 'finished_at': '2021-08-25T09:46:58-09:00', + 'message_id': '19c25c4f-1dd6-4a74-9e1c-54c09025844a', + 'message_type': 'FRBC.TimerStatus', + 'timer_id': '329b57fb-c4e8-41f4-922f-f27eb32214d5'} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_element_test.py b/tests/unit/frbc/frbc_usage_forecast_element_test.py index 3df7f63..0eb44b1 100644 --- a/tests/unit/frbc/frbc_usage_forecast_element_test.py +++ b/tests/unit/frbc/frbc_usage_forecast_element_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,14 +13,14 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "duration": 9317, - "usage_rate_expected": 866.9362374046218, - "usage_rate_lower_68PPR": 3496.6233093198375, - "usage_rate_lower_95PPR": 4206.0536932975065, - "usage_rate_lower_limit": 7353.272756502293, - "usage_rate_upper_68PPR": 5124.8129813156465, - "usage_rate_upper_95PPR": 264.3386978845277, - "usage_rate_upper_limit": 4474.174577002476 + "duration": 3339, + "usage_rate_upper_limit": 5657.302338158246, + "usage_rate_upper_95PPR": 3774.615782357365, + "usage_rate_upper_68PPR": 8333.351165894339, + "usage_rate_expected": 8333.127007404517, + "usage_rate_lower_68PPR": 6418.649606433992, + "usage_rate_lower_95PPR": 3342.9603968663487, + "usage_rate_lower_limit": 8970.532671485054 } """ @@ -27,57 +28,29 @@ def test__from_json__happy_path_full(self): frbc_usage_forecast_element = FRBCUsageForecastElement.from_json(json_str) # Assert - self.assertEqual( - frbc_usage_forecast_element.duration, - Duration.from_timedelta(timedelta(milliseconds=9317)), - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_expected, 866.9362374046218 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_lower_68PPR, 3496.6233093198375 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_lower_95PPR, 4206.0536932975065 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_lower_limit, 7353.272756502293 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_upper_68PPR, 5124.8129813156465 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_upper_95PPR, 264.3386978845277 - ) - self.assertEqual( - frbc_usage_forecast_element.usage_rate_upper_limit, 4474.174577002476 - ) + self.assertEqual(frbc_usage_forecast_element.duration, Duration.from_timedelta(timedelta(milliseconds=3339))) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_limit, 5657.302338158246) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_95PPR, 3774.615782357365) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_68PPR, 8333.351165894339) + self.assertEqual(frbc_usage_forecast_element.usage_rate_expected, 8333.127007404517) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_68PPR, 6418.649606433992) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_95PPR, 3342.9603968663487) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_limit, 8970.532671485054) def test__to_json__happy_path_full(self): # Arrange - frbc_usage_forecast_element = FRBCUsageForecastElement( - duration=Duration.from_timedelta(timedelta(milliseconds=9317)), - usage_rate_expected=866.9362374046218, - usage_rate_lower_68PPR=3496.6233093198375, - usage_rate_lower_95PPR=4206.0536932975065, - usage_rate_lower_limit=7353.272756502293, - usage_rate_upper_68PPR=5124.8129813156465, - usage_rate_upper_95PPR=264.3386978845277, - usage_rate_upper_limit=4474.174577002476, - ) + frbc_usage_forecast_element = FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=3339)), usage_rate_upper_limit=5657.302338158246, usage_rate_upper_95PPR=3774.615782357365, usage_rate_upper_68PPR=8333.351165894339, usage_rate_expected=8333.127007404517, usage_rate_lower_68PPR=6418.649606433992, usage_rate_lower_95PPR=3342.9603968663487, usage_rate_lower_limit=8970.532671485054) # Act json_str = frbc_usage_forecast_element.to_json() # Assert - expected_json = { - "duration": 9317, - "usage_rate_expected": 866.9362374046218, - "usage_rate_lower_68PPR": 3496.6233093198375, - "usage_rate_lower_95PPR": 4206.0536932975065, - "usage_rate_lower_limit": 7353.272756502293, - "usage_rate_upper_68PPR": 5124.8129813156465, - "usage_rate_upper_95PPR": 264.3386978845277, - "usage_rate_upper_limit": 4474.174577002476, - } + expected_json = { 'duration': 3339, + 'usage_rate_expected': 8333.127007404517, + 'usage_rate_lower_68PPR': 6418.649606433992, + 'usage_rate_lower_95PPR': 3342.9603968663487, + 'usage_rate_lower_limit': 8970.532671485054, + 'usage_rate_upper_68PPR': 8333.351165894339, + 'usage_rate_upper_95PPR': 3774.615782357365, + 'usage_rate_upper_limit': 5657.302338158246} self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_test.py b/tests/unit/frbc/frbc_usage_forecast_test.py index a7f0da1..98e06fd 100644 --- a/tests/unit/frbc/frbc_usage_forecast_test.py +++ b/tests/unit/frbc/frbc_usage_forecast_test.py @@ -1,3 +1,4 @@ + from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -12,21 +13,21 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_type": "FRBC.UsageForecast", + "message_id": "88f742b5-ef8e-4af9-8ae4-633922859a8e", + "start_time": "2022-11-21T01:57:56-09:00", "elements": [ { - "duration": 14010, - "usage_rate_expected": 8032.572599815139, - "usage_rate_lower_68PPR": 3910.197692207213, - "usage_rate_lower_95PPR": 6541.633895752248, - "usage_rate_lower_limit": 3419.1709124422173, - "usage_rate_upper_68PPR": 7146.0702352976305, - "usage_rate_upper_95PPR": 627.7040858037238, - "usage_rate_upper_limit": 8477.800850190179 + "duration": 8336, + "usage_rate_upper_limit": 2600.090517037444, + "usage_rate_upper_95PPR": 5466.368905051084, + "usage_rate_upper_68PPR": 7874.9948212758245, + "usage_rate_expected": 1233.4751600765392, + "usage_rate_lower_68PPR": 1785.90944809586, + "usage_rate_lower_95PPR": 1047.0960233716157, + "usage_rate_lower_limit": 4680.219153555034 } - ], - "message_id": "4a91b4ab-21fb-42ae-b97d-6170f8b922cc", - "message_type": "FRBC.UsageForecast", - "start_time": "2023-03-25T13:48:35+02:00" + ] } """ @@ -34,86 +35,28 @@ def test__from_json__happy_path_full(self): frbc_usage_forecast = FRBCUsageForecast.from_json(json_str) # Assert - self.assertEqual( - frbc_usage_forecast.elements, - [ - FRBCUsageForecastElement( - duration=Duration.from_timedelta(timedelta(milliseconds=14010)), - usage_rate_expected=8032.572599815139, - usage_rate_lower_68PPR=3910.197692207213, - usage_rate_lower_95PPR=6541.633895752248, - usage_rate_lower_limit=3419.1709124422173, - usage_rate_upper_68PPR=7146.0702352976305, - usage_rate_upper_95PPR=627.7040858037238, - usage_rate_upper_limit=8477.800850190179, - ) - ], - ) - self.assertEqual( - frbc_usage_forecast.message_id, - uuid.UUID("4a91b4ab-21fb-42ae-b97d-6170f8b922cc"), - ) - self.assertEqual(frbc_usage_forecast.message_type, "FRBC.UsageForecast") - self.assertEqual( - frbc_usage_forecast.start_time, - datetime( - year=2023, - month=3, - day=25, - hour=13, - minute=48, - second=35, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - ) + self.assertEqual(frbc_usage_forecast.message_type, FRBC.UsageForecast) + self.assertEqual(frbc_usage_forecast.message_id, uuid.UUID("88f742b5-ef8e-4af9-8ae4-633922859a8e")) + self.assertEqual(frbc_usage_forecast.start_time, datetime(year=2022, month=11, day=21, hour=1, minute=57, second=56, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) + self.assertEqual(frbc_usage_forecast.elements, [FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=8336)), usage_rate_upper_limit=2600.090517037444, usage_rate_upper_95PPR=5466.368905051084, usage_rate_upper_68PPR=7874.9948212758245, usage_rate_expected=1233.4751600765392, usage_rate_lower_68PPR=1785.90944809586, usage_rate_lower_95PPR=1047.0960233716157, usage_rate_lower_limit=4680.219153555034)]) def test__to_json__happy_path_full(self): # Arrange - frbc_usage_forecast = FRBCUsageForecast( - elements=[ - FRBCUsageForecastElement( - duration=Duration.from_timedelta(timedelta(milliseconds=14010)), - usage_rate_expected=8032.572599815139, - usage_rate_lower_68PPR=3910.197692207213, - usage_rate_lower_95PPR=6541.633895752248, - usage_rate_lower_limit=3419.1709124422173, - usage_rate_upper_68PPR=7146.0702352976305, - usage_rate_upper_95PPR=627.7040858037238, - usage_rate_upper_limit=8477.800850190179, - ) - ], - message_id=uuid.UUID("4a91b4ab-21fb-42ae-b97d-6170f8b922cc"), - message_type="FRBC.UsageForecast", - start_time=datetime( - year=2023, - month=3, - day=25, - hour=13, - minute=48, - second=35, - tzinfo=offset(offset=timedelta(seconds=7200.0)), - ), - ) + frbc_usage_forecast = FRBCUsageForecast(message_type=FRBC.UsageForecast, message_id=uuid.UUID("88f742b5-ef8e-4af9-8ae4-633922859a8e"), start_time=datetime(year=2022, month=11, day=21, hour=1, minute=57, second=56, tzinfo=offset(offset=timedelta(seconds=-32400.0))), elements=[FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=8336)), usage_rate_upper_limit=2600.090517037444, usage_rate_upper_95PPR=5466.368905051084, usage_rate_upper_68PPR=7874.9948212758245, usage_rate_expected=1233.4751600765392, usage_rate_lower_68PPR=1785.90944809586, usage_rate_lower_95PPR=1047.0960233716157, usage_rate_lower_limit=4680.219153555034)]) # Act json_str = frbc_usage_forecast.to_json() # Assert - expected_json = { - "elements": [ - { - "duration": 14010, - "usage_rate_expected": 8032.572599815139, - "usage_rate_lower_68PPR": 3910.197692207213, - "usage_rate_lower_95PPR": 6541.633895752248, - "usage_rate_lower_limit": 3419.1709124422173, - "usage_rate_upper_68PPR": 7146.0702352976305, - "usage_rate_upper_95PPR": 627.7040858037238, - "usage_rate_upper_limit": 8477.800850190179, - } - ], - "message_id": "4a91b4ab-21fb-42ae-b97d-6170f8b922cc", - "message_type": "FRBC.UsageForecast", - "start_time": "2023-03-25T13:48:35+02:00", - } + expected_json = { 'elements': [ { 'duration': 8336, + 'usage_rate_expected': 1233.4751600765392, + 'usage_rate_lower_68PPR': 1785.90944809586, + 'usage_rate_lower_95PPR': 1047.0960233716157, + 'usage_rate_lower_limit': 4680.219153555034, + 'usage_rate_upper_68PPR': 7874.9948212758245, + 'usage_rate_upper_95PPR': 5466.368905051084, + 'usage_rate_upper_limit': 2600.090517037444}], + 'message_id': '88f742b5-ef8e-4af9-8ae4-633922859a8e', + 'message_type': 'FRBC.UsageForecast', + 'start_time': '2022-11-21T01:57:56-09:00'} self.assertEqual(json.loads(json_str), expected_json) From ca3465ffa37091a418b078db40f8307b4b6e2803 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Mon, 23 Dec 2024 10:10:12 +0200 Subject: [PATCH 07/27] Ignore the randomly generated unit tests --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8a082ed..d197a2d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ venv .tox/ dist/ build/ +tests/unit/frbc From 699800fa937e6778f4ddaac67c4a2667d85e91f9 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Mon, 23 Dec 2024 10:17:57 +0200 Subject: [PATCH 08/27] Stop tracking ignored files --- .../frbc/frbc_actuator_description_test.py | 122 -------------- tests/unit/frbc/frbc_actuator_status_test.py | 53 ------- ..._fill_level_target_profile_element_test.py | 42 ----- .../frbc_fill_level_target_profile_test.py | 54 ------- tests/unit/frbc/frbc_instruction_test.py | 56 ------- .../frbc_leakage_behaviour_element_test.py | 42 ----- .../unit/frbc/frbc_leakage_behaviour_test.py | 54 ------- .../frbc/frbc_operation_mode_element_test.py | 64 -------- tests/unit/frbc/frbc_operation_mode_test.py | 74 --------- .../frbc/frbc_storage_description_test.py | 54 ------- tests/unit/frbc/frbc_storage_status_test.py | 41 ----- .../unit/frbc/frbc_system_description_test.py | 149 ------------------ tests/unit/frbc/frbc_timer_status_test.py | 47 ------ .../frbc/frbc_usage_forecast_element_test.py | 56 ------- tests/unit/frbc/frbc_usage_forecast_test.py | 62 -------- 15 files changed, 970 deletions(-) delete mode 100644 tests/unit/frbc/frbc_actuator_description_test.py delete mode 100644 tests/unit/frbc/frbc_actuator_status_test.py delete mode 100644 tests/unit/frbc/frbc_fill_level_target_profile_element_test.py delete mode 100644 tests/unit/frbc/frbc_fill_level_target_profile_test.py delete mode 100644 tests/unit/frbc/frbc_instruction_test.py delete mode 100644 tests/unit/frbc/frbc_leakage_behaviour_element_test.py delete mode 100644 tests/unit/frbc/frbc_leakage_behaviour_test.py delete mode 100644 tests/unit/frbc/frbc_operation_mode_element_test.py delete mode 100644 tests/unit/frbc/frbc_operation_mode_test.py delete mode 100644 tests/unit/frbc/frbc_storage_description_test.py delete mode 100644 tests/unit/frbc/frbc_storage_status_test.py delete mode 100644 tests/unit/frbc/frbc_system_description_test.py delete mode 100644 tests/unit/frbc/frbc_timer_status_test.py delete mode 100644 tests/unit/frbc/frbc_usage_forecast_element_test.py delete mode 100644 tests/unit/frbc/frbc_usage_forecast_test.py diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py deleted file mode 100644 index 46d3e11..0000000 --- a/tests/unit/frbc/frbc_actuator_description_test.py +++ /dev/null @@ -1,122 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCActuatorDescriptionTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "id": "5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9", - "diagnostic_label": "some-test-string8906", - "supported_commodities": [ - "GAS" - ], - "operation_modes": [ - { - "id": "512001f9-e720-40ce-88e2-06694bb2ca6d", - "diagnostic_label": "some-test-string1472", - "elements": [ - { - "fill_level_range": { - "start_of_range": 27494.741307134802, - "end_of_range": 58877.84844659244 - }, - "fill_rate": { - "start_of_range": 25438.73202815934, - "end_of_range": 32365.85082869052 - }, - "power_ranges": [ - { - "start_of_range": 810.7301251031068, - "end_of_range": 36176.5333835611, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 37284.94705878388, - "end_of_range": 61334.00977608085 - } - } - ], - "abnormal_condition_only": false - } - ], - "transitions": [ - { - "id": "5427f2cc-7d7d-4d30-bdc8-5f6594a86297", - "from_": "de2771e5-bac3-4385-8ee1-ad1500b2905f", - "to": "a169553d-cac4-4b29-9cce-9a9a17b5aebd", - "start_timers": [ - "26538354-e139-448d-9cb1-19da008bbd43" - ], - "blocking_timers": [ - "947ad518-3305-4be4-b847-a4b01a0bfd01" - ], - "transition_costs": 3119.39267710221, - "transition_duration": 21417, - "abnormal_condition_only": false - } - ], - "timers": [ - { - "id": "166fdf46-2695-412e-a014-dbd95dee1eb2", - "diagnostic_label": "some-test-string9309", - "duration": 16076 - } - ] -} - """ - - # Act - frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) - - # Assert - self.assertEqual(frbc_actuator_description.id, uuid.UUID("5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9")) - self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string8906") - self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) - self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("512001f9-e720-40ce-88e2-06694bb2ca6d"), diagnostic_label="some-test-string1472", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=27494.741307134802, end_of_range=58877.84844659244), fill_rate=NumberRange(start_of_range=25438.73202815934, end_of_range=32365.85082869052), power_ranges=[PowerRange(start_of_range=810.7301251031068, end_of_range=36176.5333835611, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=37284.94705878388, end_of_range=61334.00977608085))], abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("5427f2cc-7d7d-4d30-bdc8-5f6594a86297"), from_=uuid.UUID("de2771e5-bac3-4385-8ee1-ad1500b2905f"), to=uuid.UUID("a169553d-cac4-4b29-9cce-9a9a17b5aebd"), start_timers=[uuid.UUID("26538354-e139-448d-9cb1-19da008bbd43")], blocking_timers=[uuid.UUID("947ad518-3305-4be4-b847-a4b01a0bfd01")], transition_costs=3119.39267710221, transition_duration=Duration.from_timedelta(timedelta(milliseconds=21417)), abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("166fdf46-2695-412e-a014-dbd95dee1eb2"), diagnostic_label="some-test-string9309", duration=Duration.from_timedelta(timedelta(milliseconds=16076)))]) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9"), diagnostic_label="some-test-string8906", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("512001f9-e720-40ce-88e2-06694bb2ca6d"), diagnostic_label="some-test-string1472", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=27494.741307134802, end_of_range=58877.84844659244), fill_rate=NumberRange(start_of_range=25438.73202815934, end_of_range=32365.85082869052), power_ranges=[PowerRange(start_of_range=810.7301251031068, end_of_range=36176.5333835611, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=37284.94705878388, end_of_range=61334.00977608085))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5427f2cc-7d7d-4d30-bdc8-5f6594a86297"), from_=uuid.UUID("de2771e5-bac3-4385-8ee1-ad1500b2905f"), to=uuid.UUID("a169553d-cac4-4b29-9cce-9a9a17b5aebd"), start_timers=[uuid.UUID("26538354-e139-448d-9cb1-19da008bbd43")], blocking_timers=[uuid.UUID("947ad518-3305-4be4-b847-a4b01a0bfd01")], transition_costs=3119.39267710221, transition_duration=Duration.from_timedelta(timedelta(milliseconds=21417)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("166fdf46-2695-412e-a014-dbd95dee1eb2"), diagnostic_label="some-test-string9309", duration=Duration.from_timedelta(timedelta(milliseconds=16076)))]) - - # Act - json_str = frbc_actuator_description.to_json() - - # Assert - expected_json = { 'diagnostic_label': 'some-test-string8906', - 'id': '5ebe5f2d-3046-48b3-b2ce-5f4c58630fc9', - 'operation_modes': [ { 'abnormal_condition_only': False, - 'diagnostic_label': 'some-test-string1472', - 'elements': [ { 'fill_level_range': { 'end_of_range': 58877.84844659244, - 'start_of_range': 27494.741307134802}, - 'fill_rate': { 'end_of_range': 32365.85082869052, - 'start_of_range': 25438.73202815934}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 36176.5333835611, - 'start_of_range': 810.7301251031068}], - 'running_costs': { 'end_of_range': 61334.00977608085, - 'start_of_range': 37284.94705878388}}], - 'id': '512001f9-e720-40ce-88e2-06694bb2ca6d'}], - 'supported_commodities': ['GAS'], - 'timers': [ { 'diagnostic_label': 'some-test-string9309', - 'duration': 16076, - 'id': '166fdf46-2695-412e-a014-dbd95dee1eb2'}], - 'transitions': [ { 'abnormal_condition_only': False, - 'blocking_timers': [ '947ad518-3305-4be4-b847-a4b01a0bfd01'], - 'from_': 'de2771e5-bac3-4385-8ee1-ad1500b2905f', - 'id': '5427f2cc-7d7d-4d30-bdc8-5f6594a86297', - 'start_timers': [ '26538354-e139-448d-9cb1-19da008bbd43'], - 'to': 'a169553d-cac4-4b29-9cce-9a9a17b5aebd', - 'transition_costs': 3119.39267710221, - 'transition_duration': 21417}]} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py deleted file mode 100644 index 819a923..0000000 --- a/tests/unit/frbc/frbc_actuator_status_test.py +++ /dev/null @@ -1,53 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCActuatorStatusTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.ActuatorStatus", - "message_id": "5d000419-7f2c-451a-b803-ebcc72b7d0bd", - "actuator_id": "9752ddc9-b04c-4902-a92c-3f181eafd270", - "active_operation_mode_id": "8d00a902-e202-4c6a-876a-4182bb83c8ac", - "operation_mode_factor": 39.51079942572766, - "previous_operation_mode_id": "767ff15a-e406-4ad3-8d74-0f470f9b0589", - "transition_timestamp": "2022-12-10T08:41:46+07:00" -} - """ - - # Act - frbc_actuator_status = FRBCActuatorStatus.from_json(json_str) - - # Assert - self.assertEqual(frbc_actuator_status.message_type, FRBC.ActuatorStatus) - self.assertEqual(frbc_actuator_status.message_id, uuid.UUID("5d000419-7f2c-451a-b803-ebcc72b7d0bd")) - self.assertEqual(frbc_actuator_status.actuator_id, uuid.UUID("9752ddc9-b04c-4902-a92c-3f181eafd270")) - self.assertEqual(frbc_actuator_status.active_operation_mode_id, uuid.UUID("8d00a902-e202-4c6a-876a-4182bb83c8ac")) - self.assertEqual(frbc_actuator_status.operation_mode_factor, 39.51079942572766) - self.assertEqual(frbc_actuator_status.previous_operation_mode_id, uuid.UUID("767ff15a-e406-4ad3-8d74-0f470f9b0589")) - self.assertEqual(frbc_actuator_status.transition_timestamp, datetime(year=2022, month=12, day=10, hour=8, minute=41, second=46, tzinfo=offset(offset=timedelta(seconds=25200.0)))) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_actuator_status = FRBCActuatorStatus(message_type=FRBC.ActuatorStatus, message_id=uuid.UUID("5d000419-7f2c-451a-b803-ebcc72b7d0bd"), actuator_id=uuid.UUID("9752ddc9-b04c-4902-a92c-3f181eafd270"), active_operation_mode_id=uuid.UUID("8d00a902-e202-4c6a-876a-4182bb83c8ac"), operation_mode_factor=39.51079942572766, previous_operation_mode_id=uuid.UUID("767ff15a-e406-4ad3-8d74-0f470f9b0589"), transition_timestamp=datetime(year=2022, month=12, day=10, hour=8, minute=41, second=46, tzinfo=offset(offset=timedelta(seconds=25200.0)))) - - # Act - json_str = frbc_actuator_status.to_json() - - # Assert - expected_json = { 'active_operation_mode_id': '8d00a902-e202-4c6a-876a-4182bb83c8ac', - 'actuator_id': '9752ddc9-b04c-4902-a92c-3f181eafd270', - 'message_id': '5d000419-7f2c-451a-b803-ebcc72b7d0bd', - 'message_type': 'FRBC.ActuatorStatus', - 'operation_mode_factor': 39.51079942572766, - 'previous_operation_mode_id': '767ff15a-e406-4ad3-8d74-0f470f9b0589', - 'transition_timestamp': '2022-12-10T08:41:46+07:00'} - self.assertEqual(json.loads(json_str), expected_json) 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 deleted file mode 100644 index 5f72002..0000000 --- a/tests/unit/frbc/frbc_fill_level_target_profile_element_test.py +++ /dev/null @@ -1,42 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCFillLevelTargetProfileElementTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "duration": 16041, - "fill_level_range": { - "start_of_range": 38789.06538190935, - "end_of_range": 45889.620464907246 - } -} - """ - - # Act - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json(json_str) - - # Assert - self.assertEqual(frbc_fill_level_target_profile_element.duration, Duration.from_timedelta(timedelta(milliseconds=16041))) - self.assertEqual(frbc_fill_level_target_profile_element.fill_level_range, NumberRange(start_of_range=38789.06538190935, end_of_range=45889.620464907246)) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=16041)), fill_level_range=NumberRange(start_of_range=38789.06538190935, end_of_range=45889.620464907246)) - - # Act - json_str = frbc_fill_level_target_profile_element.to_json() - - # Assert - expected_json = { 'duration': 16041, - 'fill_level_range': { 'end_of_range': 45889.620464907246, - 'start_of_range': 38789.06538190935}} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_fill_level_target_profile_test.py b/tests/unit/frbc/frbc_fill_level_target_profile_test.py deleted file mode 100644 index 5142704..0000000 --- a/tests/unit/frbc/frbc_fill_level_target_profile_test.py +++ /dev/null @@ -1,54 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCFillLevelTargetProfileTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.FillLevelTargetProfile", - "message_id": "698ea780-c918-4ea8-aef2-885a5c0228ad", - "start_time": "2020-05-16T18:38:12+08:00", - "elements": [ - { - "duration": 30067, - "fill_level_range": { - "start_of_range": 18107.25728114559, - "end_of_range": 51078.86550304321 - } - } - ] -} - """ - - # Act - frbc_fill_level_target_profile = FRBCFillLevelTargetProfile.from_json(json_str) - - # Assert - self.assertEqual(frbc_fill_level_target_profile.message_type, FRBC.FillLevelTargetProfile) - self.assertEqual(frbc_fill_level_target_profile.message_id, uuid.UUID("698ea780-c918-4ea8-aef2-885a5c0228ad")) - self.assertEqual(frbc_fill_level_target_profile.start_time, datetime(year=2020, month=5, day=16, hour=18, minute=38, second=12, tzinfo=offset(offset=timedelta(seconds=28800.0)))) - self.assertEqual(frbc_fill_level_target_profile.elements, [FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=30067)), fill_level_range=NumberRange(start_of_range=18107.25728114559, end_of_range=51078.86550304321))]) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_fill_level_target_profile = FRBCFillLevelTargetProfile(message_type=FRBC.FillLevelTargetProfile, message_id=uuid.UUID("698ea780-c918-4ea8-aef2-885a5c0228ad"), start_time=datetime(year=2020, month=5, day=16, hour=18, minute=38, second=12, tzinfo=offset(offset=timedelta(seconds=28800.0))), elements=[FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=30067)), fill_level_range=NumberRange(start_of_range=18107.25728114559, end_of_range=51078.86550304321))]) - - # Act - json_str = frbc_fill_level_target_profile.to_json() - - # Assert - expected_json = { 'elements': [ { 'duration': 30067, - 'fill_level_range': { 'end_of_range': 51078.86550304321, - 'start_of_range': 18107.25728114559}}], - 'message_id': '698ea780-c918-4ea8-aef2-885a5c0228ad', - 'message_type': 'FRBC.FillLevelTargetProfile', - 'start_time': '2020-05-16T18:38:12+08:00'} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_instruction_test.py b/tests/unit/frbc/frbc_instruction_test.py deleted file mode 100644 index 9f552e6..0000000 --- a/tests/unit/frbc/frbc_instruction_test.py +++ /dev/null @@ -1,56 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCInstructionTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.Instruction", - "message_id": "185b511f-725d-42c8-a1db-523cda5c45a9", - "id": "8db97bd0-258e-4a8b-92be-40e10b1c7a2c", - "actuator_id": "cb06550c-e8a2-418d-8885-24a530dde4c4", - "operation_mode": "4a687962-da90-4379-9190-5d1d68f9dd18", - "operation_mode_factor": 4456.156981790672, - "execution_time": "2020-11-11T07:59:54-11:00", - "abnormal_condition": true -} - """ - - # Act - frbc_instruction = FRBCInstruction.from_json(json_str) - - # Assert - self.assertEqual(frbc_instruction.message_type, FRBC.Instruction) - self.assertEqual(frbc_instruction.message_id, uuid.UUID("185b511f-725d-42c8-a1db-523cda5c45a9")) - self.assertEqual(frbc_instruction.id, uuid.UUID("8db97bd0-258e-4a8b-92be-40e10b1c7a2c")) - self.assertEqual(frbc_instruction.actuator_id, uuid.UUID("cb06550c-e8a2-418d-8885-24a530dde4c4")) - self.assertEqual(frbc_instruction.operation_mode, uuid.UUID("4a687962-da90-4379-9190-5d1d68f9dd18")) - self.assertEqual(frbc_instruction.operation_mode_factor, 4456.156981790672) - self.assertEqual(frbc_instruction.execution_time, datetime(year=2020, month=11, day=11, hour=7, minute=59, second=54, tzinfo=offset(offset=timedelta(seconds=-39600.0)))) - self.assertEqual(frbc_instruction.abnormal_condition, True) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_instruction = FRBCInstruction(message_type=FRBC.Instruction, message_id=uuid.UUID("185b511f-725d-42c8-a1db-523cda5c45a9"), id=uuid.UUID("8db97bd0-258e-4a8b-92be-40e10b1c7a2c"), actuator_id=uuid.UUID("cb06550c-e8a2-418d-8885-24a530dde4c4"), operation_mode=uuid.UUID("4a687962-da90-4379-9190-5d1d68f9dd18"), operation_mode_factor=4456.156981790672, execution_time=datetime(year=2020, month=11, day=11, hour=7, minute=59, second=54, tzinfo=offset(offset=timedelta(seconds=-39600.0))), abnormal_condition=True) - - # Act - json_str = frbc_instruction.to_json() - - # Assert - expected_json = { 'abnormal_condition': True, - 'actuator_id': 'cb06550c-e8a2-418d-8885-24a530dde4c4', - 'execution_time': '2020-11-11T07:59:54-11:00', - 'id': '8db97bd0-258e-4a8b-92be-40e10b1c7a2c', - 'message_id': '185b511f-725d-42c8-a1db-523cda5c45a9', - 'message_type': 'FRBC.Instruction', - 'operation_mode': '4a687962-da90-4379-9190-5d1d68f9dd18', - 'operation_mode_factor': 4456.156981790672} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py deleted file mode 100644 index e900d5d..0000000 --- a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py +++ /dev/null @@ -1,42 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCLeakageBehaviourElementTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "fill_level_range": { - "start_of_range": 28885.23485069778, - "end_of_range": 40074.88428029624 - }, - "leakage_rate": 6677.574789126709 -} - """ - - # Act - frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement.from_json(json_str) - - # Assert - self.assertEqual(frbc_leakage_behaviour_element.fill_level_range, NumberRange(start_of_range=28885.23485069778, end_of_range=40074.88428029624)) - self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 6677.574789126709) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=28885.23485069778, end_of_range=40074.88428029624), leakage_rate=6677.574789126709) - - # Act - json_str = frbc_leakage_behaviour_element.to_json() - - # Assert - expected_json = { 'fill_level_range': { 'end_of_range': 40074.88428029624, - 'start_of_range': 28885.23485069778}, - 'leakage_rate': 6677.574789126709} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_test.py b/tests/unit/frbc/frbc_leakage_behaviour_test.py deleted file mode 100644 index ac7155c..0000000 --- a/tests/unit/frbc/frbc_leakage_behaviour_test.py +++ /dev/null @@ -1,54 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCLeakageBehaviourTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.LeakageBehaviour", - "message_id": "f681fa18-589b-4081-a85b-cdb608795248", - "valid_from": "2023-09-26T17:14:46+13:00", - "elements": [ - { - "fill_level_range": { - "start_of_range": 5349.224337716178, - "end_of_range": 26642.603531976765 - }, - "leakage_rate": 3062.489155126407 - } - ] -} - """ - - # Act - frbc_leakage_behaviour = FRBCLeakageBehaviour.from_json(json_str) - - # Assert - self.assertEqual(frbc_leakage_behaviour.message_type, FRBC.LeakageBehaviour) - self.assertEqual(frbc_leakage_behaviour.message_id, uuid.UUID("f681fa18-589b-4081-a85b-cdb608795248")) - self.assertEqual(frbc_leakage_behaviour.valid_from, datetime(year=2023, month=9, day=26, hour=17, minute=14, second=46, tzinfo=offset(offset=timedelta(seconds=46800.0)))) - self.assertEqual(frbc_leakage_behaviour.elements, [FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=5349.224337716178, end_of_range=26642.603531976765), leakage_rate=3062.489155126407)]) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_leakage_behaviour = FRBCLeakageBehaviour(message_type=FRBC.LeakageBehaviour, message_id=uuid.UUID("f681fa18-589b-4081-a85b-cdb608795248"), valid_from=datetime(year=2023, month=9, day=26, hour=17, minute=14, second=46, tzinfo=offset(offset=timedelta(seconds=46800.0))), elements=[FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=5349.224337716178, end_of_range=26642.603531976765), leakage_rate=3062.489155126407)]) - - # Act - json_str = frbc_leakage_behaviour.to_json() - - # Assert - expected_json = { 'elements': [ { 'fill_level_range': { 'end_of_range': 26642.603531976765, - 'start_of_range': 5349.224337716178}, - 'leakage_rate': 3062.489155126407}], - 'message_id': 'f681fa18-589b-4081-a85b-cdb608795248', - 'message_type': 'FRBC.LeakageBehaviour', - 'valid_from': '2023-09-26T17:14:46+13:00'} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_element_test.py b/tests/unit/frbc/frbc_operation_mode_element_test.py deleted file mode 100644 index 2c8bcba..0000000 --- a/tests/unit/frbc/frbc_operation_mode_element_test.py +++ /dev/null @@ -1,64 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCOperationModeElementTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "fill_level_range": { - "start_of_range": 2839.6809937410153, - "end_of_range": 23657.59074127252 - }, - "fill_rate": { - "start_of_range": 31115.515599679075, - "end_of_range": 34674.7451119136 - }, - "power_ranges": [ - { - "start_of_range": 28918.644493729425, - "end_of_range": 62283.65396785374, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 27450.2722984119, - "end_of_range": 63683.41689038279 - } -} - """ - - # Act - frbc_operation_mode_element = FRBCOperationModeElement.from_json(json_str) - - # Assert - self.assertEqual(frbc_operation_mode_element.fill_level_range, NumberRange(start_of_range=2839.6809937410153, end_of_range=23657.59074127252)) - self.assertEqual(frbc_operation_mode_element.fill_rate, NumberRange(start_of_range=31115.515599679075, end_of_range=34674.7451119136)) - self.assertEqual(frbc_operation_mode_element.power_ranges, [PowerRange(start_of_range=28918.644493729425, end_of_range=62283.65396785374, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)]) - self.assertEqual(frbc_operation_mode_element.running_costs, NumberRange(start_of_range=27450.2722984119, end_of_range=63683.41689038279)) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_operation_mode_element = FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=2839.6809937410153, end_of_range=23657.59074127252), fill_rate=NumberRange(start_of_range=31115.515599679075, end_of_range=34674.7451119136), power_ranges=[PowerRange(start_of_range=28918.644493729425, end_of_range=62283.65396785374, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=27450.2722984119, end_of_range=63683.41689038279)) - - # Act - json_str = frbc_operation_mode_element.to_json() - - # Assert - expected_json = { 'fill_level_range': { 'end_of_range': 23657.59074127252, - 'start_of_range': 2839.6809937410153}, - 'fill_rate': { 'end_of_range': 34674.7451119136, - 'start_of_range': 31115.515599679075}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 62283.65396785374, - 'start_of_range': 28918.644493729425}], - 'running_costs': { 'end_of_range': 63683.41689038279, - 'start_of_range': 27450.2722984119}} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_test.py b/tests/unit/frbc/frbc_operation_mode_test.py deleted file mode 100644 index 446de12..0000000 --- a/tests/unit/frbc/frbc_operation_mode_test.py +++ /dev/null @@ -1,74 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCOperationModeTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "id": "44ea8c08-6aca-4b93-8434-ede68200dc69", - "diagnostic_label": "some-test-string6411", - "elements": [ - { - "fill_level_range": { - "start_of_range": 13185.562172385307, - "end_of_range": 28351.769654896747 - }, - "fill_rate": { - "start_of_range": 25266.999524961477, - "end_of_range": 34750.38438764264 - }, - "power_ranges": [ - { - "start_of_range": 14986.569871211224, - "end_of_range": 24935.417325009203, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 18871.059938463823, - "end_of_range": 30857.989097156864 - } - } - ], - "abnormal_condition_only": false -} - """ - - # Act - frbc_operation_mode = FRBCOperationMode.from_json(json_str) - - # Assert - self.assertEqual(frbc_operation_mode.id, uuid.UUID("44ea8c08-6aca-4b93-8434-ede68200dc69")) - self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string6411") - self.assertEqual(frbc_operation_mode.elements, [FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=13185.562172385307, end_of_range=28351.769654896747), fill_rate=NumberRange(start_of_range=25266.999524961477, end_of_range=34750.38438764264), power_ranges=[PowerRange(start_of_range=14986.569871211224, end_of_range=24935.417325009203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=18871.059938463823, end_of_range=30857.989097156864))]) - self.assertEqual(frbc_operation_mode.abnormal_condition_only, False) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_operation_mode = FRBCOperationMode(id=uuid.UUID("44ea8c08-6aca-4b93-8434-ede68200dc69"), diagnostic_label="some-test-string6411", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=13185.562172385307, end_of_range=28351.769654896747), fill_rate=NumberRange(start_of_range=25266.999524961477, end_of_range=34750.38438764264), power_ranges=[PowerRange(start_of_range=14986.569871211224, end_of_range=24935.417325009203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=18871.059938463823, end_of_range=30857.989097156864))], abnormal_condition_only=False) - - # Act - json_str = frbc_operation_mode.to_json() - - # Assert - expected_json = { 'abnormal_condition_only': False, - 'diagnostic_label': 'some-test-string6411', - 'elements': [ { 'fill_level_range': { 'end_of_range': 28351.769654896747, - 'start_of_range': 13185.562172385307}, - 'fill_rate': { 'end_of_range': 34750.38438764264, - 'start_of_range': 25266.999524961477}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 24935.417325009203, - 'start_of_range': 14986.569871211224}], - 'running_costs': { 'end_of_range': 30857.989097156864, - 'start_of_range': 18871.059938463823}}], - 'id': '44ea8c08-6aca-4b93-8434-ede68200dc69'} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_description_test.py b/tests/unit/frbc/frbc_storage_description_test.py deleted file mode 100644 index 948202c..0000000 --- a/tests/unit/frbc/frbc_storage_description_test.py +++ /dev/null @@ -1,54 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCStorageDescriptionTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "diagnostic_label": "some-test-string6024", - "fill_level_label": "some-test-string4194", - "provides_leakage_behaviour": true, - "provides_fill_level_target_profile": true, - "provides_usage_forecast": false, - "fill_level_range": { - "start_of_range": 4284.806107128117, - "end_of_range": 15952.434979774753 - } -} - """ - - # Act - frbc_storage_description = FRBCStorageDescription.from_json(json_str) - - # Assert - self.assertEqual(frbc_storage_description.diagnostic_label, "some-test-string6024") - self.assertEqual(frbc_storage_description.fill_level_label, "some-test-string4194") - self.assertEqual(frbc_storage_description.provides_leakage_behaviour, True) - self.assertEqual(frbc_storage_description.provides_fill_level_target_profile, True) - self.assertEqual(frbc_storage_description.provides_usage_forecast, False) - self.assertEqual(frbc_storage_description.fill_level_range, NumberRange(start_of_range=4284.806107128117, end_of_range=15952.434979774753)) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_storage_description = FRBCStorageDescription(diagnostic_label="some-test-string6024", fill_level_label="some-test-string4194", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=False, fill_level_range=NumberRange(start_of_range=4284.806107128117, end_of_range=15952.434979774753)) - - # Act - json_str = frbc_storage_description.to_json() - - # Assert - expected_json = { 'diagnostic_label': 'some-test-string6024', - 'fill_level_label': 'some-test-string4194', - 'fill_level_range': { 'end_of_range': 15952.434979774753, - 'start_of_range': 4284.806107128117}, - 'provides_fill_level_target_profile': True, - 'provides_leakage_behaviour': True, - 'provides_usage_forecast': False} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_status_test.py b/tests/unit/frbc/frbc_storage_status_test.py deleted file mode 100644 index 2d819cf..0000000 --- a/tests/unit/frbc/frbc_storage_status_test.py +++ /dev/null @@ -1,41 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCStorageStatusTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.StorageStatus", - "message_id": "84266c85-e666-49b3-a4e5-a1feff77df6e", - "present_fill_level": 8890.677190972274 -} - """ - - # Act - frbc_storage_status = FRBCStorageStatus.from_json(json_str) - - # Assert - self.assertEqual(frbc_storage_status.message_type, FRBC.StorageStatus) - self.assertEqual(frbc_storage_status.message_id, uuid.UUID("84266c85-e666-49b3-a4e5-a1feff77df6e")) - self.assertEqual(frbc_storage_status.present_fill_level, 8890.677190972274) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_storage_status = FRBCStorageStatus(message_type=FRBC.StorageStatus, message_id=uuid.UUID("84266c85-e666-49b3-a4e5-a1feff77df6e"), present_fill_level=8890.677190972274) - - # Act - json_str = frbc_storage_status.to_json() - - # Assert - expected_json = { 'message_id': '84266c85-e666-49b3-a4e5-a1feff77df6e', - 'message_type': 'FRBC.StorageStatus', - 'present_fill_level': 8890.677190972274} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py deleted file mode 100644 index f943a1a..0000000 --- a/tests/unit/frbc/frbc_system_description_test.py +++ /dev/null @@ -1,149 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCSystemDescriptionTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.SystemDescription", - "message_id": "873112de-a6d8-4123-a523-fc908e9a0310", - "valid_from": "2021-07-20T04:50:11+12:00", - "actuators": [ - { - "id": "9d5243b4-f92c-42cd-9d6b-00e3e5953dd4", - "diagnostic_label": "some-test-string2832", - "supported_commodities": [ - "GAS" - ], - "operation_modes": [ - { - "id": "4526bc26-cdd8-4cd6-ae35-dfb2a6751375", - "diagnostic_label": "some-test-string2393", - "elements": [ - { - "fill_level_range": { - "start_of_range": 22530.41813108864, - "end_of_range": 50141.518189827795 - }, - "fill_rate": { - "start_of_range": 10937.461391570358, - "end_of_range": 22111.530603779658 - }, - "power_ranges": [ - { - "start_of_range": 3372.255894277375, - "end_of_range": 11155.631994233203, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 31076.328103540887, - "end_of_range": 38380.84063645549 - } - } - ], - "abnormal_condition_only": false - } - ], - "transitions": [ - { - "id": "5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e", - "from_": "cac664cf-bec3-49df-aeb4-d5dca13f3f0b", - "to": "61605548-7d68-4ceb-bbe2-b4d79d385b11", - "start_timers": [ - "d2436620-dce4-43fe-a59d-a7bc3045a7cd" - ], - "blocking_timers": [ - "2f690536-73d5-4441-90b4-a28fa60e962f" - ], - "transition_costs": 2339.5028721662775, - "transition_duration": 35219, - "abnormal_condition_only": true - } - ], - "timers": [ - { - "id": "0c8a46de-b2bf-4adc-9052-0d2aa5dade66", - "diagnostic_label": "some-test-string8913", - "duration": 20103 - } - ] - } - ], - "storage": { - "diagnostic_label": "some-test-string816", - "fill_level_label": "some-test-string5484", - "provides_leakage_behaviour": true, - "provides_fill_level_target_profile": true, - "provides_usage_forecast": true, - "fill_level_range": { - "start_of_range": 16537.091716121224, - "end_of_range": 19907.512143139258 - } - } -} - """ - - # Act - frbc_system_description = FRBCSystemDescription.from_json(json_str) - - # Assert - self.assertEqual(frbc_system_description.message_type, FRBC.SystemDescription) - self.assertEqual(frbc_system_description.message_id, uuid.UUID("873112de-a6d8-4123-a523-fc908e9a0310")) - self.assertEqual(frbc_system_description.valid_from, datetime(year=2021, month=7, day=20, hour=4, minute=50, second=11, tzinfo=offset(offset=timedelta(seconds=43200.0)))) - self.assertEqual(frbc_system_description.actuators, [FRBCActuatorDescription(id=uuid.UUID("9d5243b4-f92c-42cd-9d6b-00e3e5953dd4"), diagnostic_label="some-test-string2832", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("4526bc26-cdd8-4cd6-ae35-dfb2a6751375"), diagnostic_label="some-test-string2393", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=22530.41813108864, end_of_range=50141.518189827795), fill_rate=NumberRange(start_of_range=10937.461391570358, end_of_range=22111.530603779658), power_ranges=[PowerRange(start_of_range=3372.255894277375, end_of_range=11155.631994233203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=31076.328103540887, end_of_range=38380.84063645549))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e"), from_=uuid.UUID("cac664cf-bec3-49df-aeb4-d5dca13f3f0b"), to=uuid.UUID("61605548-7d68-4ceb-bbe2-b4d79d385b11"), start_timers=[uuid.UUID("d2436620-dce4-43fe-a59d-a7bc3045a7cd")], blocking_timers=[uuid.UUID("2f690536-73d5-4441-90b4-a28fa60e962f")], transition_costs=2339.5028721662775, transition_duration=Duration.from_timedelta(timedelta(milliseconds=35219)), abnormal_condition_only=True)], timers=[Timer(id=uuid.UUID("0c8a46de-b2bf-4adc-9052-0d2aa5dade66"), diagnostic_label="some-test-string8913", duration=Duration.from_timedelta(timedelta(milliseconds=20103)))])]) - self.assertEqual(frbc_system_description.storage, FRBCStorageDescription(diagnostic_label="some-test-string816", fill_level_label="some-test-string5484", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=16537.091716121224, end_of_range=19907.512143139258))) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_system_description = FRBCSystemDescription(message_type=FRBC.SystemDescription, message_id=uuid.UUID("873112de-a6d8-4123-a523-fc908e9a0310"), valid_from=datetime(year=2021, month=7, day=20, hour=4, minute=50, second=11, tzinfo=offset(offset=timedelta(seconds=43200.0))), actuators=[FRBCActuatorDescription(id=uuid.UUID("9d5243b4-f92c-42cd-9d6b-00e3e5953dd4"), diagnostic_label="some-test-string2832", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("4526bc26-cdd8-4cd6-ae35-dfb2a6751375"), diagnostic_label="some-test-string2393", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=22530.41813108864, end_of_range=50141.518189827795), fill_rate=NumberRange(start_of_range=10937.461391570358, end_of_range=22111.530603779658), power_ranges=[PowerRange(start_of_range=3372.255894277375, end_of_range=11155.631994233203, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=31076.328103540887, end_of_range=38380.84063645549))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e"), from_=uuid.UUID("cac664cf-bec3-49df-aeb4-d5dca13f3f0b"), to=uuid.UUID("61605548-7d68-4ceb-bbe2-b4d79d385b11"), start_timers=[uuid.UUID("d2436620-dce4-43fe-a59d-a7bc3045a7cd")], blocking_timers=[uuid.UUID("2f690536-73d5-4441-90b4-a28fa60e962f")], transition_costs=2339.5028721662775, transition_duration=Duration.from_timedelta(timedelta(milliseconds=35219)), abnormal_condition_only=True)], timers=[Timer(id=uuid.UUID("0c8a46de-b2bf-4adc-9052-0d2aa5dade66"), diagnostic_label="some-test-string8913", duration=Duration.from_timedelta(timedelta(milliseconds=20103)))])], storage=FRBCStorageDescription(diagnostic_label="some-test-string816", fill_level_label="some-test-string5484", provides_leakage_behaviour=True, provides_fill_level_target_profile=True, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=16537.091716121224, end_of_range=19907.512143139258))) - - # Act - json_str = frbc_system_description.to_json() - - # Assert - expected_json = { 'actuators': [ { 'diagnostic_label': 'some-test-string2832', - 'id': '9d5243b4-f92c-42cd-9d6b-00e3e5953dd4', - 'operation_modes': [ { 'abnormal_condition_only': False, - 'diagnostic_label': 'some-test-string2393', - 'elements': [ { 'fill_level_range': { 'end_of_range': 50141.518189827795, - 'start_of_range': 22530.41813108864}, - 'fill_rate': { 'end_of_range': 22111.530603779658, - 'start_of_range': 10937.461391570358}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 11155.631994233203, - 'start_of_range': 3372.255894277375}], - 'running_costs': { 'end_of_range': 38380.84063645549, - 'start_of_range': 31076.328103540887}}], - 'id': '4526bc26-cdd8-4cd6-ae35-dfb2a6751375'}], - 'supported_commodities': ['GAS'], - 'timers': [ { 'diagnostic_label': 'some-test-string8913', - 'duration': 20103, - 'id': '0c8a46de-b2bf-4adc-9052-0d2aa5dade66'}], - 'transitions': [ { 'abnormal_condition_only': True, - 'blocking_timers': [ '2f690536-73d5-4441-90b4-a28fa60e962f'], - 'from_': 'cac664cf-bec3-49df-aeb4-d5dca13f3f0b', - 'id': '5b9bd90f-d9ec-4eb0-915e-f6b7d7cfab3e', - 'start_timers': [ 'd2436620-dce4-43fe-a59d-a7bc3045a7cd'], - 'to': '61605548-7d68-4ceb-bbe2-b4d79d385b11', - 'transition_costs': 2339.5028721662775, - 'transition_duration': 35219}]}], - 'message_id': '873112de-a6d8-4123-a523-fc908e9a0310', - 'message_type': 'FRBC.SystemDescription', - 'storage': { 'diagnostic_label': 'some-test-string816', - 'fill_level_label': 'some-test-string5484', - 'fill_level_range': { 'end_of_range': 19907.512143139258, - 'start_of_range': 16537.091716121224}, - 'provides_fill_level_target_profile': True, - 'provides_leakage_behaviour': True, - 'provides_usage_forecast': True}, - 'valid_from': '2021-07-20T04:50:11+12:00'} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_timer_status_test.py b/tests/unit/frbc/frbc_timer_status_test.py deleted file mode 100644 index e99176d..0000000 --- a/tests/unit/frbc/frbc_timer_status_test.py +++ /dev/null @@ -1,47 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCTimerStatusTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.TimerStatus", - "message_id": "19c25c4f-1dd6-4a74-9e1c-54c09025844a", - "timer_id": "329b57fb-c4e8-41f4-922f-f27eb32214d5", - "actuator_id": "c24e5b81-0047-41db-9dba-1840c0a1aaca", - "finished_at": "2021-08-25T09:46:58-09:00" -} - """ - - # Act - frbc_timer_status = FRBCTimerStatus.from_json(json_str) - - # Assert - self.assertEqual(frbc_timer_status.message_type, FRBC.TimerStatus) - self.assertEqual(frbc_timer_status.message_id, uuid.UUID("19c25c4f-1dd6-4a74-9e1c-54c09025844a")) - self.assertEqual(frbc_timer_status.timer_id, uuid.UUID("329b57fb-c4e8-41f4-922f-f27eb32214d5")) - self.assertEqual(frbc_timer_status.actuator_id, uuid.UUID("c24e5b81-0047-41db-9dba-1840c0a1aaca")) - self.assertEqual(frbc_timer_status.finished_at, datetime(year=2021, month=8, day=25, hour=9, minute=46, second=58, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_timer_status = FRBCTimerStatus(message_type=FRBC.TimerStatus, message_id=uuid.UUID("19c25c4f-1dd6-4a74-9e1c-54c09025844a"), timer_id=uuid.UUID("329b57fb-c4e8-41f4-922f-f27eb32214d5"), actuator_id=uuid.UUID("c24e5b81-0047-41db-9dba-1840c0a1aaca"), finished_at=datetime(year=2021, month=8, day=25, hour=9, minute=46, second=58, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) - - # Act - json_str = frbc_timer_status.to_json() - - # Assert - expected_json = { 'actuator_id': 'c24e5b81-0047-41db-9dba-1840c0a1aaca', - 'finished_at': '2021-08-25T09:46:58-09:00', - 'message_id': '19c25c4f-1dd6-4a74-9e1c-54c09025844a', - 'message_type': 'FRBC.TimerStatus', - 'timer_id': '329b57fb-c4e8-41f4-922f-f27eb32214d5'} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_element_test.py b/tests/unit/frbc/frbc_usage_forecast_element_test.py deleted file mode 100644 index 0eb44b1..0000000 --- a/tests/unit/frbc/frbc_usage_forecast_element_test.py +++ /dev/null @@ -1,56 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCUsageForecastElementTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "duration": 3339, - "usage_rate_upper_limit": 5657.302338158246, - "usage_rate_upper_95PPR": 3774.615782357365, - "usage_rate_upper_68PPR": 8333.351165894339, - "usage_rate_expected": 8333.127007404517, - "usage_rate_lower_68PPR": 6418.649606433992, - "usage_rate_lower_95PPR": 3342.9603968663487, - "usage_rate_lower_limit": 8970.532671485054 -} - """ - - # Act - frbc_usage_forecast_element = FRBCUsageForecastElement.from_json(json_str) - - # Assert - self.assertEqual(frbc_usage_forecast_element.duration, Duration.from_timedelta(timedelta(milliseconds=3339))) - self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_limit, 5657.302338158246) - self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_95PPR, 3774.615782357365) - self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_68PPR, 8333.351165894339) - self.assertEqual(frbc_usage_forecast_element.usage_rate_expected, 8333.127007404517) - self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_68PPR, 6418.649606433992) - self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_95PPR, 3342.9603968663487) - self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_limit, 8970.532671485054) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_usage_forecast_element = FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=3339)), usage_rate_upper_limit=5657.302338158246, usage_rate_upper_95PPR=3774.615782357365, usage_rate_upper_68PPR=8333.351165894339, usage_rate_expected=8333.127007404517, usage_rate_lower_68PPR=6418.649606433992, usage_rate_lower_95PPR=3342.9603968663487, usage_rate_lower_limit=8970.532671485054) - - # Act - json_str = frbc_usage_forecast_element.to_json() - - # Assert - expected_json = { 'duration': 3339, - 'usage_rate_expected': 8333.127007404517, - 'usage_rate_lower_68PPR': 6418.649606433992, - 'usage_rate_lower_95PPR': 3342.9603968663487, - 'usage_rate_lower_limit': 8970.532671485054, - 'usage_rate_upper_68PPR': 8333.351165894339, - 'usage_rate_upper_95PPR': 3774.615782357365, - 'usage_rate_upper_limit': 5657.302338158246} - self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_test.py b/tests/unit/frbc/frbc_usage_forecast_test.py deleted file mode 100644 index 98e06fd..0000000 --- a/tests/unit/frbc/frbc_usage_forecast_test.py +++ /dev/null @@ -1,62 +0,0 @@ - -from datetime import timedelta, datetime, timezone as offset -import json -from unittest import TestCase -import uuid - -from s2python.common import * -from s2python.frbc import * - - -class FRBCUsageForecastTest(TestCase): - def test__from_json__happy_path_full(self): - # Arrange - json_str = """ -{ - "message_type": "FRBC.UsageForecast", - "message_id": "88f742b5-ef8e-4af9-8ae4-633922859a8e", - "start_time": "2022-11-21T01:57:56-09:00", - "elements": [ - { - "duration": 8336, - "usage_rate_upper_limit": 2600.090517037444, - "usage_rate_upper_95PPR": 5466.368905051084, - "usage_rate_upper_68PPR": 7874.9948212758245, - "usage_rate_expected": 1233.4751600765392, - "usage_rate_lower_68PPR": 1785.90944809586, - "usage_rate_lower_95PPR": 1047.0960233716157, - "usage_rate_lower_limit": 4680.219153555034 - } - ] -} - """ - - # Act - frbc_usage_forecast = FRBCUsageForecast.from_json(json_str) - - # Assert - self.assertEqual(frbc_usage_forecast.message_type, FRBC.UsageForecast) - self.assertEqual(frbc_usage_forecast.message_id, uuid.UUID("88f742b5-ef8e-4af9-8ae4-633922859a8e")) - self.assertEqual(frbc_usage_forecast.start_time, datetime(year=2022, month=11, day=21, hour=1, minute=57, second=56, tzinfo=offset(offset=timedelta(seconds=-32400.0)))) - self.assertEqual(frbc_usage_forecast.elements, [FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=8336)), usage_rate_upper_limit=2600.090517037444, usage_rate_upper_95PPR=5466.368905051084, usage_rate_upper_68PPR=7874.9948212758245, usage_rate_expected=1233.4751600765392, usage_rate_lower_68PPR=1785.90944809586, usage_rate_lower_95PPR=1047.0960233716157, usage_rate_lower_limit=4680.219153555034)]) - - def test__to_json__happy_path_full(self): - # Arrange - frbc_usage_forecast = FRBCUsageForecast(message_type=FRBC.UsageForecast, message_id=uuid.UUID("88f742b5-ef8e-4af9-8ae4-633922859a8e"), start_time=datetime(year=2022, month=11, day=21, hour=1, minute=57, second=56, tzinfo=offset(offset=timedelta(seconds=-32400.0))), elements=[FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=8336)), usage_rate_upper_limit=2600.090517037444, usage_rate_upper_95PPR=5466.368905051084, usage_rate_upper_68PPR=7874.9948212758245, usage_rate_expected=1233.4751600765392, usage_rate_lower_68PPR=1785.90944809586, usage_rate_lower_95PPR=1047.0960233716157, usage_rate_lower_limit=4680.219153555034)]) - - # Act - json_str = frbc_usage_forecast.to_json() - - # Assert - expected_json = { 'elements': [ { 'duration': 8336, - 'usage_rate_expected': 1233.4751600765392, - 'usage_rate_lower_68PPR': 1785.90944809586, - 'usage_rate_lower_95PPR': 1047.0960233716157, - 'usage_rate_lower_limit': 4680.219153555034, - 'usage_rate_upper_68PPR': 7874.9948212758245, - 'usage_rate_upper_95PPR': 5466.368905051084, - 'usage_rate_upper_limit': 2600.090517037444}], - 'message_id': '88f742b5-ef8e-4af9-8ae4-633922859a8e', - 'message_type': 'FRBC.UsageForecast', - 'start_time': '2022-11-21T01:57:56-09:00'} - self.assertEqual(json.loads(json_str), expected_json) From 8edb0299c8ed486548182724e89b056a336096fa Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Mon, 23 Dec 2024 10:35:41 +0200 Subject: [PATCH 09/27] Refactor FRBC for better readability and add TODO comments for abstract methods --- examples/example_frbc_rm.py | 12 +++++++++--- src/s2python/s2_control_type.py | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/examples/example_frbc_rm.py b/examples/example_frbc_rm.py index 8f77b82..53b80df 100644 --- a/examples/example_frbc_rm.py +++ b/examples/example_frbc_rm.py @@ -90,7 +90,9 @@ def activate(self, conn: S2Connection) -> None: ) ], storage=FRBCStorageDescription( - fill_level_range=NumberRange(start_of_range=0.0, end_of_range=100.0), + fill_level_range=NumberRange( + start_of_range=0.0, end_of_range=100.0 + ), fill_level_label="%", diagnostic_label="Imaginary battery", provides_fill_level_target_profile=True, @@ -108,11 +110,15 @@ def activate(self, conn: S2Connection) -> None: elements=[ FRBCFillLevelTargetProfileElement( duration=Duration.from_milliseconds(30_000), - fill_level_range=NumberRange(start_of_range=20.0, end_of_range=30.0), + fill_level_range=NumberRange( + start_of_range=20.0, end_of_range=30.0 + ), ), FRBCFillLevelTargetProfileElement( duration=Duration.from_milliseconds(300_000), - fill_level_range=NumberRange(start_of_range=40.0, end_of_range=50.0), + fill_level_range=NumberRange( + start_of_range=40.0, end_of_range=50.0 + ), ), ], ) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index 547ead7..eaea081 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -36,9 +36,11 @@ def handle_instruction( self, conn: "S2Connection", msg: S2Message, send_okay: typing.Callable[[], None] ) -> None: ... + # TODO @abc.abstractmethod def activate(self, conn: "S2Connection") -> None: ... + # TODO @abc.abstractmethod def deactivate(self, conn: "S2Connection") -> None: ... @@ -55,9 +57,11 @@ def handle_instruction( self, conn: "S2Connection", msg: S2Message, send_okay: typing.Callable[[], None] ) -> None: ... + # TODO @abc.abstractmethod def activate(self, conn: "S2Connection") -> None: ... + # TODO @abc.abstractmethod def deactivate(self, conn: "S2Connection") -> None: ... From ea2635abe418123015496e414ffd2f3e221790a2 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Mon, 23 Dec 2024 10:53:23 +0200 Subject: [PATCH 10/27] Remove unused imports from PPBC modules --- src/s2python/ppbc/ppbc_end_interruption_instruction.py | 1 - src/s2python/ppbc/ppbc_power_profile_status.py | 1 - src/s2python/ppbc/ppbc_power_sequence_container_status.py | 1 - src/s2python/ppbc/ppbc_power_sequence_element.py | 1 - src/s2python/ppbc/ppbc_start_interruption_instruction.py | 1 - 5 files changed, 5 deletions(-) diff --git a/src/s2python/ppbc/ppbc_end_interruption_instruction.py b/src/s2python/ppbc/ppbc_end_interruption_instruction.py index 1dfdee8..a27ccba 100644 --- a/src/s2python/ppbc/ppbc_end_interruption_instruction.py +++ b/src/s2python/ppbc/ppbc_end_interruption_instruction.py @@ -1,4 +1,3 @@ -from typing import List import uuid from s2python.generated.gen_s2 import ( diff --git a/src/s2python/ppbc/ppbc_power_profile_status.py b/src/s2python/ppbc/ppbc_power_profile_status.py index 7dd5dca..d7e3635 100644 --- a/src/s2python/ppbc/ppbc_power_profile_status.py +++ b/src/s2python/ppbc/ppbc_power_profile_status.py @@ -1,5 +1,4 @@ from typing import List -import uuid from s2python.generated.gen_s2 import ( PPBCPowerProfileStatus as GenPPBCPowerProfileStatus, diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index 2d0a50e..55c41f1 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -1,4 +1,3 @@ -from typing import List import uuid from s2python.generated.gen_s2 import ( diff --git a/src/s2python/ppbc/ppbc_power_sequence_element.py b/src/s2python/ppbc/ppbc_power_sequence_element.py index e239453..c3c858a 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_element.py +++ b/src/s2python/ppbc/ppbc_power_sequence_element.py @@ -1,5 +1,4 @@ from typing import List -import uuid from s2python.generated.gen_s2 import ( PPBCPowerSequenceElement as GenPPBCPowerSequenceElement, diff --git a/src/s2python/ppbc/ppbc_start_interruption_instruction.py b/src/s2python/ppbc/ppbc_start_interruption_instruction.py index 6629daa..0390b49 100644 --- a/src/s2python/ppbc/ppbc_start_interruption_instruction.py +++ b/src/s2python/ppbc/ppbc_start_interruption_instruction.py @@ -1,4 +1,3 @@ -from typing import List import uuid from s2python.generated.gen_s2 import ( From ab202de8c444dfbadc40fd0ff239ae8338337899 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 14:36:59 +0200 Subject: [PATCH 11/27] Added the frbcs tests back --- .gitignore | 1 - development_utilities/gen_unit_test_template.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index d197a2d..8a082ed 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,3 @@ venv .tox/ dist/ build/ -tests/unit/frbc diff --git a/development_utilities/gen_unit_test_template.py b/development_utilities/gen_unit_test_template.py index 4e45260..9d91d49 100644 --- a/development_utilities/gen_unit_test_template.py +++ b/development_utilities/gen_unit_test_template.py @@ -13,9 +13,7 @@ TypeVar, Callable, Sequence, - Literal, ) -from typing_extensions import _LiteralGenericAlias import uuid import pydantic From a26405ffe2e0e1e8670c67d24bd76b85aba7f15e Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 14:37:42 +0200 Subject: [PATCH 12/27] Added the frbcs tests back --- .../frbc/frbc_actuator_description_test.py | 122 ++++++++++++++ tests/unit/frbc/frbc_actuator_status_test.py | 53 +++++++ ..._fill_level_target_profile_element_test.py | 42 +++++ .../frbc_fill_level_target_profile_test.py | 54 +++++++ tests/unit/frbc/frbc_instruction_test.py | 56 +++++++ .../frbc_leakage_behaviour_element_test.py | 42 +++++ .../unit/frbc/frbc_leakage_behaviour_test.py | 54 +++++++ .../frbc/frbc_operation_mode_element_test.py | 64 ++++++++ tests/unit/frbc/frbc_operation_mode_test.py | 74 +++++++++ .../frbc/frbc_storage_description_test.py | 54 +++++++ tests/unit/frbc/frbc_storage_status_test.py | 41 +++++ .../unit/frbc/frbc_system_description_test.py | 149 ++++++++++++++++++ tests/unit/frbc/frbc_timer_status_test.py | 47 ++++++ .../frbc/frbc_usage_forecast_element_test.py | 56 +++++++ tests/unit/frbc/frbc_usage_forecast_test.py | 62 ++++++++ 15 files changed, 970 insertions(+) create mode 100644 tests/unit/frbc/frbc_actuator_description_test.py create mode 100644 tests/unit/frbc/frbc_actuator_status_test.py create mode 100644 tests/unit/frbc/frbc_fill_level_target_profile_element_test.py create mode 100644 tests/unit/frbc/frbc_fill_level_target_profile_test.py create mode 100644 tests/unit/frbc/frbc_instruction_test.py create mode 100644 tests/unit/frbc/frbc_leakage_behaviour_element_test.py create mode 100644 tests/unit/frbc/frbc_leakage_behaviour_test.py create mode 100644 tests/unit/frbc/frbc_operation_mode_element_test.py create mode 100644 tests/unit/frbc/frbc_operation_mode_test.py create mode 100644 tests/unit/frbc/frbc_storage_description_test.py create mode 100644 tests/unit/frbc/frbc_storage_status_test.py create mode 100644 tests/unit/frbc/frbc_system_description_test.py create mode 100644 tests/unit/frbc/frbc_timer_status_test.py create mode 100644 tests/unit/frbc/frbc_usage_forecast_element_test.py create mode 100644 tests/unit/frbc/frbc_usage_forecast_test.py diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py new file mode 100644 index 0000000..6e40960 --- /dev/null +++ b/tests/unit/frbc/frbc_actuator_description_test.py @@ -0,0 +1,122 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCActuatorDescriptionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "id": "d94c8444-fecd-4e2b-a753-3ba95c80a8b9", + "diagnostic_label": "some-test-string7020", + "supported_commodities": [ + "GAS" + ], + "operation_modes": [ + { + "id": "0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6", + "diagnostic_label": "some-test-string5647", + "elements": [ + { + "fill_level_range": { + "start_of_range": 29629.998638216148, + "end_of_range": 47173.64237787903 + }, + "fill_rate": { + "start_of_range": 6928.475192465882, + "end_of_range": 27602.436966784364 + }, + "power_ranges": [ + { + "start_of_range": 4601.097096109952, + "end_of_range": 40109.78028113443, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 32340.91070921078, + "end_of_range": 68671.08362229214 + } + } + ], + "abnormal_condition_only": false + } + ], + "transitions": [ + { + "id": "ba747269-ce4b-405c-bc8a-38cba20342a7", + "from_": "c8b76640-8eb1-4656-9477-2bc2afdca867", + "to": "e53e871e-b09e-4a81-9a6d-4b317b30ee04", + "start_timers": [ + "fce4d143-a7d4-4c53-a66c-da2faec9cc61" + ], + "blocking_timers": [ + "c33dc9cd-b82f-4649-b610-a70f3787facd" + ], + "transition_costs": 4575.2975690198045, + "transition_duration": 10950, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "033d2a04-a874-4537-a475-ecc5fc538282", + "diagnostic_label": "some-test-string380", + "duration": 28910 + } + ] +} + """ + + # Act + frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) + + # Assert + self.assertEqual(frbc_actuator_description.id, uuid.UUID("d94c8444-fecd-4e2b-a753-3ba95c80a8b9")) + self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string7020") + self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) + self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6"), diagnostic_label="some-test-string5647", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29629.998638216148, end_of_range=47173.64237787903), fill_rate=NumberRange(start_of_range=6928.475192465882, end_of_range=27602.436966784364), power_ranges=[PowerRange(start_of_range=4601.097096109952, end_of_range=40109.78028113443, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=32340.91070921078, end_of_range=68671.08362229214))], abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("ba747269-ce4b-405c-bc8a-38cba20342a7"), from_=uuid.UUID("c8b76640-8eb1-4656-9477-2bc2afdca867"), to=uuid.UUID("e53e871e-b09e-4a81-9a6d-4b317b30ee04"), start_timers=[uuid.UUID("fce4d143-a7d4-4c53-a66c-da2faec9cc61")], blocking_timers=[uuid.UUID("c33dc9cd-b82f-4649-b610-a70f3787facd")], transition_costs=4575.2975690198045, transition_duration=Duration.from_timedelta(timedelta(milliseconds=10950)), abnormal_condition_only=False)]) + self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("033d2a04-a874-4537-a475-ecc5fc538282"), diagnostic_label="some-test-string380", duration=Duration.from_timedelta(timedelta(milliseconds=28910)))]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("d94c8444-fecd-4e2b-a753-3ba95c80a8b9"), diagnostic_label="some-test-string7020", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6"), diagnostic_label="some-test-string5647", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29629.998638216148, end_of_range=47173.64237787903), fill_rate=NumberRange(start_of_range=6928.475192465882, end_of_range=27602.436966784364), power_ranges=[PowerRange(start_of_range=4601.097096109952, end_of_range=40109.78028113443, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=32340.91070921078, end_of_range=68671.08362229214))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("ba747269-ce4b-405c-bc8a-38cba20342a7"), from_=uuid.UUID("c8b76640-8eb1-4656-9477-2bc2afdca867"), to=uuid.UUID("e53e871e-b09e-4a81-9a6d-4b317b30ee04"), start_timers=[uuid.UUID("fce4d143-a7d4-4c53-a66c-da2faec9cc61")], blocking_timers=[uuid.UUID("c33dc9cd-b82f-4649-b610-a70f3787facd")], transition_costs=4575.2975690198045, transition_duration=Duration.from_timedelta(timedelta(milliseconds=10950)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("033d2a04-a874-4537-a475-ecc5fc538282"), diagnostic_label="some-test-string380", duration=Duration.from_timedelta(timedelta(milliseconds=28910)))]) + + # Act + json_str = frbc_actuator_description.to_json() + + # Assert + expected_json = { 'diagnostic_label': 'some-test-string7020', + 'id': 'd94c8444-fecd-4e2b-a753-3ba95c80a8b9', + 'operation_modes': [ { 'abnormal_condition_only': False, + 'diagnostic_label': 'some-test-string5647', + 'elements': [ { 'fill_level_range': { 'end_of_range': 47173.64237787903, + 'start_of_range': 29629.998638216148}, + 'fill_rate': { 'end_of_range': 27602.436966784364, + 'start_of_range': 6928.475192465882}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 40109.78028113443, + 'start_of_range': 4601.097096109952}], + 'running_costs': { 'end_of_range': 68671.08362229214, + 'start_of_range': 32340.91070921078}}], + 'id': '0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string380', + 'duration': 28910, + 'id': '033d2a04-a874-4537-a475-ecc5fc538282'}], + 'transitions': [ { 'abnormal_condition_only': False, + 'blocking_timers': [ 'c33dc9cd-b82f-4649-b610-a70f3787facd'], + 'from_': 'c8b76640-8eb1-4656-9477-2bc2afdca867', + 'id': 'ba747269-ce4b-405c-bc8a-38cba20342a7', + 'start_timers': [ 'fce4d143-a7d4-4c53-a66c-da2faec9cc61'], + 'to': 'e53e871e-b09e-4a81-9a6d-4b317b30ee04', + 'transition_costs': 4575.2975690198045, + 'transition_duration': 10950}]} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py new file mode 100644 index 0000000..ac47d8e --- /dev/null +++ b/tests/unit/frbc/frbc_actuator_status_test.py @@ -0,0 +1,53 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCActuatorStatusTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.ActuatorStatus", + "message_id": "4dbb1dee-cda6-46d8-92f7-98c1f0cd4157", + "actuator_id": "0b6552ac-7af4-4627-87da-30fef76d22b7", + "active_operation_mode_id": "b1ff587d-8257-4644-8adf-7e0a7787863e", + "operation_mode_factor": 674.4963166817159, + "previous_operation_mode_id": "7b2553e7-ae6c-4ce4-8059-3712e33b0648", + "transition_timestamp": "2022-11-24T09:56:49+08:00" +} + """ + + # Act + frbc_actuator_status = FRBCActuatorStatus.from_json(json_str) + + # Assert + self.assertEqual(frbc_actuator_status.message_type, FRBC.ActuatorStatus) + self.assertEqual(frbc_actuator_status.message_id, uuid.UUID("4dbb1dee-cda6-46d8-92f7-98c1f0cd4157")) + self.assertEqual(frbc_actuator_status.actuator_id, uuid.UUID("0b6552ac-7af4-4627-87da-30fef76d22b7")) + self.assertEqual(frbc_actuator_status.active_operation_mode_id, uuid.UUID("b1ff587d-8257-4644-8adf-7e0a7787863e")) + self.assertEqual(frbc_actuator_status.operation_mode_factor, 674.4963166817159) + self.assertEqual(frbc_actuator_status.previous_operation_mode_id, uuid.UUID("7b2553e7-ae6c-4ce4-8059-3712e33b0648")) + self.assertEqual(frbc_actuator_status.transition_timestamp, datetime(year=2022, month=11, day=24, hour=9, minute=56, second=49, tzinfo=offset(offset=timedelta(seconds=28800.0)))) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_actuator_status = FRBCActuatorStatus(message_type=FRBC.ActuatorStatus, message_id=uuid.UUID("4dbb1dee-cda6-46d8-92f7-98c1f0cd4157"), actuator_id=uuid.UUID("0b6552ac-7af4-4627-87da-30fef76d22b7"), active_operation_mode_id=uuid.UUID("b1ff587d-8257-4644-8adf-7e0a7787863e"), operation_mode_factor=674.4963166817159, previous_operation_mode_id=uuid.UUID("7b2553e7-ae6c-4ce4-8059-3712e33b0648"), transition_timestamp=datetime(year=2022, month=11, day=24, hour=9, minute=56, second=49, tzinfo=offset(offset=timedelta(seconds=28800.0)))) + + # Act + json_str = frbc_actuator_status.to_json() + + # Assert + expected_json = { 'active_operation_mode_id': 'b1ff587d-8257-4644-8adf-7e0a7787863e', + 'actuator_id': '0b6552ac-7af4-4627-87da-30fef76d22b7', + 'message_id': '4dbb1dee-cda6-46d8-92f7-98c1f0cd4157', + 'message_type': 'FRBC.ActuatorStatus', + 'operation_mode_factor': 674.4963166817159, + 'previous_operation_mode_id': '7b2553e7-ae6c-4ce4-8059-3712e33b0648', + 'transition_timestamp': '2022-11-24T09:56:49+08:00'} + self.assertEqual(json.loads(json_str), expected_json) 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 new file mode 100644 index 0000000..678e9de --- /dev/null +++ b/tests/unit/frbc/frbc_fill_level_target_profile_element_test.py @@ -0,0 +1,42 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCFillLevelTargetProfileElementTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "duration": 20277, + "fill_level_range": { + "start_of_range": 32523.727089572538, + "end_of_range": 51457.69540766515 + } +} + """ + + # Act + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json(json_str) + + # Assert + self.assertEqual(frbc_fill_level_target_profile_element.duration, Duration.from_timedelta(timedelta(milliseconds=20277))) + self.assertEqual(frbc_fill_level_target_profile_element.fill_level_range, NumberRange(start_of_range=32523.727089572538, end_of_range=51457.69540766515)) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=20277)), fill_level_range=NumberRange(start_of_range=32523.727089572538, end_of_range=51457.69540766515)) + + # Act + json_str = frbc_fill_level_target_profile_element.to_json() + + # Assert + expected_json = { 'duration': 20277, + 'fill_level_range': { 'end_of_range': 51457.69540766515, + 'start_of_range': 32523.727089572538}} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_fill_level_target_profile_test.py b/tests/unit/frbc/frbc_fill_level_target_profile_test.py new file mode 100644 index 0000000..8f47374 --- /dev/null +++ b/tests/unit/frbc/frbc_fill_level_target_profile_test.py @@ -0,0 +1,54 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCFillLevelTargetProfileTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.FillLevelTargetProfile", + "message_id": "8df9b5ac-de60-402c-9400-a4dd731fbd99", + "start_time": "2023-02-12T11:42:28+11:00", + "elements": [ + { + "duration": 26527, + "fill_level_range": { + "start_of_range": 4668.6876987963915, + "end_of_range": 25463.75618862868 + } + } + ] +} + """ + + # Act + frbc_fill_level_target_profile = FRBCFillLevelTargetProfile.from_json(json_str) + + # Assert + self.assertEqual(frbc_fill_level_target_profile.message_type, FRBC.FillLevelTargetProfile) + self.assertEqual(frbc_fill_level_target_profile.message_id, uuid.UUID("8df9b5ac-de60-402c-9400-a4dd731fbd99")) + self.assertEqual(frbc_fill_level_target_profile.start_time, datetime(year=2023, month=2, day=12, hour=11, minute=42, second=28, tzinfo=offset(offset=timedelta(seconds=39600.0)))) + self.assertEqual(frbc_fill_level_target_profile.elements, [FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=26527)), fill_level_range=NumberRange(start_of_range=4668.6876987963915, end_of_range=25463.75618862868))]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_fill_level_target_profile = FRBCFillLevelTargetProfile(message_type=FRBC.FillLevelTargetProfile, message_id=uuid.UUID("8df9b5ac-de60-402c-9400-a4dd731fbd99"), start_time=datetime(year=2023, month=2, day=12, hour=11, minute=42, second=28, tzinfo=offset(offset=timedelta(seconds=39600.0))), elements=[FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=26527)), fill_level_range=NumberRange(start_of_range=4668.6876987963915, end_of_range=25463.75618862868))]) + + # Act + json_str = frbc_fill_level_target_profile.to_json() + + # Assert + expected_json = { 'elements': [ { 'duration': 26527, + 'fill_level_range': { 'end_of_range': 25463.75618862868, + 'start_of_range': 4668.6876987963915}}], + 'message_id': '8df9b5ac-de60-402c-9400-a4dd731fbd99', + 'message_type': 'FRBC.FillLevelTargetProfile', + 'start_time': '2023-02-12T11:42:28+11:00'} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_instruction_test.py b/tests/unit/frbc/frbc_instruction_test.py new file mode 100644 index 0000000..893ce46 --- /dev/null +++ b/tests/unit/frbc/frbc_instruction_test.py @@ -0,0 +1,56 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCInstructionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.Instruction", + "message_id": "e961f92e-32a7-4117-81f3-8868968d2d14", + "id": "933e2c9c-526a-4934-96de-6eae0ae1f941", + "actuator_id": "02979ffc-3083-437d-8540-925073dbb465", + "operation_mode": "90c03b06-728a-4f79-a54b-e01a6bad6f37", + "operation_mode_factor": 2038.574227328529, + "execution_time": "2020-07-02T06:03:25+07:00", + "abnormal_condition": true +} + """ + + # Act + frbc_instruction = FRBCInstruction.from_json(json_str) + + # Assert + self.assertEqual(frbc_instruction.message_type, FRBC.Instruction) + self.assertEqual(frbc_instruction.message_id, uuid.UUID("e961f92e-32a7-4117-81f3-8868968d2d14")) + self.assertEqual(frbc_instruction.id, uuid.UUID("933e2c9c-526a-4934-96de-6eae0ae1f941")) + self.assertEqual(frbc_instruction.actuator_id, uuid.UUID("02979ffc-3083-437d-8540-925073dbb465")) + self.assertEqual(frbc_instruction.operation_mode, uuid.UUID("90c03b06-728a-4f79-a54b-e01a6bad6f37")) + self.assertEqual(frbc_instruction.operation_mode_factor, 2038.574227328529) + self.assertEqual(frbc_instruction.execution_time, datetime(year=2020, month=7, day=2, hour=6, minute=3, second=25, tzinfo=offset(offset=timedelta(seconds=25200.0)))) + self.assertEqual(frbc_instruction.abnormal_condition, True) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_instruction = FRBCInstruction(message_type=FRBC.Instruction, message_id=uuid.UUID("e961f92e-32a7-4117-81f3-8868968d2d14"), id=uuid.UUID("933e2c9c-526a-4934-96de-6eae0ae1f941"), actuator_id=uuid.UUID("02979ffc-3083-437d-8540-925073dbb465"), operation_mode=uuid.UUID("90c03b06-728a-4f79-a54b-e01a6bad6f37"), operation_mode_factor=2038.574227328529, execution_time=datetime(year=2020, month=7, day=2, hour=6, minute=3, second=25, tzinfo=offset(offset=timedelta(seconds=25200.0))), abnormal_condition=True) + + # Act + json_str = frbc_instruction.to_json() + + # Assert + expected_json = { 'abnormal_condition': True, + 'actuator_id': '02979ffc-3083-437d-8540-925073dbb465', + 'execution_time': '2020-07-02T06:03:25+07:00', + 'id': '933e2c9c-526a-4934-96de-6eae0ae1f941', + 'message_id': 'e961f92e-32a7-4117-81f3-8868968d2d14', + 'message_type': 'FRBC.Instruction', + 'operation_mode': '90c03b06-728a-4f79-a54b-e01a6bad6f37', + 'operation_mode_factor': 2038.574227328529} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py new file mode 100644 index 0000000..8c80f50 --- /dev/null +++ b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py @@ -0,0 +1,42 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCLeakageBehaviourElementTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "fill_level_range": { + "start_of_range": 13590.14671121342, + "end_of_range": 25393.13672324905 + }, + "leakage_rate": 8822.586828251793 +} + """ + + # Act + frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement.from_json(json_str) + + # Assert + self.assertEqual(frbc_leakage_behaviour_element.fill_level_range, NumberRange(start_of_range=13590.14671121342, end_of_range=25393.13672324905)) + self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 8822.586828251793) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=13590.14671121342, end_of_range=25393.13672324905), leakage_rate=8822.586828251793) + + # Act + json_str = frbc_leakage_behaviour_element.to_json() + + # Assert + expected_json = { 'fill_level_range': { 'end_of_range': 25393.13672324905, + 'start_of_range': 13590.14671121342}, + 'leakage_rate': 8822.586828251793} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_test.py b/tests/unit/frbc/frbc_leakage_behaviour_test.py new file mode 100644 index 0000000..3e95596 --- /dev/null +++ b/tests/unit/frbc/frbc_leakage_behaviour_test.py @@ -0,0 +1,54 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCLeakageBehaviourTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.LeakageBehaviour", + "message_id": "b77d33ab-1dce-4fde-859a-d22b40364c2e", + "valid_from": "2021-09-02T00:18:01+00:00", + "elements": [ + { + "fill_level_range": { + "start_of_range": 8607.996640989155, + "end_of_range": 48389.84849678871 + }, + "leakage_rate": 8425.146853345715 + } + ] +} + """ + + # Act + frbc_leakage_behaviour = FRBCLeakageBehaviour.from_json(json_str) + + # Assert + self.assertEqual(frbc_leakage_behaviour.message_type, FRBC.LeakageBehaviour) + self.assertEqual(frbc_leakage_behaviour.message_id, uuid.UUID("b77d33ab-1dce-4fde-859a-d22b40364c2e")) + self.assertEqual(frbc_leakage_behaviour.valid_from, datetime(year=2021, month=9, day=2, hour=0, minute=18, second=1, tzinfo=offset(offset=timedelta(seconds=0.0)))) + self.assertEqual(frbc_leakage_behaviour.elements, [FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=8607.996640989155, end_of_range=48389.84849678871), leakage_rate=8425.146853345715)]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_leakage_behaviour = FRBCLeakageBehaviour(message_type=FRBC.LeakageBehaviour, message_id=uuid.UUID("b77d33ab-1dce-4fde-859a-d22b40364c2e"), valid_from=datetime(year=2021, month=9, day=2, hour=0, minute=18, second=1, tzinfo=offset(offset=timedelta(seconds=0.0))), elements=[FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=8607.996640989155, end_of_range=48389.84849678871), leakage_rate=8425.146853345715)]) + + # Act + json_str = frbc_leakage_behaviour.to_json() + + # Assert + expected_json = { 'elements': [ { 'fill_level_range': { 'end_of_range': 48389.84849678871, + 'start_of_range': 8607.996640989155}, + 'leakage_rate': 8425.146853345715}], + 'message_id': 'b77d33ab-1dce-4fde-859a-d22b40364c2e', + 'message_type': 'FRBC.LeakageBehaviour', + 'valid_from': '2021-09-02T00:18:01+00:00'} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_element_test.py b/tests/unit/frbc/frbc_operation_mode_element_test.py new file mode 100644 index 0000000..21301dc --- /dev/null +++ b/tests/unit/frbc/frbc_operation_mode_element_test.py @@ -0,0 +1,64 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCOperationModeElementTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "fill_level_range": { + "start_of_range": 17902.57617756065, + "end_of_range": 23155.498329551232 + }, + "fill_rate": { + "start_of_range": 24592.14185864383, + "end_of_range": 64041.99840646259 + }, + "power_ranges": [ + { + "start_of_range": 32526.45719448619, + "end_of_range": 71172.61140295293, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 34310.357669763165, + "end_of_range": 49896.43882374468 + } +} + """ + + # Act + frbc_operation_mode_element = FRBCOperationModeElement.from_json(json_str) + + # Assert + self.assertEqual(frbc_operation_mode_element.fill_level_range, NumberRange(start_of_range=17902.57617756065, end_of_range=23155.498329551232)) + self.assertEqual(frbc_operation_mode_element.fill_rate, NumberRange(start_of_range=24592.14185864383, end_of_range=64041.99840646259)) + self.assertEqual(frbc_operation_mode_element.power_ranges, [PowerRange(start_of_range=32526.45719448619, end_of_range=71172.61140295293, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)]) + self.assertEqual(frbc_operation_mode_element.running_costs, NumberRange(start_of_range=34310.357669763165, end_of_range=49896.43882374468)) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_operation_mode_element = FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=17902.57617756065, end_of_range=23155.498329551232), fill_rate=NumberRange(start_of_range=24592.14185864383, end_of_range=64041.99840646259), power_ranges=[PowerRange(start_of_range=32526.45719448619, end_of_range=71172.61140295293, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=34310.357669763165, end_of_range=49896.43882374468)) + + # Act + json_str = frbc_operation_mode_element.to_json() + + # Assert + expected_json = { 'fill_level_range': { 'end_of_range': 23155.498329551232, + 'start_of_range': 17902.57617756065}, + 'fill_rate': { 'end_of_range': 64041.99840646259, + 'start_of_range': 24592.14185864383}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 71172.61140295293, + 'start_of_range': 32526.45719448619}], + 'running_costs': { 'end_of_range': 49896.43882374468, + 'start_of_range': 34310.357669763165}} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_test.py b/tests/unit/frbc/frbc_operation_mode_test.py new file mode 100644 index 0000000..4e19053 --- /dev/null +++ b/tests/unit/frbc/frbc_operation_mode_test.py @@ -0,0 +1,74 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCOperationModeTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "id": "689ee211-83c6-4907-9bde-6ddd47996557", + "diagnostic_label": "some-test-string8118", + "elements": [ + { + "fill_level_range": { + "start_of_range": 29586.80244711599, + "end_of_range": 40811.76669416521 + }, + "fill_rate": { + "start_of_range": 36920.12023083362, + "end_of_range": 67087.76396982145 + }, + "power_ranges": [ + { + "start_of_range": 33844.8053625118, + "end_of_range": 67624.38313311148, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 26024.33483461767, + "end_of_range": 48053.179872036795 + } + } + ], + "abnormal_condition_only": true +} + """ + + # Act + frbc_operation_mode = FRBCOperationMode.from_json(json_str) + + # Assert + self.assertEqual(frbc_operation_mode.id, uuid.UUID("689ee211-83c6-4907-9bde-6ddd47996557")) + self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string8118") + self.assertEqual(frbc_operation_mode.elements, [FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29586.80244711599, end_of_range=40811.76669416521), fill_rate=NumberRange(start_of_range=36920.12023083362, end_of_range=67087.76396982145), power_ranges=[PowerRange(start_of_range=33844.8053625118, end_of_range=67624.38313311148, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=26024.33483461767, end_of_range=48053.179872036795))]) + self.assertEqual(frbc_operation_mode.abnormal_condition_only, True) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_operation_mode = FRBCOperationMode(id=uuid.UUID("689ee211-83c6-4907-9bde-6ddd47996557"), diagnostic_label="some-test-string8118", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29586.80244711599, end_of_range=40811.76669416521), fill_rate=NumberRange(start_of_range=36920.12023083362, end_of_range=67087.76396982145), power_ranges=[PowerRange(start_of_range=33844.8053625118, end_of_range=67624.38313311148, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=26024.33483461767, end_of_range=48053.179872036795))], abnormal_condition_only=True) + + # Act + json_str = frbc_operation_mode.to_json() + + # Assert + expected_json = { 'abnormal_condition_only': True, + 'diagnostic_label': 'some-test-string8118', + 'elements': [ { 'fill_level_range': { 'end_of_range': 40811.76669416521, + 'start_of_range': 29586.80244711599}, + 'fill_rate': { 'end_of_range': 67087.76396982145, + 'start_of_range': 36920.12023083362}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 67624.38313311148, + 'start_of_range': 33844.8053625118}], + 'running_costs': { 'end_of_range': 48053.179872036795, + 'start_of_range': 26024.33483461767}}], + 'id': '689ee211-83c6-4907-9bde-6ddd47996557'} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_description_test.py b/tests/unit/frbc/frbc_storage_description_test.py new file mode 100644 index 0000000..ab7ae37 --- /dev/null +++ b/tests/unit/frbc/frbc_storage_description_test.py @@ -0,0 +1,54 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCStorageDescriptionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "diagnostic_label": "some-test-string7988", + "fill_level_label": "some-test-string7290", + "provides_leakage_behaviour": false, + "provides_fill_level_target_profile": false, + "provides_usage_forecast": false, + "fill_level_range": { + "start_of_range": 10366.413731478093, + "end_of_range": 12149.613524580698 + } +} + """ + + # Act + frbc_storage_description = FRBCStorageDescription.from_json(json_str) + + # Assert + self.assertEqual(frbc_storage_description.diagnostic_label, "some-test-string7988") + self.assertEqual(frbc_storage_description.fill_level_label, "some-test-string7290") + self.assertEqual(frbc_storage_description.provides_leakage_behaviour, False) + self.assertEqual(frbc_storage_description.provides_fill_level_target_profile, False) + self.assertEqual(frbc_storage_description.provides_usage_forecast, False) + self.assertEqual(frbc_storage_description.fill_level_range, NumberRange(start_of_range=10366.413731478093, end_of_range=12149.613524580698)) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_storage_description = FRBCStorageDescription(diagnostic_label="some-test-string7988", fill_level_label="some-test-string7290", provides_leakage_behaviour=False, provides_fill_level_target_profile=False, provides_usage_forecast=False, fill_level_range=NumberRange(start_of_range=10366.413731478093, end_of_range=12149.613524580698)) + + # Act + json_str = frbc_storage_description.to_json() + + # Assert + expected_json = { 'diagnostic_label': 'some-test-string7988', + 'fill_level_label': 'some-test-string7290', + 'fill_level_range': { 'end_of_range': 12149.613524580698, + 'start_of_range': 10366.413731478093}, + 'provides_fill_level_target_profile': False, + 'provides_leakage_behaviour': False, + 'provides_usage_forecast': False} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_status_test.py b/tests/unit/frbc/frbc_storage_status_test.py new file mode 100644 index 0000000..ceb2a11 --- /dev/null +++ b/tests/unit/frbc/frbc_storage_status_test.py @@ -0,0 +1,41 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCStorageStatusTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.StorageStatus", + "message_id": "2b7b06cb-b4a6-4997-9d0c-8ea075f9941a", + "present_fill_level": 226.70889257233483 +} + """ + + # Act + frbc_storage_status = FRBCStorageStatus.from_json(json_str) + + # Assert + self.assertEqual(frbc_storage_status.message_type, FRBC.StorageStatus) + self.assertEqual(frbc_storage_status.message_id, uuid.UUID("2b7b06cb-b4a6-4997-9d0c-8ea075f9941a")) + self.assertEqual(frbc_storage_status.present_fill_level, 226.70889257233483) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_storage_status = FRBCStorageStatus(message_type=FRBC.StorageStatus, message_id=uuid.UUID("2b7b06cb-b4a6-4997-9d0c-8ea075f9941a"), present_fill_level=226.70889257233483) + + # Act + json_str = frbc_storage_status.to_json() + + # Assert + expected_json = { 'message_id': '2b7b06cb-b4a6-4997-9d0c-8ea075f9941a', + 'message_type': 'FRBC.StorageStatus', + 'present_fill_level': 226.70889257233483} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py new file mode 100644 index 0000000..6f1f427 --- /dev/null +++ b/tests/unit/frbc/frbc_system_description_test.py @@ -0,0 +1,149 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCSystemDescriptionTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.SystemDescription", + "message_id": "a2dfaa9c-e52c-435d-94e4-15007f208ec6", + "valid_from": "2021-07-13T15:48:07+00:00", + "actuators": [ + { + "id": "b6872218-3fb7-436e-bdcb-e393f8ef95f7", + "diagnostic_label": "some-test-string6489", + "supported_commodities": [ + "GAS" + ], + "operation_modes": [ + { + "id": "052029e8-85e2-4b96-bd03-7f3345445e76", + "diagnostic_label": "some-test-string7949", + "elements": [ + { + "fill_level_range": { + "start_of_range": 17740.22962461282, + "end_of_range": 52507.59091283206 + }, + "fill_rate": { + "start_of_range": 35532.15914996989, + "end_of_range": 48265.232076205815 + }, + "power_ranges": [ + { + "start_of_range": 38748.50842724231, + "end_of_range": 47741.69817105044, + "commodity_quantity": "ELECTRIC.POWER.L1" + } + ], + "running_costs": { + "start_of_range": 35692.82350352573, + "end_of_range": 47884.66649499221 + } + } + ], + "abnormal_condition_only": true + } + ], + "transitions": [ + { + "id": "b1ace854-611e-4064-8c9b-434b453879df", + "from_": "66c5b0af-36e0-425d-b5c3-9cf27944f555", + "to": "8aed333c-663c-4502-8dc2-45c21acbe366", + "start_timers": [ + "56258c00-182b-4299-b9f0-c44d533dfdcb" + ], + "blocking_timers": [ + "8dac5674-4428-46cd-bfad-c89bb4d23029" + ], + "transition_costs": 1403.4899643577787, + "transition_duration": 32794, + "abnormal_condition_only": false + } + ], + "timers": [ + { + "id": "e8a806df-eb45-4ed6-959d-db717d2462c8", + "diagnostic_label": "some-test-string3261", + "duration": 5659 + } + ] + } + ], + "storage": { + "diagnostic_label": "some-test-string9735", + "fill_level_label": "some-test-string1238", + "provides_leakage_behaviour": true, + "provides_fill_level_target_profile": false, + "provides_usage_forecast": true, + "fill_level_range": { + "start_of_range": 8670.277473912942, + "end_of_range": 27152.584459033194 + } + } +} + """ + + # Act + frbc_system_description = FRBCSystemDescription.from_json(json_str) + + # Assert + self.assertEqual(frbc_system_description.message_type, FRBC.SystemDescription) + self.assertEqual(frbc_system_description.message_id, uuid.UUID("a2dfaa9c-e52c-435d-94e4-15007f208ec6")) + self.assertEqual(frbc_system_description.valid_from, datetime(year=2021, month=7, day=13, hour=15, minute=48, second=7, tzinfo=offset(offset=timedelta(seconds=0.0)))) + self.assertEqual(frbc_system_description.actuators, [FRBCActuatorDescription(id=uuid.UUID("b6872218-3fb7-436e-bdcb-e393f8ef95f7"), diagnostic_label="some-test-string6489", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("052029e8-85e2-4b96-bd03-7f3345445e76"), diagnostic_label="some-test-string7949", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=17740.22962461282, end_of_range=52507.59091283206), fill_rate=NumberRange(start_of_range=35532.15914996989, end_of_range=48265.232076205815), power_ranges=[PowerRange(start_of_range=38748.50842724231, end_of_range=47741.69817105044, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=35692.82350352573, end_of_range=47884.66649499221))], abnormal_condition_only=True)], transitions=[Transition(id=uuid.UUID("b1ace854-611e-4064-8c9b-434b453879df"), from_=uuid.UUID("66c5b0af-36e0-425d-b5c3-9cf27944f555"), to=uuid.UUID("8aed333c-663c-4502-8dc2-45c21acbe366"), start_timers=[uuid.UUID("56258c00-182b-4299-b9f0-c44d533dfdcb")], blocking_timers=[uuid.UUID("8dac5674-4428-46cd-bfad-c89bb4d23029")], transition_costs=1403.4899643577787, transition_duration=Duration.from_timedelta(timedelta(milliseconds=32794)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("e8a806df-eb45-4ed6-959d-db717d2462c8"), diagnostic_label="some-test-string3261", duration=Duration.from_timedelta(timedelta(milliseconds=5659)))])]) + self.assertEqual(frbc_system_description.storage, FRBCStorageDescription(diagnostic_label="some-test-string9735", fill_level_label="some-test-string1238", provides_leakage_behaviour=True, provides_fill_level_target_profile=False, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=8670.277473912942, end_of_range=27152.584459033194))) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_system_description = FRBCSystemDescription(message_type=FRBC.SystemDescription, message_id=uuid.UUID("a2dfaa9c-e52c-435d-94e4-15007f208ec6"), valid_from=datetime(year=2021, month=7, day=13, hour=15, minute=48, second=7, tzinfo=offset(offset=timedelta(seconds=0.0))), actuators=[FRBCActuatorDescription(id=uuid.UUID("b6872218-3fb7-436e-bdcb-e393f8ef95f7"), diagnostic_label="some-test-string6489", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("052029e8-85e2-4b96-bd03-7f3345445e76"), diagnostic_label="some-test-string7949", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=17740.22962461282, end_of_range=52507.59091283206), fill_rate=NumberRange(start_of_range=35532.15914996989, end_of_range=48265.232076205815), power_ranges=[PowerRange(start_of_range=38748.50842724231, end_of_range=47741.69817105044, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=35692.82350352573, end_of_range=47884.66649499221))], abnormal_condition_only=True)], transitions=[Transition(id=uuid.UUID("b1ace854-611e-4064-8c9b-434b453879df"), from_=uuid.UUID("66c5b0af-36e0-425d-b5c3-9cf27944f555"), to=uuid.UUID("8aed333c-663c-4502-8dc2-45c21acbe366"), start_timers=[uuid.UUID("56258c00-182b-4299-b9f0-c44d533dfdcb")], blocking_timers=[uuid.UUID("8dac5674-4428-46cd-bfad-c89bb4d23029")], transition_costs=1403.4899643577787, transition_duration=Duration.from_timedelta(timedelta(milliseconds=32794)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("e8a806df-eb45-4ed6-959d-db717d2462c8"), diagnostic_label="some-test-string3261", duration=Duration.from_timedelta(timedelta(milliseconds=5659)))])], storage=FRBCStorageDescription(diagnostic_label="some-test-string9735", fill_level_label="some-test-string1238", provides_leakage_behaviour=True, provides_fill_level_target_profile=False, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=8670.277473912942, end_of_range=27152.584459033194))) + + # Act + json_str = frbc_system_description.to_json() + + # Assert + expected_json = { 'actuators': [ { 'diagnostic_label': 'some-test-string6489', + 'id': 'b6872218-3fb7-436e-bdcb-e393f8ef95f7', + 'operation_modes': [ { 'abnormal_condition_only': True, + 'diagnostic_label': 'some-test-string7949', + 'elements': [ { 'fill_level_range': { 'end_of_range': 52507.59091283206, + 'start_of_range': 17740.22962461282}, + 'fill_rate': { 'end_of_range': 48265.232076205815, + 'start_of_range': 35532.15914996989}, + 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', + 'end_of_range': 47741.69817105044, + 'start_of_range': 38748.50842724231}], + 'running_costs': { 'end_of_range': 47884.66649499221, + 'start_of_range': 35692.82350352573}}], + 'id': '052029e8-85e2-4b96-bd03-7f3345445e76'}], + 'supported_commodities': ['GAS'], + 'timers': [ { 'diagnostic_label': 'some-test-string3261', + 'duration': 5659, + 'id': 'e8a806df-eb45-4ed6-959d-db717d2462c8'}], + 'transitions': [ { 'abnormal_condition_only': False, + 'blocking_timers': [ '8dac5674-4428-46cd-bfad-c89bb4d23029'], + 'from_': '66c5b0af-36e0-425d-b5c3-9cf27944f555', + 'id': 'b1ace854-611e-4064-8c9b-434b453879df', + 'start_timers': [ '56258c00-182b-4299-b9f0-c44d533dfdcb'], + 'to': '8aed333c-663c-4502-8dc2-45c21acbe366', + 'transition_costs': 1403.4899643577787, + 'transition_duration': 32794}]}], + 'message_id': 'a2dfaa9c-e52c-435d-94e4-15007f208ec6', + 'message_type': 'FRBC.SystemDescription', + 'storage': { 'diagnostic_label': 'some-test-string9735', + 'fill_level_label': 'some-test-string1238', + 'fill_level_range': { 'end_of_range': 27152.584459033194, + 'start_of_range': 8670.277473912942}, + 'provides_fill_level_target_profile': False, + 'provides_leakage_behaviour': True, + 'provides_usage_forecast': True}, + 'valid_from': '2021-07-13T15:48:07+00:00'} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_timer_status_test.py b/tests/unit/frbc/frbc_timer_status_test.py new file mode 100644 index 0000000..44643ad --- /dev/null +++ b/tests/unit/frbc/frbc_timer_status_test.py @@ -0,0 +1,47 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCTimerStatusTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.TimerStatus", + "message_id": "b9060ff4-322c-4ec4-a1d0-1db024dd2fe7", + "timer_id": "564ceeaa-5f4f-46f9-8986-8c7920f76c85", + "actuator_id": "5d0523a8-2d85-417e-a83c-ddc9a6694cc2", + "finished_at": "2021-10-18T02:07:15-02:00" +} + """ + + # Act + frbc_timer_status = FRBCTimerStatus.from_json(json_str) + + # Assert + self.assertEqual(frbc_timer_status.message_type, FRBC.TimerStatus) + self.assertEqual(frbc_timer_status.message_id, uuid.UUID("b9060ff4-322c-4ec4-a1d0-1db024dd2fe7")) + self.assertEqual(frbc_timer_status.timer_id, uuid.UUID("564ceeaa-5f4f-46f9-8986-8c7920f76c85")) + self.assertEqual(frbc_timer_status.actuator_id, uuid.UUID("5d0523a8-2d85-417e-a83c-ddc9a6694cc2")) + self.assertEqual(frbc_timer_status.finished_at, datetime(year=2021, month=10, day=18, hour=2, minute=7, second=15, tzinfo=offset(offset=timedelta(seconds=-7200.0)))) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_timer_status = FRBCTimerStatus(message_type=FRBC.TimerStatus, message_id=uuid.UUID("b9060ff4-322c-4ec4-a1d0-1db024dd2fe7"), timer_id=uuid.UUID("564ceeaa-5f4f-46f9-8986-8c7920f76c85"), actuator_id=uuid.UUID("5d0523a8-2d85-417e-a83c-ddc9a6694cc2"), finished_at=datetime(year=2021, month=10, day=18, hour=2, minute=7, second=15, tzinfo=offset(offset=timedelta(seconds=-7200.0)))) + + # Act + json_str = frbc_timer_status.to_json() + + # Assert + expected_json = { 'actuator_id': '5d0523a8-2d85-417e-a83c-ddc9a6694cc2', + 'finished_at': '2021-10-18T02:07:15-02:00', + 'message_id': 'b9060ff4-322c-4ec4-a1d0-1db024dd2fe7', + 'message_type': 'FRBC.TimerStatus', + 'timer_id': '564ceeaa-5f4f-46f9-8986-8c7920f76c85'} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_element_test.py b/tests/unit/frbc/frbc_usage_forecast_element_test.py new file mode 100644 index 0000000..4865abd --- /dev/null +++ b/tests/unit/frbc/frbc_usage_forecast_element_test.py @@ -0,0 +1,56 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCUsageForecastElementTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "duration": 5364, + "usage_rate_upper_limit": 2449.222540239615, + "usage_rate_upper_95PPR": 1833.1006975798746, + "usage_rate_upper_68PPR": 7339.63634801623, + "usage_rate_expected": 2939.725042558339, + "usage_rate_lower_68PPR": 4643.821202571105, + "usage_rate_lower_95PPR": 1244.2496151489897, + "usage_rate_lower_limit": 5214.934978137386 +} + """ + + # Act + frbc_usage_forecast_element = FRBCUsageForecastElement.from_json(json_str) + + # Assert + self.assertEqual(frbc_usage_forecast_element.duration, Duration.from_timedelta(timedelta(milliseconds=5364))) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_limit, 2449.222540239615) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_95PPR, 1833.1006975798746) + self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_68PPR, 7339.63634801623) + self.assertEqual(frbc_usage_forecast_element.usage_rate_expected, 2939.725042558339) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_68PPR, 4643.821202571105) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_95PPR, 1244.2496151489897) + self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_limit, 5214.934978137386) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_usage_forecast_element = FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=5364)), usage_rate_upper_limit=2449.222540239615, usage_rate_upper_95PPR=1833.1006975798746, usage_rate_upper_68PPR=7339.63634801623, usage_rate_expected=2939.725042558339, usage_rate_lower_68PPR=4643.821202571105, usage_rate_lower_95PPR=1244.2496151489897, usage_rate_lower_limit=5214.934978137386) + + # Act + json_str = frbc_usage_forecast_element.to_json() + + # Assert + expected_json = { 'duration': 5364, + 'usage_rate_expected': 2939.725042558339, + 'usage_rate_lower_68PPR': 4643.821202571105, + 'usage_rate_lower_95PPR': 1244.2496151489897, + 'usage_rate_lower_limit': 5214.934978137386, + 'usage_rate_upper_68PPR': 7339.63634801623, + 'usage_rate_upper_95PPR': 1833.1006975798746, + 'usage_rate_upper_limit': 2449.222540239615} + self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_test.py b/tests/unit/frbc/frbc_usage_forecast_test.py new file mode 100644 index 0000000..6d40f99 --- /dev/null +++ b/tests/unit/frbc/frbc_usage_forecast_test.py @@ -0,0 +1,62 @@ + +from datetime import timedelta, datetime, timezone as offset +import json +from unittest import TestCase +import uuid + +from s2python.common import * +from s2python.frbc import * + + +class FRBCUsageForecastTest(TestCase): + def test__from_json__happy_path_full(self): + # Arrange + json_str = """ +{ + "message_type": "FRBC.UsageForecast", + "message_id": "251e2ef9-31d5-40a0-bbf6-8331742f18ad", + "start_time": "2021-09-09T03:50:01-01:00", + "elements": [ + { + "duration": 1074, + "usage_rate_upper_limit": 3368.642281190435, + "usage_rate_upper_95PPR": 6948.368971520732, + "usage_rate_upper_68PPR": 1917.1869702535832, + "usage_rate_expected": 3804.5466318560825, + "usage_rate_lower_68PPR": 7641.102737197381, + "usage_rate_lower_95PPR": 1017.0167039060763, + "usage_rate_lower_limit": 1775.7219717302214 + } + ] +} + """ + + # Act + frbc_usage_forecast = FRBCUsageForecast.from_json(json_str) + + # Assert + self.assertEqual(frbc_usage_forecast.message_type, FRBC.UsageForecast) + self.assertEqual(frbc_usage_forecast.message_id, uuid.UUID("251e2ef9-31d5-40a0-bbf6-8331742f18ad")) + self.assertEqual(frbc_usage_forecast.start_time, datetime(year=2021, month=9, day=9, hour=3, minute=50, second=1, tzinfo=offset(offset=timedelta(seconds=-3600.0)))) + self.assertEqual(frbc_usage_forecast.elements, [FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=1074)), usage_rate_upper_limit=3368.642281190435, usage_rate_upper_95PPR=6948.368971520732, usage_rate_upper_68PPR=1917.1869702535832, usage_rate_expected=3804.5466318560825, usage_rate_lower_68PPR=7641.102737197381, usage_rate_lower_95PPR=1017.0167039060763, usage_rate_lower_limit=1775.7219717302214)]) + + def test__to_json__happy_path_full(self): + # Arrange + frbc_usage_forecast = FRBCUsageForecast(message_type=FRBC.UsageForecast, message_id=uuid.UUID("251e2ef9-31d5-40a0-bbf6-8331742f18ad"), start_time=datetime(year=2021, month=9, day=9, hour=3, minute=50, second=1, tzinfo=offset(offset=timedelta(seconds=-3600.0))), elements=[FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=1074)), usage_rate_upper_limit=3368.642281190435, usage_rate_upper_95PPR=6948.368971520732, usage_rate_upper_68PPR=1917.1869702535832, usage_rate_expected=3804.5466318560825, usage_rate_lower_68PPR=7641.102737197381, usage_rate_lower_95PPR=1017.0167039060763, usage_rate_lower_limit=1775.7219717302214)]) + + # Act + json_str = frbc_usage_forecast.to_json() + + # Assert + expected_json = { 'elements': [ { 'duration': 1074, + 'usage_rate_expected': 3804.5466318560825, + 'usage_rate_lower_68PPR': 7641.102737197381, + 'usage_rate_lower_95PPR': 1017.0167039060763, + 'usage_rate_lower_limit': 1775.7219717302214, + 'usage_rate_upper_68PPR': 1917.1869702535832, + 'usage_rate_upper_95PPR': 6948.368971520732, + 'usage_rate_upper_limit': 3368.642281190435}], + 'message_id': '251e2ef9-31d5-40a0-bbf6-8331742f18ad', + 'message_type': 'FRBC.UsageForecast', + 'start_time': '2021-09-09T03:50:01-01:00'} + self.assertEqual(json.loads(json_str), expected_json) From 743e923af20884344180fe170a9c26ac167959fc Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 14:54:40 +0200 Subject: [PATCH 13/27] Reverted example_frbc_type.py to put it on a different PR --- examples/example_frbc_rm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example_frbc_rm.py b/examples/example_frbc_rm.py index 53b80df..1bdd03e 100644 --- a/examples/example_frbc_rm.py +++ b/examples/example_frbc_rm.py @@ -152,7 +152,7 @@ def deactivate(self, conn: S2Connection) -> None: s2_conn = S2Connection( - url="ws://localhost:8080/backend/rm/s2python-frbc/cem/dummy_model/ws", + url="ws://localhost:8001/backend/rm/s2python-frbc/cem/dummy_model/ws", role=EnergyManagementRole.RM, control_types=[MyFRBCControlType(), MyNoControlControlType()], asset_details=AssetDetails( From 8d0f3a6d147f011bde0af8f8b19b6660aab9615a Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 14:56:41 +0200 Subject: [PATCH 14/27] Reverted example_frbc_type.py to put it on a different PR --- examples/example_frbc_rm.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/examples/example_frbc_rm.py b/examples/example_frbc_rm.py index 1bdd03e..aea2337 100644 --- a/examples/example_frbc_rm.py +++ b/examples/example_frbc_rm.py @@ -90,9 +90,7 @@ def activate(self, conn: S2Connection) -> None: ) ], storage=FRBCStorageDescription( - fill_level_range=NumberRange( - start_of_range=0.0, end_of_range=100.0 - ), + fill_level_range=NumberRange(start_of_range=0.0, end_of_range=100.0), fill_level_label="%", diagnostic_label="Imaginary battery", provides_fill_level_target_profile=True, @@ -110,15 +108,11 @@ def activate(self, conn: S2Connection) -> None: elements=[ FRBCFillLevelTargetProfileElement( duration=Duration.from_milliseconds(30_000), - fill_level_range=NumberRange( - start_of_range=20.0, end_of_range=30.0 - ), + fill_level_range=NumberRange(start_of_range=20.0, end_of_range=30.0), ), FRBCFillLevelTargetProfileElement( duration=Duration.from_milliseconds(300_000), - fill_level_range=NumberRange( - start_of_range=40.0, end_of_range=50.0 - ), + fill_level_range=NumberRange(start_of_range=40.0, end_of_range=50.0), ), ], ) From f87df9f00b54d421d574579354a1594cf37fcaf9 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 15:06:04 +0200 Subject: [PATCH 15/27] Reverted src/s2python/generated/gen_s2.py to put it on a different PR --- src/s2python/generated/gen_s2.py | 1012 +++++++++++++++--------------- 1 file changed, 506 insertions(+), 506 deletions(-) diff --git a/src/s2python/generated/gen_s2.py b/src/s2python/generated/gen_s2.py index f665886..c7febd6 100644 --- a/src/s2python/generated/gen_s2.py +++ b/src/s2python/generated/gen_s2.py @@ -20,805 +20,805 @@ class Duration(RootModel[conint(ge=0)]): - root: conint(ge=0) = Field(..., description="Duration in milliseconds") + root: conint(ge=0) = Field(..., description='Duration in milliseconds') -class ID(RootModel[constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}")]): - root: constr(pattern=r"[a-zA-Z0-9\-_:]{2,64}") = Field(..., description="UUID") +class ID(RootModel[constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}')]): + root: constr(pattern=r'[a-zA-Z0-9\-_:]{2,64}') = Field(..., description='UUID') class Currency(Enum): - AED = "AED" - ANG = "ANG" - AUD = "AUD" - CHE = "CHE" - CHF = "CHF" - CHW = "CHW" - EUR = "EUR" - GBP = "GBP" - LBP = "LBP" - LKR = "LKR" - LRD = "LRD" - LSL = "LSL" - LYD = "LYD" - MAD = "MAD" - MDL = "MDL" - MGA = "MGA" - MKD = "MKD" - MMK = "MMK" - MNT = "MNT" - MOP = "MOP" - MRO = "MRO" - MUR = "MUR" - MVR = "MVR" - MWK = "MWK" - MXN = "MXN" - MXV = "MXV" - MYR = "MYR" - MZN = "MZN" - NAD = "NAD" - NGN = "NGN" - NIO = "NIO" - NOK = "NOK" - NPR = "NPR" - NZD = "NZD" - OMR = "OMR" - PAB = "PAB" - PEN = "PEN" - PGK = "PGK" - PHP = "PHP" - PKR = "PKR" - PLN = "PLN" - PYG = "PYG" - QAR = "QAR" - RON = "RON" - RSD = "RSD" - RUB = "RUB" - RWF = "RWF" - SAR = "SAR" - SBD = "SBD" - SCR = "SCR" - SDG = "SDG" - SEK = "SEK" - SGD = "SGD" - SHP = "SHP" - SLL = "SLL" - SOS = "SOS" - SRD = "SRD" - SSP = "SSP" - STD = "STD" - SYP = "SYP" - SZL = "SZL" - THB = "THB" - TJS = "TJS" - TMT = "TMT" - TND = "TND" - TOP = "TOP" - TRY = "TRY" - TTD = "TTD" - TWD = "TWD" - TZS = "TZS" - UAH = "UAH" - UGX = "UGX" - USD = "USD" - USN = "USN" - UYI = "UYI" - UYU = "UYU" - UZS = "UZS" - VEF = "VEF" - VND = "VND" - VUV = "VUV" - WST = "WST" - XAG = "XAG" - XAU = "XAU" - XBA = "XBA" - XBB = "XBB" - XBC = "XBC" - XBD = "XBD" - XCD = "XCD" - XOF = "XOF" - XPD = "XPD" - XPF = "XPF" - XPT = "XPT" - XSU = "XSU" - XTS = "XTS" - XUA = "XUA" - XXX = "XXX" - YER = "YER" - ZAR = "ZAR" - ZMW = "ZMW" - ZWL = "ZWL" + AED = 'AED' + ANG = 'ANG' + AUD = 'AUD' + CHE = 'CHE' + CHF = 'CHF' + CHW = 'CHW' + EUR = 'EUR' + GBP = 'GBP' + LBP = 'LBP' + LKR = 'LKR' + LRD = 'LRD' + LSL = 'LSL' + LYD = 'LYD' + MAD = 'MAD' + MDL = 'MDL' + MGA = 'MGA' + MKD = 'MKD' + MMK = 'MMK' + MNT = 'MNT' + MOP = 'MOP' + MRO = 'MRO' + MUR = 'MUR' + MVR = 'MVR' + MWK = 'MWK' + MXN = 'MXN' + MXV = 'MXV' + MYR = 'MYR' + MZN = 'MZN' + NAD = 'NAD' + NGN = 'NGN' + NIO = 'NIO' + NOK = 'NOK' + NPR = 'NPR' + NZD = 'NZD' + OMR = 'OMR' + PAB = 'PAB' + PEN = 'PEN' + PGK = 'PGK' + PHP = 'PHP' + PKR = 'PKR' + PLN = 'PLN' + PYG = 'PYG' + QAR = 'QAR' + RON = 'RON' + RSD = 'RSD' + RUB = 'RUB' + RWF = 'RWF' + SAR = 'SAR' + SBD = 'SBD' + SCR = 'SCR' + SDG = 'SDG' + SEK = 'SEK' + SGD = 'SGD' + SHP = 'SHP' + SLL = 'SLL' + SOS = 'SOS' + SRD = 'SRD' + SSP = 'SSP' + STD = 'STD' + SYP = 'SYP' + SZL = 'SZL' + THB = 'THB' + TJS = 'TJS' + TMT = 'TMT' + TND = 'TND' + TOP = 'TOP' + TRY = 'TRY' + TTD = 'TTD' + TWD = 'TWD' + TZS = 'TZS' + UAH = 'UAH' + UGX = 'UGX' + USD = 'USD' + USN = 'USN' + UYI = 'UYI' + UYU = 'UYU' + UZS = 'UZS' + VEF = 'VEF' + VND = 'VND' + VUV = 'VUV' + WST = 'WST' + XAG = 'XAG' + XAU = 'XAU' + XBA = 'XBA' + XBB = 'XBB' + XBC = 'XBC' + XBD = 'XBD' + XCD = 'XCD' + XOF = 'XOF' + XPD = 'XPD' + XPF = 'XPF' + XPT = 'XPT' + XSU = 'XSU' + XTS = 'XTS' + XUA = 'XUA' + XXX = 'XXX' + YER = 'YER' + ZAR = 'ZAR' + ZMW = 'ZMW' + ZWL = 'ZWL' class SessionRequestType(Enum): - RECONNECT = "RECONNECT" - TERMINATE = "TERMINATE" + RECONNECT = 'RECONNECT' + TERMINATE = 'TERMINATE' class RevokableObjects(Enum): - PEBC_PowerConstraints = "PEBC.PowerConstraints" - PEBC_EnergyConstraint = "PEBC.EnergyConstraint" - PEBC_Instruction = "PEBC.Instruction" - PPBC_PowerProfileDefinition = "PPBC.PowerProfileDefinition" - PPBC_ScheduleInstruction = "PPBC.ScheduleInstruction" - PPBC_StartInterruptionInstruction = "PPBC.StartInterruptionInstruction" - PPBC_EndInterruptionInstruction = "PPBC.EndInterruptionInstruction" - OMBC_SystemDescription = "OMBC.SystemDescription" - OMBC_Instruction = "OMBC.Instruction" - FRBC_SystemDescription = "FRBC.SystemDescription" - FRBC_Instruction = "FRBC.Instruction" - DDBC_SystemDescription = "DDBC.SystemDescription" - DDBC_Instruction = "DDBC.Instruction" + PEBC_PowerConstraints = 'PEBC.PowerConstraints' + PEBC_EnergyConstraint = 'PEBC.EnergyConstraint' + PEBC_Instruction = 'PEBC.Instruction' + PPBC_PowerProfileDefinition = 'PPBC.PowerProfileDefinition' + PPBC_ScheduleInstruction = 'PPBC.ScheduleInstruction' + PPBC_StartInterruptionInstruction = 'PPBC.StartInterruptionInstruction' + PPBC_EndInterruptionInstruction = 'PPBC.EndInterruptionInstruction' + OMBC_SystemDescription = 'OMBC.SystemDescription' + OMBC_Instruction = 'OMBC.Instruction' + FRBC_SystemDescription = 'FRBC.SystemDescription' + FRBC_Instruction = 'FRBC.Instruction' + DDBC_SystemDescription = 'DDBC.SystemDescription' + DDBC_Instruction = 'DDBC.Instruction' class EnergyManagementRole(Enum): - CEM = "CEM" - RM = "RM" + CEM = 'CEM' + RM = 'RM' class ReceptionStatusValues(Enum): - INVALID_DATA = "INVALID_DATA" - INVALID_MESSAGE = "INVALID_MESSAGE" - INVALID_CONTENT = "INVALID_CONTENT" - TEMPORARY_ERROR = "TEMPORARY_ERROR" - PERMANENT_ERROR = "PERMANENT_ERROR" - OK = "OK" + INVALID_DATA = 'INVALID_DATA' + INVALID_MESSAGE = 'INVALID_MESSAGE' + INVALID_CONTENT = 'INVALID_CONTENT' + TEMPORARY_ERROR = 'TEMPORARY_ERROR' + PERMANENT_ERROR = 'PERMANENT_ERROR' + OK = 'OK' class NumberRange(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) start_of_range: float = Field( - ..., description="Number that defines the start of the range" + ..., description='Number that defines the start of the range' ) end_of_range: float = Field( - ..., description="Number that defines the end of the range" + ..., description='Number that defines the end of the range' ) class Transition(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.", + description='ID of the Transition. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', ) from_: ID = Field( ..., - alias="from", - description="ID of the OperationMode (exact type differs per ControlType) that should be switched from.", + alias='from', + description='ID of the OperationMode (exact type differs per ControlType) that should be switched from.', ) to: ID = Field( ..., - description="ID of the OperationMode (exact type differs per ControlType) that will be switched to.", + description='ID of the OperationMode (exact type differs per ControlType) that will be switched to.', ) start_timers: List[ID] = Field( ..., - description="List of IDs of Timers that will be (re)started when this transition is initiated", + description='List of IDs of Timers that will be (re)started when this transition is initiated', max_length=1000, min_length=0, ) blocking_timers: List[ID] = Field( ..., - description="List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished", + description='List of IDs of Timers that block this Transition from initiating while at least one of these Timers is not yet finished', max_length=1000, min_length=0, ) transition_costs: Optional[float] = Field( None, - description="Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.", + description='Absolute costs for going through this Transition in the currency as described in the ResourceManagerDetails.', ) transition_duration: Optional[Duration] = Field( None, - description="Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.", + description='Indicates the time between the initiation of this Transition, and the time at which the device behaves according to the Operation Mode which is defined in the ‘to’ data element. When no value is provided it is assumed the transition duration is negligible.', ) abnormal_condition_only: bool = Field( ..., - description="Indicates if this Transition may only be used during an abnormal condition (see Clause )", + description='Indicates if this Transition may only be used during an abnormal condition (see Clause )', ) class Timer(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.", + description='ID of the Timer. Must be unique in the scope of the OMBC.SystemDescription, FRBC.ActuatorDescription or DDBC.ActuatorDescription in which it is used.', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description of the Timer. This element is only intended for diagnostic purposes and not for HMI applications.', ) duration: Duration = Field( ..., - description="The time it takes for the Timer to finish after it has been started", + description='The time it takes for the Timer to finish after it has been started', ) class PEBCPowerEnvelopeElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - duration: Duration = Field(..., description="The duration of the element") + duration: Duration = Field(..., description='The duration of the element') upper_limit: float = Field( ..., - description="Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.", + description='Upper power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or below the upper_limit. The upper_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type UPPER_LIMIT.', ) lower_limit: float = Field( ..., - description="Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.", + description='Lower power limit according to the commodity_quantity of the containing PEBC.PowerEnvelope. The lower_limit must be smaller or equal to the upper_limit. The Resource Manager is requested to keep the power values for the given commodity quantity equal to or above the lower_limit. The lower_limit shall be in accordance with the constraints provided by the Resource Manager through any PEBC.AllowedLimitRange with limit_type LOWER_LIMIT.', ) class FRBCStorageDescription(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description of the storage (e.g. hot water buffer or battery). This element is only intended for diagnostic purposes and not for HMI applications.', ) fill_level_label: Optional[str] = Field( None, - description="Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable description of the (physical) units associated with the fill_level (e.g. degrees Celsius or percentage state of charge). This element is only intended for diagnostic purposes and not for HMI applications.', ) provides_leakage_behaviour: bool = Field( ..., - description="Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.", + description='Indicates whether the Storage could provide details of power leakage behaviour through the FRBC.LeakageBehaviour.', ) provides_fill_level_target_profile: bool = Field( ..., - description="Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.", + description='Indicates whether the Storage could provide a target profile for the fill level through the FRBC.FillLevelTargetProfile.', ) provides_usage_forecast: bool = Field( ..., - description="Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.", + description='Indicates whether the Storage could provide a UsageForecast through the FRBC.UsageForecast.', ) fill_level_range: NumberRange = Field( ..., - description="The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ", + description='The range in which the fill_level should remain. It is expected of the CEM to keep the fill_level within this range. When the fill_level is not within this range, the Resource Manager can ignore instructions from the CEM (except during abnormal conditions). ', ) class FRBCLeakageBehaviourElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) fill_level_range: NumberRange = Field( ..., - description="The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.", + description='The fill level range for which this FRBC.LeakageBehaviourElement applies. The start of the range must be less than the end of the range.', ) leakage_rate: float = Field( ..., - description="Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.", + description='Indicates how fast the momentary fill level will decrease per second due to leakage within the given range of the fill level. A positive value indicates that the fill level decreases over time due to leakage.', ) class FRBCUsageForecastElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) duration: Duration = Field( - ..., description="Indicator for how long the given usage_rate is valid." + ..., description='Indicator for how long the given usage_rate is valid.' ) usage_rate_upper_limit: Optional[float] = Field( None, - description="The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", + description='The upper limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', ) usage_rate_upper_95PPR: Optional[float] = Field( None, - description="The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", + description='The upper limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', ) usage_rate_upper_68PPR: Optional[float] = Field( None, - description="The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", + description='The upper limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', ) usage_rate_expected: float = Field( ..., - description="The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.", + description='The most likely value for the usage rate; the expected increase or decrease of the fill_level per second. A positive value indicates that the fill level will decrease due to usage.', ) usage_rate_lower_68PPR: Optional[float] = Field( None, - description="The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", + description='The lower limit of the range with a 68 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', ) usage_rate_lower_95PPR: Optional[float] = Field( None, - description="The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", + description='The lower limit of the range with a 95 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', ) usage_rate_lower_limit: Optional[float] = Field( None, - description="The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.", + description='The lower limit of the range with a 100 % probability that the usage rate is within that range. A positive value indicates that the fill level will decrease due to usage.', ) class FRBCFillLevelTargetProfileElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - duration: Duration = Field(..., description="The duration of the element.") + duration: Duration = Field(..., description='The duration of the element.') fill_level_range: NumberRange = Field( ..., - description="The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.", + description='The target range in which the fill_level must be for the time period during which the element is active. The start of the range must be smaller or equal to the end of the range. The CEM must take best-effort actions to proactively achieve this target.', ) class DDBCAverageDemandRateForecastElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - duration: Duration = Field(..., description="Duration of the element") + duration: Duration = Field(..., description='Duration of the element') demand_rate_upper_limit: Optional[float] = Field( None, - description="The upper limit of the range with a 100 % probability that the demand rate is within that range", + description='The upper limit of the range with a 100 % probability that the demand rate is within that range', ) demand_rate_upper_95PPR: Optional[float] = Field( None, - description="The upper limit of the range with a 95 % probability that the demand rate is within that range", + description='The upper limit of the range with a 95 % probability that the demand rate is within that range', ) demand_rate_upper_68PPR: Optional[float] = Field( None, - description="The upper limit of the range with a 68 % probability that the demand rate is within that range", + description='The upper limit of the range with a 68 % probability that the demand rate is within that range', ) demand_rate_expected: float = Field( ..., - description="The most likely value for the demand rate; the expected increase or decrease of the fill_level per second", + description='The most likely value for the demand rate; the expected increase or decrease of the fill_level per second', ) demand_rate_lower_68PPR: Optional[float] = Field( None, - description="The lower limit of the range with a 68 % probability that the demand rate is within that range", + description='The lower limit of the range with a 68 % probability that the demand rate is within that range', ) demand_rate_lower_95PPR: Optional[float] = Field( None, - description="The lower limit of the range with a 95 % probability that the demand rate is within that range", + description='The lower limit of the range with a 95 % probability that the demand rate is within that range', ) demand_rate_lower_limit: Optional[float] = Field( None, - description="The lower limit of the range with a 100 % probability that the demand rate is within that range", + description='The lower limit of the range with a 100 % probability that the demand rate is within that range', ) class RoleType(Enum): - ENERGY_PRODUCER = "ENERGY_PRODUCER" - ENERGY_CONSUMER = "ENERGY_CONSUMER" - ENERGY_STORAGE = "ENERGY_STORAGE" + ENERGY_PRODUCER = 'ENERGY_PRODUCER' + ENERGY_CONSUMER = 'ENERGY_CONSUMER' + ENERGY_STORAGE = 'ENERGY_STORAGE' class Commodity(Enum): - GAS = "GAS" - HEAT = "HEAT" - ELECTRICITY = "ELECTRICITY" - OIL = "OIL" + GAS = 'GAS' + HEAT = 'HEAT' + ELECTRICITY = 'ELECTRICITY' + OIL = 'OIL' class CommodityQuantity(Enum): - ELECTRIC_POWER_L1 = "ELECTRIC.POWER.L1" - ELECTRIC_POWER_L2 = "ELECTRIC.POWER.L2" - ELECTRIC_POWER_L3 = "ELECTRIC.POWER.L3" - ELECTRIC_POWER_3_PHASE_SYMMETRIC = "ELECTRIC.POWER.3_PHASE_SYMMETRIC" - NATURAL_GAS_FLOW_RATE = "NATURAL_GAS.FLOW_RATE" - HYDROGEN_FLOW_RATE = "HYDROGEN.FLOW_RATE" - HEAT_TEMPERATURE = "HEAT.TEMPERATURE" - HEAT_FLOW_RATE = "HEAT.FLOW_RATE" - HEAT_THERMAL_POWER = "HEAT.THERMAL_POWER" - OIL_FLOW_RATE = "OIL.FLOW_RATE" + ELECTRIC_POWER_L1 = 'ELECTRIC.POWER.L1' + ELECTRIC_POWER_L2 = 'ELECTRIC.POWER.L2' + ELECTRIC_POWER_L3 = 'ELECTRIC.POWER.L3' + ELECTRIC_POWER_3_PHASE_SYMMETRIC = 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' + NATURAL_GAS_FLOW_RATE = 'NATURAL_GAS.FLOW_RATE' + HYDROGEN_FLOW_RATE = 'HYDROGEN.FLOW_RATE' + HEAT_TEMPERATURE = 'HEAT.TEMPERATURE' + HEAT_FLOW_RATE = 'HEAT.FLOW_RATE' + HEAT_THERMAL_POWER = 'HEAT.THERMAL_POWER' + OIL_FLOW_RATE = 'OIL.FLOW_RATE' class InstructionStatus(Enum): - NEW = "NEW" - ACCEPTED = "ACCEPTED" - REJECTED = "REJECTED" - REVOKED = "REVOKED" - STARTED = "STARTED" - SUCCEEDED = "SUCCEEDED" - ABORTED = "ABORTED" + NEW = 'NEW' + ACCEPTED = 'ACCEPTED' + REJECTED = 'REJECTED' + REVOKED = 'REVOKED' + STARTED = 'STARTED' + SUCCEEDED = 'SUCCEEDED' + ABORTED = 'ABORTED' class ControlType(Enum): - POWER_ENVELOPE_BASED_CONTROL = "POWER_ENVELOPE_BASED_CONTROL" - POWER_PROFILE_BASED_CONTROL = "POWER_PROFILE_BASED_CONTROL" - OPERATION_MODE_BASED_CONTROL = "OPERATION_MODE_BASED_CONTROL" - FILL_RATE_BASED_CONTROL = "FILL_RATE_BASED_CONTROL" - DEMAND_DRIVEN_BASED_CONTROL = "DEMAND_DRIVEN_BASED_CONTROL" - NOT_CONTROLABLE = "NOT_CONTROLABLE" - NO_SELECTION = "NO_SELECTION" + POWER_ENVELOPE_BASED_CONTROL = 'POWER_ENVELOPE_BASED_CONTROL' + POWER_PROFILE_BASED_CONTROL = 'POWER_PROFILE_BASED_CONTROL' + OPERATION_MODE_BASED_CONTROL = 'OPERATION_MODE_BASED_CONTROL' + FILL_RATE_BASED_CONTROL = 'FILL_RATE_BASED_CONTROL' + DEMAND_DRIVEN_BASED_CONTROL = 'DEMAND_DRIVEN_BASED_CONTROL' + NOT_CONTROLABLE = 'NOT_CONTROLABLE' + NO_SELECTION = 'NO_SELECTION' class PEBCPowerEnvelopeLimitType(Enum): - UPPER_LIMIT = "UPPER_LIMIT" - LOWER_LIMIT = "LOWER_LIMIT" + UPPER_LIMIT = 'UPPER_LIMIT' + LOWER_LIMIT = 'LOWER_LIMIT' class PEBCPowerEnvelopeConsequenceType(Enum): - VANISH = "VANISH" - DEFER = "DEFER" + VANISH = 'VANISH' + DEFER = 'DEFER' class PPBCPowerSequenceStatus(Enum): - NOT_SCHEDULED = "NOT_SCHEDULED" - SCHEDULED = "SCHEDULED" - EXECUTING = "EXECUTING" - INTERRUPTED = "INTERRUPTED" - FINISHED = "FINISHED" - ABORTED = "ABORTED" + NOT_SCHEDULED = 'NOT_SCHEDULED' + SCHEDULED = 'SCHEDULED' + EXECUTING = 'EXECUTING' + INTERRUPTED = 'INTERRUPTED' + FINISHED = 'FINISHED' + ABORTED = 'ABORTED' class OMBCTimerStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["OMBC.TimerStatus"] = "OMBC.TimerStatus" + message_type: Literal['OMBC.TimerStatus'] = 'OMBC.TimerStatus' message_id: ID - timer_id: ID = Field(..., description="The ID of the timer this message refers to") + timer_id: ID = Field(..., description='The ID of the timer this message refers to') finished_at: AwareDatetime = Field( ..., - description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", + description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', ) class FRBCTimerStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.TimerStatus"] = "FRBC.TimerStatus" + message_type: Literal['FRBC.TimerStatus'] = 'FRBC.TimerStatus' message_id: ID - timer_id: ID = Field(..., description="The ID of the timer this message refers to") + timer_id: ID = Field(..., description='The ID of the timer this message refers to') actuator_id: ID = Field( - ..., description="The ID of the actuator the timer belongs to" + ..., description='The ID of the actuator the timer belongs to' ) finished_at: AwareDatetime = Field( ..., - description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", + description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', ) class DDBCTimerStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["DDBC.TimerStatus"] = "DDBC.TimerStatus" + message_type: Literal['DDBC.TimerStatus'] = 'DDBC.TimerStatus' message_id: ID - timer_id: ID = Field(..., description="The ID of the timer this message refers to") + timer_id: ID = Field(..., description='The ID of the timer this message refers to') actuator_id: ID = Field( - ..., description="The ID of the actuator the timer belongs to" + ..., description='The ID of the actuator the timer belongs to' ) finished_at: AwareDatetime = Field( ..., - description="Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.", + description='Indicates when the Timer will be finished. If the DateTimeStamp is in the future, the timer is not yet finished. If the DateTimeStamp is in the past, the timer is finished. If the timer was never started, the value can be an arbitrary DateTimeStamp in the past.', ) class SelectControlType(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["SelectControlType"] = "SelectControlType" + message_type: Literal['SelectControlType'] = 'SelectControlType' message_id: ID control_type: ControlType = Field( ..., - description="The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails", + description='The ControlType to activate. Must be one of the available ControlTypes as defined in the ResourceManagerDetails', ) class SessionRequest(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["SessionRequest"] = "SessionRequest" + message_type: Literal['SessionRequest'] = 'SessionRequest' message_id: ID - request: SessionRequestType = Field(..., description="The type of request") + request: SessionRequestType = Field(..., description='The type of request') diagnostic_label: Optional[str] = Field( None, - description="Optional field for a human readible descirption for debugging purposes", + description='Optional field for a human readible descirption for debugging purposes', ) class RevokeObject(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["RevokeObject"] = "RevokeObject" + message_type: Literal['RevokeObject'] = 'RevokeObject' message_id: ID object_type: RevokableObjects = Field( - ..., description="The type of object that needs to be revoked" + ..., description='The type of object that needs to be revoked' ) - object_id: ID = Field(..., description="The ID of object that needs to be revoked") + object_id: ID = Field(..., description='The ID of object that needs to be revoked') class Handshake(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["Handshake"] = "Handshake" + message_type: Literal['Handshake'] = 'Handshake' message_id: ID role: EnergyManagementRole = Field( - ..., description="The role of the sender of this message" + ..., description='The role of the sender of this message' ) supported_protocol_versions: Optional[List[str]] = Field( None, - description="Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.", + description='Protocol versions supported by the sender of this message. This field is mandatory for the RM, but optional for the CEM.', min_length=1, ) class HandshakeResponse(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["HandshakeResponse"] = "HandshakeResponse" + message_type: Literal['HandshakeResponse'] = 'HandshakeResponse' message_id: ID selected_protocol_version: str = Field( - ..., description="The protocol version the CEM selected for this session" + ..., description='The protocol version the CEM selected for this session' ) class ReceptionStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["ReceptionStatus"] = "ReceptionStatus" + message_type: Literal['ReceptionStatus'] = 'ReceptionStatus' subject_message_id: ID = Field( - ..., description="The message this ReceptionStatus refers to" + ..., description='The message this ReceptionStatus refers to' ) status: ReceptionStatusValues = Field( - ..., description="Enumeration of status values" + ..., description='Enumeration of status values' ) diagnostic_label: Optional[str] = Field( None, - description="Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.", + description='Diagnostic label that can be used to provide additional information for debugging. However, not for HMI purposes.', ) class InstructionStatusUpdate(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["InstructionStatusUpdate"] = "InstructionStatusUpdate" + message_type: Literal['InstructionStatusUpdate'] = 'InstructionStatusUpdate' message_id: ID instruction_id: ID = Field( - ..., description="ID of this instruction (as provided by the CEM) " + ..., description='ID of this instruction (as provided by the CEM) ' ) status_type: InstructionStatus = Field( - ..., description="Present status of this instruction." + ..., description='Present status of this instruction.' ) timestamp: AwareDatetime = Field( - ..., description="Timestamp when status_type has changed the last time." + ..., description='Timestamp when status_type has changed the last time.' ) class PEBCEnergyConstraint(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PEBC.EnergyConstraint"] = "PEBC.EnergyConstraint" + message_type: Literal['PEBC.EnergyConstraint'] = 'PEBC.EnergyConstraint' message_id: ID id: ID = Field( ..., - description="Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='Identifier of this PEBC.EnergyConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) valid_from: AwareDatetime = Field( ..., - description="Moment this PEBC.EnergyConstraints information starts to be valid", + description='Moment this PEBC.EnergyConstraints information starts to be valid', ) valid_until: AwareDatetime = Field( ..., - description="Moment until this PEBC.EnergyConstraints information is valid.", + description='Moment until this PEBC.EnergyConstraints information is valid.', ) upper_average_power: float = Field( ..., - description="Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.", + description='Upper average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated upper energy content can be derived. This is the highest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy consumption (in case the number is positive). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', ) lower_average_power: float = Field( ..., - description="Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.", + description='Lower average power within the time period given by valid_from and valid_until. If the duration is multiplied with this power value, then the associated lower energy content can be derived. This is the lowest amount of energy the resource will consume during that period of time. The Power Envelope created by the CEM must allow at least this much energy production (in case the number is negative). Must be greater than or equal to lower_average_power, and can be negative in case of energy production.', ) commodity_quantity: CommodityQuantity = Field( ..., - description="Type of power quantity which applies to upper_average_power and lower_average_power", + description='Type of power quantity which applies to upper_average_power and lower_average_power', ) class PPBCScheduleInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PPBC.ScheduleInstruction"] = "PPBC.ScheduleInstruction" + message_type: Literal['PPBC.ScheduleInstruction'] = 'PPBC.ScheduleInstruction' message_id: ID id: ID = Field( ..., - description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) power_profile_id: ID = Field( ..., - description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.", + description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', ) sequence_container_id: ID = Field( ..., - description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.", + description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being selected and scheduled by the CEM.', ) power_sequence_id: ID = Field( ..., - description="ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.", + description='ID of the PPBC.PowerSequence that is being selected and scheduled by the CEM.', ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment the PPBC.PowerSequence shall start. When the specified execution time is in the past, execution must start as soon as possible.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition", + description='Indicates if this is an instruction during an abnormal condition', ) class PPBCStartInterruptionInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PPBC.StartInterruptionInstruction"] = ( - "PPBC.StartInterruptionInstruction" + message_type: Literal['PPBC.StartInterruptionInstruction'] = ( + 'PPBC.StartInterruptionInstruction' ) message_id: ID id: ID = Field( ..., - description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) power_profile_id: ID = Field( ..., - description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.", + description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence is being interrupted by the CEM.', ) sequence_container_id: ID = Field( ..., - description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.", + description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence is being interrupted by the CEM.', ) power_sequence_id: ID = Field( - ..., description="ID of the PPBC.PowerSequence that the CEM wants to interrupt." + ..., description='ID of the PPBC.PowerSequence that the CEM wants to interrupt.' ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment the PPBC.PowerSequence shall be interrupted. When the specified execution time is in the past, execution must start as soon as possible.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition", + description='Indicates if this is an instruction during an abnormal condition', ) class PPBCEndInterruptionInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PPBC.EndInterruptionInstruction"] = ( - "PPBC.EndInterruptionInstruction" + message_type: Literal['PPBC.EndInterruptionInstruction'] = ( + 'PPBC.EndInterruptionInstruction' ) message_id: ID id: ID = Field( ..., - description="ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) power_profile_id: ID = Field( ..., - description="ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.", + description='ID of the PPBC.PowerProfileDefinition of which the PPBC.PowerSequence interruption is being ended by the CEM.', ) sequence_container_id: ID = Field( ..., - description="ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.", + description='ID of the PPBC.PowerSequnceContainer of which the PPBC.PowerSequence interruption is being ended by the CEM.', ) power_sequence_id: ID = Field( ..., - description="ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.", + description='ID of the PPBC.PowerSequence for which the CEM wants to end the interruption.', ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment PPBC.PowerSequence interruption shall end. When the specified execution time is in the past, execution must start as soon as possible.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition", + description='Indicates if this is an instruction during an abnormal condition', ) class OMBCStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["OMBC.Status"] = "OMBC.Status" + message_type: Literal['OMBC.Status'] = 'OMBC.Status' message_id: ID active_operation_mode_id: ID = Field( - ..., description="ID of the active OMBC.OperationMode." + ..., description='ID of the active OMBC.OperationMode.' ) operation_mode_factor: float = Field( ..., - description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.", + description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', ) previous_operation_mode_id: Optional[ID] = Field( None, - description="ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.", + description='ID of the OMBC.OperationMode that was previously active. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description="Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.", + description='Time at which the transition from the previous OMBC.OperationMode to the active OMBC.OperationMode was initiated. This value shall always be provided, unless the active OMBC.OperationMode is the first OMBC.OperationMode the Resource Manager is aware of.', ) class OMBCInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["OMBC.Instruction"] = "OMBC.Instruction" + message_type: Literal['OMBC.Instruction'] = 'OMBC.Instruction' message_id: ID id: ID = Field( ..., - description="ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', ) operation_mode_id: ID = Field( - ..., description="ID of the OMBC.OperationMode that should be activated" + ..., description='ID of the OMBC.OperationMode that should be activated' ) operation_mode_factor: float = Field( ..., - description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.", + description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal than 0 and less or equal to 1.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition", + description='Indicates if this is an instruction during an abnormal condition', ) class FRBCActuatorStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.ActuatorStatus"] = "FRBC.ActuatorStatus" + message_type: Literal['FRBC.ActuatorStatus'] = 'FRBC.ActuatorStatus' message_id: ID actuator_id: ID = Field( - ..., description="ID of the actuator this messages refers to" + ..., description='ID of the actuator this messages refers to' ) active_operation_mode_id: ID = Field( - ..., description="ID of the FRBC.OperationMode that is presently active." + ..., description='ID of the FRBC.OperationMode that is presently active.' ) operation_mode_factor: float = Field( ..., - description="The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.", + description='The number indicates the factor with which the FRBC.OperationMode is configured. The factor should be greater than or equal than 0 and less or equal to 1.', ) previous_operation_mode_id: Optional[ID] = Field( None, - description="ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.", + description='ID of the FRBC.OperationMode that was active before the present one. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description="Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.", + description='Time at which the transition from the previous FRBC.OperationMode to the active FRBC.OperationMode was initiated. This value shall always be provided, unless the active FRBC.OperationMode is the first FRBC.OperationMode the Resource Manager is aware of.', ) class FRBCStorageStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.StorageStatus"] = "FRBC.StorageStatus" + message_type: Literal['FRBC.StorageStatus'] = 'FRBC.StorageStatus' message_id: ID present_fill_level: float = Field( - ..., description="Present fill level of the Storage" + ..., description='Present fill level of the Storage' ) class FRBCLeakageBehaviour(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.LeakageBehaviour"] = "FRBC.LeakageBehaviour" + message_type: Literal['FRBC.LeakageBehaviour'] = 'FRBC.LeakageBehaviour' message_id: ID valid_from: AwareDatetime = Field( ..., - description="Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.", + description='Moment this FRBC.LeakageBehaviour starts to be valid. If the FRBC.LeakageBehaviour is immediately valid, the DateTimeStamp should be now or in the past.', ) elements: List[FRBCLeakageBehaviourElement] = Field( ..., - description="List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.", + description='List of elements that model the leakage behaviour of the buffer. The fill_level_ranges of the elements must be contiguous.', max_length=288, min_length=1, ) @@ -826,46 +826,46 @@ class FRBCLeakageBehaviour(BaseModel): class FRBCInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.Instruction"] = "FRBC.Instruction" + message_type: Literal['FRBC.Instruction'] = 'FRBC.Instruction' message_id: ID id: ID = Field( ..., - description="ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) actuator_id: ID = Field( - ..., description="ID of the actuator this instruction belongs to." + ..., description='ID of the actuator this instruction belongs to.' ) operation_mode: ID = Field( - ..., description="ID of the FRBC.OperationMode that should be activated." + ..., description='ID of the FRBC.OperationMode that should be activated.' ) operation_mode_factor: float = Field( ..., - description="The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.", + description='The number indicates the factor with which the FRBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition.", + description='Indicates if this is an instruction during an abnormal condition.', ) class FRBCUsageForecast(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.UsageForecast"] = "FRBC.UsageForecast" + message_type: Literal['FRBC.UsageForecast'] = 'FRBC.UsageForecast' message_id: ID start_time: AwareDatetime = Field( - ..., description="Time at which the FRBC.UsageForecast starts." + ..., description='Time at which the FRBC.UsageForecast starts.' ) elements: List[FRBCUsageForecastElement] = Field( ..., - description="Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.", + description='Further elements that model the profile. There shall be at least one element. Elements must be placed in chronological order.', max_length=288, min_length=1, ) @@ -873,16 +873,16 @@ class FRBCUsageForecast(BaseModel): class FRBCFillLevelTargetProfile(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.FillLevelTargetProfile"] = "FRBC.FillLevelTargetProfile" + message_type: Literal['FRBC.FillLevelTargetProfile'] = 'FRBC.FillLevelTargetProfile' message_id: ID start_time: AwareDatetime = Field( - ..., description="Time at which the FRBC.FillLevelTargetProfile starts." + ..., description='Time at which the FRBC.FillLevelTargetProfile starts.' ) elements: List[FRBCFillLevelTargetProfileElement] = Field( ..., - description="List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.", + description='List of different fill levels that have to be targeted within a given duration. There shall be at least one element. Elements must be placed in chronological order.', max_length=288, min_length=1, ) @@ -890,71 +890,71 @@ class FRBCFillLevelTargetProfile(BaseModel): class DDBCActuatorStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["DDBC.ActuatorStatus"] = "DDBC.ActuatorStatus" + message_type: Literal['DDBC.ActuatorStatus'] = 'DDBC.ActuatorStatus' message_id: ID actuator_id: ID = Field( - ..., description="ID of the actuator this messages refers to" + ..., description='ID of the actuator this messages refers to' ) active_operation_mode_id: ID = Field( ..., - description="The operation mode that is presently active for this actuator.", + description='The operation mode that is presently active for this actuator.', ) operation_mode_factor: float = Field( ..., - description="The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.", + description='The number indicates the factor with which the DDBC.OperationMode is configured. The factor should be greater than or equal to 0 and less or equal to 1.', ) previous_operation_mode_id: Optional[ID] = Field( None, - description="ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.", + description='ID of the DDBC,OperationMode that was active before the present one. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', ) transition_timestamp: Optional[AwareDatetime] = Field( None, - description="Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.", + description='Time at which the transition from the previous DDBC.OperationMode to the active DDBC.OperationMode was initiated. This value shall always be provided, unless the active DDBC.OperationMode is the first DDBC.OperationMode the Resource Manager is aware of.', ) class DDBCInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["DDBC.Instruction"] = "DDBC.Instruction" + message_type: Literal['DDBC.Instruction'] = 'DDBC.Instruction' message_id: ID id: ID = Field( ..., - description="Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='Identifier of this DDBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition", + description='Indicates if this is an instruction during an abnormal condition', ) actuator_id: ID = Field( - ..., description="ID of the actuator this Instruction belongs to." + ..., description='ID of the actuator this Instruction belongs to.' ) - operation_mode_id: ID = Field(..., description="ID of the DDBC.OperationMode") + operation_mode_id: ID = Field(..., description='ID of the DDBC.OperationMode') operation_mode_factor: float = Field( ..., - description="The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.", + description='The number indicates the factor with which the OMBC.OperationMode should be configured. The factor should be greater than or equal to 0 and less or equal to 1.', ) class DDBCAverageDemandRateForecast(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["DDBC.AverageDemandRateForecast"] = ( - "DDBC.AverageDemandRateForecast" + message_type: Literal['DDBC.AverageDemandRateForecast'] = ( + 'DDBC.AverageDemandRateForecast' ) message_id: ID - start_time: AwareDatetime = Field(..., description="Start time of the profile.") + start_time: AwareDatetime = Field(..., description='Start time of the profile.') elements: List[DDBCAverageDemandRateForecastElement] = Field( ..., - description="Elements of the profile. Elements must be placed in chronological order.", + description='Elements of the profile. Elements must be placed in chronological order.', max_length=288, min_length=1, ) @@ -962,84 +962,84 @@ class DDBCAverageDemandRateForecast(BaseModel): class PowerValue(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) commodity_quantity: CommodityQuantity = Field( - ..., description="The power quantity the value refers to" + ..., description='The power quantity the value refers to' ) value: float = Field( ..., - description="Power value expressed in the unit associated with the CommodityQuantity", + description='Power value expressed in the unit associated with the CommodityQuantity', ) class PowerForecastValue(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) value_upper_limit: Optional[float] = Field( None, - description="The upper boundary of the range with 100 % certainty the power value is in it", + description='The upper boundary of the range with 100 % certainty the power value is in it', ) value_upper_95PPR: Optional[float] = Field( None, - description="The upper boundary of the range with 95 % certainty the power value is in it", + description='The upper boundary of the range with 95 % certainty the power value is in it', ) value_upper_68PPR: Optional[float] = Field( None, - description="The upper boundary of the range with 68 % certainty the power value is in it", + description='The upper boundary of the range with 68 % certainty the power value is in it', ) - value_expected: float = Field(..., description="The expected power value.") + value_expected: float = Field(..., description='The expected power value.') value_lower_68PPR: Optional[float] = Field( None, - description="The lower boundary of the range with 68 % certainty the power value is in it", + description='The lower boundary of the range with 68 % certainty the power value is in it', ) value_lower_95PPR: Optional[float] = Field( None, - description="The lower boundary of the range with 95 % certainty the power value is in it", + description='The lower boundary of the range with 95 % certainty the power value is in it', ) value_lower_limit: Optional[float] = Field( None, - description="The lower boundary of the range with 100 % certainty the power value is in it", + description='The lower boundary of the range with 100 % certainty the power value is in it', ) commodity_quantity: CommodityQuantity = Field( - ..., description="The power quantity the value refers to" + ..., description='The power quantity the value refers to' ) class PowerRange(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) start_of_range: float = Field( - ..., description="Power value that defines the start of the range." + ..., description='Power value that defines the start of the range.' ) end_of_range: float = Field( - ..., description="Power value that defines the end of the range." + ..., description='Power value that defines the end of the range.' ) commodity_quantity: CommodityQuantity = Field( - ..., description="The power quantity the values refer to" + ..., description='The power quantity the values refer to' ) class Role(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) role: RoleType = Field( - ..., description="Role type of the Resource Manager for the given commodity" + ..., description='Role type of the Resource Manager for the given commodity' ) - commodity: Commodity = Field(..., description="Commodity the role refers to.") + commodity: Commodity = Field(..., description='Commodity the role refers to.') class PowerForecastElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - duration: Duration = Field(..., description="Duration of the PowerForecastElement") + duration: Duration = Field(..., description='Duration of the PowerForecastElement') power_values: List[PowerForecastValue] = Field( ..., - description="The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.", + description='The values of power that are expected for the given period of time. There shall be at least one PowerForecastValue, and at most one PowerForecastValue per CommodityQuantity.', max_length=10, min_length=1, ) @@ -1047,39 +1047,39 @@ class PowerForecastElement(BaseModel): class PEBCAllowedLimitRange(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) commodity_quantity: CommodityQuantity = Field( - ..., description="Type of power quantity this PEBC.AllowedLimitRange applies to" + ..., description='Type of power quantity this PEBC.AllowedLimitRange applies to' ) limit_type: PEBCPowerEnvelopeLimitType = Field( ..., - description="Indicates if this ranges applies to the upper limit or the lower limit", + description='Indicates if this ranges applies to the upper limit or the lower limit', ) range_boundary: NumberRange = Field( ..., - description="Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ", + description='Boundaries of the power range of this PEBC.AllowedLimitRange. The CEM is allowed to choose values within this range for the power envelope for the limit as described in limit_type. The start of the range shall be smaller or equal than the end of the range. ', ) abnormal_condition_only: bool = Field( ..., - description="Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition", + description='Indicates if this PEBC.AllowedLimitRange may only be used during an abnormal condition', ) class PEBCPowerEnvelope(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='Identifier of this PEBC.PowerEnvelope. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) commodity_quantity: CommodityQuantity = Field( - ..., description="Type of power quantity this PEBC.PowerEnvelope applies to" + ..., description='Type of power quantity this PEBC.PowerEnvelope applies to' ) power_envelope_elements: List[PEBCPowerEnvelopeElement] = Field( ..., - description="The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.", + description='The elements of this PEBC.PowerEnvelope. Shall contain at least one element. Elements must be placed in chronological order.', max_length=288, min_length=1, ) @@ -1087,14 +1087,14 @@ class PEBCPowerEnvelope(BaseModel): class PPBCPowerSequenceElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) duration: Duration = Field( - ..., description="Duration of the PPBC.PowerSequenceElement." + ..., description='Duration of the PPBC.PowerSequenceElement.' ) power_values: List[PowerForecastValue] = Field( ..., - description="The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.", + description='The value of power and deviations for the given duration. The array should contain at least one PowerForecastValue and at most one PowerForecastValue per CommodityQuantity.', max_length=10, min_length=1, ) @@ -1102,163 +1102,163 @@ class PPBCPowerSequenceElement(BaseModel): class PPBCPowerSequenceContainerStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) power_profile_id: ID = Field( ..., - description="ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ", + description='ID of the PPBC.PowerProfileDefinition of which the data element ‘sequence_container_id’ refers to. ', ) sequence_container_id: ID = Field( ..., - description="ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.", + description='ID of the PPBC.PowerSequenceContainer this PPBC.PowerSequenceContainerStatus provides information about.', ) selected_sequence_id: Optional[ID] = Field( None, - description="ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.", + description='ID of selected PPBC.PowerSequence. When no ID is given, no sequence was selected yet.', ) progress: Optional[Duration] = Field( None, - description="Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.", + description='Time that has passed since the selected sequence has started. A value must be provided, unless no sequence has been selected or the selected sequence hasn’t started yet.', ) status: PPBCPowerSequenceStatus = Field( - ..., description="Status of the selected PPBC.PowerSequence" + ..., description='Status of the selected PPBC.PowerSequence' ) class OMBCOperationMode(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the OBMC.OperationMode. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description of the OMBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', ) power_ranges: List[PowerRange] = Field( ..., - description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", + description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', max_length=10, min_length=1, ) running_costs: Optional[NumberRange] = Field( None, - description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", + description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails , excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', ) abnormal_condition_only: bool = Field( ..., - description="Indicates if this OMBC.OperationMode may only be used during an abnormal condition.", + description='Indicates if this OMBC.OperationMode may only be used during an abnormal condition.', ) class FRBCOperationModeElement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) fill_level_range: NumberRange = Field( ..., - description="The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.", + description='The range of the fill level for which this FRBC.OperationModeElement applies. The start of the NumberRange shall be smaller than the end of the NumberRange.', ) fill_rate: NumberRange = Field( ..., - description="Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ", + description='Indicates the change in fill_level per second. The lower_boundary of the NumberRange is associated with an operation_mode_factor of 0, the upper_boundary is associated with an operation_mode_factor of 1. ', ) power_ranges: List[PowerRange] = Field( ..., - description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", + description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', max_length=10, min_length=1, ) running_costs: Optional[NumberRange] = Field( None, - description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", + description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', ) class DDBCOperationMode(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) Id: ID = Field( ..., - description="ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.", + description='ID of this operation mode. Must be unique in the scope of the DDBC.ActuatorDescription in which it is used.', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description of the DDBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', ) power_ranges: List[PowerRange] = Field( ..., - description="The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.", + description='The power produced or consumed by this operation mode. The start of each PowerRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1. In the array there must be at least one PowerRange, and at most one PowerRange per CommodityQuantity.', max_length=10, min_length=1, ) supply_range: NumberRange = Field( ..., - description="The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.", + description='The supply rate this DDBC.OperationMode can deliver for the CEM to match the demand rate. The start of the NumberRange is associated with an operation_mode_factor of 0, the end is associated with an operation_mode_factor of 1.', ) running_costs: Optional[NumberRange] = Field( None, - description="Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.", + description='Additional costs per second (e.g. wear, services) associated with this operation mode in the currency defined by the ResourceManagerDetails, excluding the commodity cost. The range is expressing uncertainty and is not linked to the operation_mode_factor.', ) abnormal_condition_only: bool = Field( ..., - description="Indicates if this DDBC.OperationMode may only be used during an abnormal condition.", + description='Indicates if this DDBC.OperationMode may only be used during an abnormal condition.', ) class ResourceManagerDetails(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["ResourceManagerDetails"] = "ResourceManagerDetails" + message_type: Literal['ResourceManagerDetails'] = 'ResourceManagerDetails' message_id: ID resource_id: ID = Field( ..., - description="Identifier of the Resource Manager. Must be unique within the scope of the CEM.", + description='Identifier of the Resource Manager. Must be unique within the scope of the CEM.', ) - name: Optional[str] = Field(None, description="Human readable name given by user") + name: Optional[str] = Field(None, description='Human readable name given by user') roles: List[Role] = Field( ..., - description="Each Resource Manager provides one or more energy Roles", + description='Each Resource Manager provides one or more energy Roles', max_length=3, min_length=1, ) - manufacturer: Optional[str] = Field(None, description="Name of Manufacturer") + manufacturer: Optional[str] = Field(None, description='Name of Manufacturer') model: Optional[str] = Field( None, - description="Name of the model of the device (provided by the manufacturer)", + description='Name of the model of the device (provided by the manufacturer)', ) serial_number: Optional[str] = Field( - None, description="Serial number of the device (provided by the manufacturer)" + None, description='Serial number of the device (provided by the manufacturer)' ) firmware_version: Optional[str] = Field( None, - description="Version identifier of the firmware used in the device (provided by the manufacturer)", + description='Version identifier of the firmware used in the device (provided by the manufacturer)', ) instruction_processing_delay: Duration = Field( ..., - description="The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction", + description='The average time the combination of Resource Manager and HBES/BACS/SASS or (Smart) device needs to process and execute an instruction', ) available_control_types: List[ControlType] = Field( ..., - description="The control types supported by this Resource Manager.", + description='The control types supported by this Resource Manager.', max_length=5, min_length=1, ) currency: Optional[Currency] = Field( None, - description="Currency to be used for all information regarding costs. Mandatory if cost information is published.", + description='Currency to be used for all information regarding costs. Mandatory if cost information is published.', ) provides_forecast: bool = Field( ..., - description="Indicates whether the ResourceManager is able to provide PowerForecasts", + description='Indicates whether the ResourceManager is able to provide PowerForecasts', ) provides_power_measurement_types: List[CommodityQuantity] = Field( ..., - description="Array of all CommodityQuantities that this Resource Manager can provide measurements for. ", + description='Array of all CommodityQuantities that this Resource Manager can provide measurements for. ', max_length=10, min_length=1, ) @@ -1266,16 +1266,16 @@ class ResourceManagerDetails(BaseModel): class PowerMeasurement(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PowerMeasurement"] = "PowerMeasurement" + message_type: Literal['PowerMeasurement'] = 'PowerMeasurement' message_id: ID measurement_timestamp: AwareDatetime = Field( - ..., description="Timestamp when PowerValues were measured." + ..., description='Timestamp when PowerValues were measured.' ) values: List[PowerValue] = Field( ..., - description="Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).", + description='Array of measured PowerValues. Must contain at least one item and at most one item per ‘commodity_quantity’ (defined inside the PowerValue).', max_length=10, min_length=1, ) @@ -1283,16 +1283,16 @@ class PowerMeasurement(BaseModel): class PowerForecast(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PowerForecast"] = "PowerForecast" + message_type: Literal['PowerForecast'] = 'PowerForecast' message_id: ID start_time: AwareDatetime = Field( - ..., description="Start time of time period that is covered by the profile." + ..., description='Start time of time period that is covered by the profile.' ) elements: List[PowerForecastElement] = Field( ..., - description="Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.", + description='Elements of which this forecast consists. Contains at least one element. Elements must be placed in chronological order.', max_length=288, min_length=1, ) @@ -1300,27 +1300,27 @@ class PowerForecast(BaseModel): class PEBCPowerConstraints(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PEBC.PowerConstraints"] = "PEBC.PowerConstraints" + message_type: Literal['PEBC.PowerConstraints'] = 'PEBC.PowerConstraints' message_id: ID id: ID = Field( ..., - description="Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='Identifier of this PEBC.PowerConstraints. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) valid_from: AwareDatetime = Field( - ..., description="Moment this PEBC.PowerConstraints start to be valid" + ..., description='Moment this PEBC.PowerConstraints start to be valid' ) valid_until: Optional[AwareDatetime] = Field( None, - description="Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.", + description='Moment until this PEBC.PowerConstraints is valid. If valid_until is not present, there is no determined end time of this PEBC.PowerConstraints.', ) consequence_type: PEBCPowerEnvelopeConsequenceType = Field( - ..., description="Type of consequence of limiting power" + ..., description='Type of consequence of limiting power' ) allowed_limit_ranges: List[PEBCAllowedLimitRange] = Field( ..., - description="The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.", + description='The actual constraints. There shall be at least one PEBC.AllowedLimitRange for the UPPER_LIMIT and at least one AllowedLimitRange for the LOWER_LIMIT. It is allowed to have multiple PEBC.AllowedLimitRange objects with identical CommodityQuantities and LimitTypes.', max_length=100, min_length=2, ) @@ -1328,29 +1328,29 @@ class PEBCPowerConstraints(BaseModel): class PEBCInstruction(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PEBC.Instruction"] = "PEBC.Instruction" + message_type: Literal['PEBC.Instruction'] = 'PEBC.Instruction' message_id: ID id: ID = Field( ..., - description="Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='Identifier of this PEBC.Instruction. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) execution_time: AwareDatetime = Field( ..., - description="Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.", + description='Indicates the moment the execution of the instruction shall start. When the specified execution time is in the past, execution must start as soon as possible.', ) abnormal_condition: bool = Field( ..., - description="Indicates if this is an instruction during an abnormal condition.", + description='Indicates if this is an instruction during an abnormal condition.', ) power_constraints_id: ID = Field( ..., - description="Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.", + description='Identifier of the PEBC.PowerConstraints this PEBC.Instruction was based on.', ) power_envelopes: List[PEBCPowerEnvelope] = Field( ..., - description="The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.", + description='The PEBC.PowerEnvelope(s) that should be followed by the Resource Manager. There shall be at least one PEBC.PowerEnvelope, but at most one PEBC.PowerEnvelope for each CommodityQuantity.', max_length=10, min_length=1, ) @@ -1358,13 +1358,13 @@ class PEBCInstruction(BaseModel): class PPBCPowerProfileStatus(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PPBC.PowerProfileStatus"] = "PPBC.PowerProfileStatus" + message_type: Literal['PPBC.PowerProfileStatus'] = 'PPBC.PowerProfileStatus' message_id: ID sequence_container_status: List[PPBCPowerSequenceContainerStatus] = Field( ..., - description="Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.", + description='Array with status information for all PPBC.PowerSequenceContainers in the PPBC.PowerProfileDefinition.', max_length=1000, min_length=1, ) @@ -1372,29 +1372,29 @@ class PPBCPowerProfileStatus(BaseModel): class OMBCSystemDescription(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["OMBC.SystemDescription"] = "OMBC.SystemDescription" + message_type: Literal['OMBC.SystemDescription'] = 'OMBC.SystemDescription' message_id: ID valid_from: AwareDatetime = Field( ..., - description="Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", + description='Moment this OMBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', ) operation_modes: List[OMBCOperationMode] = Field( ..., - description="OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.", + description='OMBC.OperationModes available for the CEM in order to coordinate the device behaviour.', max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description="Possible transitions to switch from one OMBC.OperationMode to another.", + description='Possible transitions to switch from one OMBC.OperationMode to another.', max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description="Timers that control when certain transitions can be made.", + description='Timers that control when certain transitions can be made.', max_length=1000, min_length=0, ) @@ -1402,89 +1402,89 @@ class OMBCSystemDescription(BaseModel): class PPBCPowerSequence(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.", + description='ID of the PPBC.PowerSequence. Must be unique in the scope of the PPBC.PowerSequnceContainer in which it is used.', ) elements: List[PPBCPowerSequenceElement] = Field( ..., - description="List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.", + description='List of PPBC.PowerSequenceElements. Shall contain at least one element. Elements must be placed in chronological order.', max_length=288, min_length=1, ) is_interruptible: bool = Field( ..., - description="Indicates whether the option of pausing a sequence is available.", + description='Indicates whether the option of pausing a sequence is available.', ) max_pause_before: Optional[Duration] = Field( None, - description="The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one", + description='The maximum duration for which a device can be paused between the end of the previous running sequence and the start of this one', ) abnormal_condition_only: bool = Field( ..., - description="Indicates if this PPBC.PowerSequence may only be used during an abnormal condition", + description='Indicates if this PPBC.PowerSequence may only be used during an abnormal condition', ) class FRBCOperationMode(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.", + description='ID of the FRBC.OperationMode. Must be unique in the scope of the FRBC.ActuatorDescription in which it is used.', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description of the FRBC.OperationMode. This element is only intended for diagnostic purposes and not for HMI applications.', ) elements: List[FRBCOperationModeElement] = Field( ..., - description="List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.", + description='List of FRBC.OperationModeElements, which describe the properties of this FRBC.OperationMode depending on the fill_level. The fill_level_ranges of the items in the Array must be contiguous.', max_length=100, min_length=1, ) abnormal_condition_only: bool = Field( ..., - description="Indicates if this FRBC.OperationMode may only be used during an abnormal condition", + description='Indicates if this FRBC.OperationMode may only be used during an abnormal condition', ) class DDBCActuatorDescription(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of this DDBC.ActuatorDescription. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description of the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', ) supported_commodites: List[Commodity] = Field( ..., - description="Commodities supported by the operation modes of this actuator. There shall be at least one commodity", + description='Commodities supported by the operation modes of this actuator. There shall be at least one commodity', max_length=4, min_length=1, ) operation_modes: List[DDBCOperationMode] = Field( ..., - description="List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.", + description='List of all Operation Modes that are available for this actuator. There shall be at least one DDBC.OperationMode.', max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description="List of Transitions between Operation Modes. Shall contain at least one Transition.", + description='List of Transitions between Operation Modes. Shall contain at least one Transition.', max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description="List of Timers associated with Transitions for this Actuator. Can be empty.", + description='List of Timers associated with Transitions for this Actuator. Can be empty.', max_length=1000, min_length=0, ) @@ -1492,40 +1492,40 @@ class DDBCActuatorDescription(BaseModel): class DDBCSystemDescription(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["DDBC.SystemDescription"] = "DDBC.SystemDescription" + message_type: Literal['DDBC.SystemDescription'] = 'DDBC.SystemDescription' message_id: ID valid_from: AwareDatetime = Field( ..., - description="Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", + description='Moment this DDBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', ) actuators: List[DDBCActuatorDescription] = Field( ..., - description="List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.", + description='List of all available actuators in the system. Must contain at least one DDBC.ActuatorAggregated.', max_length=10, min_length=1, ) present_demand_rate: NumberRange = Field( - ..., description="Present demand rate that needs to be satisfied by the system" + ..., description='Present demand rate that needs to be satisfied by the system' ) provides_average_demand_rate_forecast: bool = Field( ..., - description="Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.", + description='Indicates whether the Resource Manager could provide a demand rate forecast through the DDBC.AverageDemandRateForecast.', ) class PPBCPowerSequenceContainer(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.", + description='ID of the PPBC.PowerSequenceContainer. Must be unique in the scope of the PPBC.PowerProfileDefinition in which it is used.', ) power_sequences: List[PPBCPowerSequence] = Field( ..., - description="List of alternative Sequences where one could be chosen by the CEM", + description='List of alternative Sequences where one could be chosen by the CEM', max_length=288, min_length=1, ) @@ -1533,37 +1533,37 @@ class PPBCPowerSequenceContainer(BaseModel): class FRBCActuatorDescription(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) id: ID = Field( ..., - description="ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the Actuator. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) diagnostic_label: Optional[str] = Field( None, - description="Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.", + description='Human readable name/description for the actuator. This element is only intended for diagnostic purposes and not for HMI applications.', ) supported_commodities: List[Commodity] = Field( ..., - description="List of all supported Commodities.", + description='List of all supported Commodities.', max_length=4, min_length=1, ) operation_modes: List[FRBCOperationMode] = Field( ..., - description="Provided FRBC.OperationModes associated with this actuator", + description='Provided FRBC.OperationModes associated with this actuator', max_length=100, min_length=1, ) transitions: List[Transition] = Field( ..., - description="Possible transitions between FRBC.OperationModes associated with this actuator.", + description='Possible transitions between FRBC.OperationModes associated with this actuator.', max_length=1000, min_length=0, ) timers: List[Timer] = Field( ..., - description="List of Timers associated with this actuator", + description='List of Timers associated with this actuator', max_length=1000, min_length=0, ) @@ -1571,25 +1571,25 @@ class FRBCActuatorDescription(BaseModel): class PPBCPowerProfileDefinition(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["PPBC.PowerProfileDefinition"] = "PPBC.PowerProfileDefinition" + message_type: Literal['PPBC.PowerProfileDefinition'] = 'PPBC.PowerProfileDefinition' message_id: ID id: ID = Field( ..., - description="ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.", + description='ID of the PPBC.PowerProfileDefinition. Must be unique in the scope of the Resource Manager, for at least the duration of the session between Resource Manager and CEM.', ) start_time: AwareDatetime = Field( ..., - description="Indicates the first possible time the first PPBC.PowerSequence could start", + description='Indicates the first possible time the first PPBC.PowerSequence could start', ) end_time: AwareDatetime = Field( ..., - description="Indicates when the last PPBC.PowerSequence shall be finished at the latest", + description='Indicates when the last PPBC.PowerSequence shall be finished at the latest', ) power_sequences_containers: List[PPBCPowerSequenceContainer] = Field( ..., - description="The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.", + description='The PPBC.PowerSequenceContainers that make up this PPBC.PowerProfileDefinition. There shall be at least one PPBC.PowerSequenceContainer that includes at least one PPBC.PowerSequence. PPBC.PowerSequenceContainers must be placed in chronological order.', max_length=1000, min_length=1, ) @@ -1597,15 +1597,15 @@ class PPBCPowerProfileDefinition(BaseModel): class FRBCSystemDescription(BaseModel): model_config = ConfigDict( - extra="forbid", + extra='forbid', ) - message_type: Literal["FRBC.SystemDescription"] = "FRBC.SystemDescription" + message_type: Literal['FRBC.SystemDescription'] = 'FRBC.SystemDescription' message_id: ID valid_from: AwareDatetime = Field( ..., - description="Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.", + description='Moment this FRBC.SystemDescription starts to be valid. If the system description is immediately valid, the DateTimeStamp should be now or in the past.', ) actuators: List[FRBCActuatorDescription] = Field( - ..., description="Details of all Actuators.", max_length=10, min_length=1 + ..., description='Details of all Actuators.', max_length=10, min_length=1 ) - storage: FRBCStorageDescription = Field(..., description="Details of the storage.") + storage: FRBCStorageDescription = Field(..., description='Details of the storage.') From 236b154a2a864326b7addbbc3e4043d7546c6e4c Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 15:14:58 +0200 Subject: [PATCH 16/27] Reverted development_utilities/gen_unit_test_template.py to put it on a different PR --- .../gen_unit_test_template.py | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/development_utilities/gen_unit_test_template.py b/development_utilities/gen_unit_test_template.py index 9d91d49..93e2b03 100644 --- a/development_utilities/gen_unit_test_template.py +++ b/development_utilities/gen_unit_test_template.py @@ -17,7 +17,6 @@ import uuid import pydantic -from pydantic.types import AwareDatetime from s2python import frbc from s2python.common import Duration, PowerRange, NumberRange @@ -65,7 +64,7 @@ def get_list_arg(field_type): def is_enum(field_type): - return inspect.isclass(field_type) and issubclass(field_type, Enum) + return issubclass(field_type, Enum) def snake_case(camelcased: str) -> str: @@ -112,17 +111,6 @@ def generate_json_test_data_for_field(field_type: Type): value = bool(random.randint(0, 1)) elif field_type is float: value = random.random() * 9000.0 - elif field_type is AwareDatetime: - # Generate a timezone-aware datetime - value = datetime.datetime( - year=random.randint(2020, 2023), - month=random.randint(1, 12), - day=random.randint(1, 28), - hour=random.randint(0, 23), - minute=random.randint(0, 59), - second=random.randint(0, 59), - tzinfo=datetime.timezone(datetime.timedelta(hours=random.randint(-12, 14))), - ) elif field_type is datetime.datetime: value = datetime.datetime( year=random.randint(2020, 2023), @@ -179,21 +167,13 @@ def dump_test_data_as_constructor_field_for(test_data, field_type: Type) -> str: value = str(test_data) elif field_type is float: value = str(test_data) - elif field_type is AwareDatetime or field_type is datetime.datetime: + elif field_type is datetime.datetime: test_data: datetime.datetime offset: datetime.timedelta = test_data.tzinfo.utcoffset(None) - value = ( - f"datetime(" - f"year={test_data.year}, month={test_data.month}, day={test_data.day}, " - f"hour={test_data.hour}, minute={test_data.minute}, second={test_data.second}, " - f"tzinfo=offset(offset=timedelta(seconds={offset.total_seconds()})))" - ) + value = f"datetime(year={test_data.year}, month={test_data.month}, day={test_data.day}, hour={test_data.hour}, minute={test_data.minute}, second={test_data.second}, tzinfo=offset(offset=timedelta(seconds={offset.total_seconds()})))" elif field_type is uuid.UUID: value = f'uuid.UUID("{test_data}")' - elif type(field_type).__name__ == "_LiteralGenericAlias": - value = field_type.__args__[0] else: - breakpoint() raise RuntimeError( f"Please implement dump test data for field type {field_type}" ) @@ -237,13 +217,11 @@ def dump_test_data_as_json_field_for(test_data, field_type: Type): value = test_data elif field_type is float: value = test_data - elif field_type is AwareDatetime or field_type is datetime.datetime: + elif field_type is datetime.datetime: test_data: datetime.datetime value = test_data.isoformat() elif field_type is uuid.UUID: value = str(test_data) - elif type(field_type).__name__ == "_LiteralGenericAlias": - value = test_data else: raise RuntimeError( f"Please implement dump test data to json for field type {field_type}" From fbfdd999d1c2d60d7d3d21f21c86673ad6f73343 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 15:16:03 +0200 Subject: [PATCH 17/27] Reverted the frbc unit tests to put them on a different PR --- .../frbc/frbc_actuator_description_test.py | 325 ++++++++++----- tests/unit/frbc/frbc_actuator_status_test.py | 84 +++- ..._fill_level_target_profile_element_test.py | 45 ++- .../frbc_fill_level_target_profile_test.py | 91 ++++- tests/unit/frbc/frbc_instruction_test.py | 88 ++-- .../frbc_leakage_behaviour_element_test.py | 42 +- .../unit/frbc/frbc_leakage_behaviour_test.py | 91 ++++- .../frbc/frbc_operation_mode_element_test.py | 105 +++-- tests/unit/frbc/frbc_operation_mode_test.py | 123 ++++-- .../frbc/frbc_storage_description_test.py | 65 ++- tests/unit/frbc/frbc_storage_status_test.py | 28 +- .../unit/frbc/frbc_system_description_test.py | 380 ++++++++++++++---- tests/unit/frbc/frbc_timer_status_test.py | 67 ++- .../frbc/frbc_usage_forecast_element_test.py | 79 ++-- tests/unit/frbc/frbc_usage_forecast_test.py | 115 ++++-- 15 files changed, 1272 insertions(+), 456 deletions(-) diff --git a/tests/unit/frbc/frbc_actuator_description_test.py b/tests/unit/frbc/frbc_actuator_description_test.py index 6e40960..1b4f31d 100644 --- a/tests/unit/frbc/frbc_actuator_description_test.py +++ b/tests/unit/frbc/frbc_actuator_description_test.py @@ -1,122 +1,241 @@ - -from datetime import timedelta, datetime, timezone as offset import json -from unittest import TestCase import uuid +from datetime import timedelta +from unittest import TestCase -from s2python.common import * -from s2python.frbc import * +from s2python.common import ( + Transition, + Duration, + Timer, + NumberRange, + PowerRange, + CommodityQuantity, + Commodity, +) +from s2python.frbc import ( + FRBCActuatorDescription, + FRBCOperationMode, + FRBCOperationModeElement, +) class FRBCActuatorDescriptionTest(TestCase): - def test__from_json__happy_path_full(self): + def test__from_json__happy_path(self): # Arrange - json_str = """ -{ - "id": "d94c8444-fecd-4e2b-a753-3ba95c80a8b9", - "diagnostic_label": "some-test-string7020", - "supported_commodities": [ - "GAS" - ], - "operation_modes": [ - { - "id": "0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6", - "diagnostic_label": "some-test-string5647", - "elements": [ - { - "fill_level_range": { - "start_of_range": 29629.998638216148, - "end_of_range": 47173.64237787903 - }, - "fill_rate": { - "start_of_range": 6928.475192465882, - "end_of_range": 27602.436966784364 - }, - "power_ranges": [ - { - "start_of_range": 4601.097096109952, - "end_of_range": 40109.78028113443, - "commodity_quantity": "ELECTRIC.POWER.L1" - } - ], - "running_costs": { - "start_of_range": 32340.91070921078, - "end_of_range": 68671.08362229214 - } - } - ], - "abnormal_condition_only": false - } - ], - "transitions": [ - { - "id": "ba747269-ce4b-405c-bc8a-38cba20342a7", - "from_": "c8b76640-8eb1-4656-9477-2bc2afdca867", - "to": "e53e871e-b09e-4a81-9a6d-4b317b30ee04", - "start_timers": [ - "fce4d143-a7d4-4c53-a66c-da2faec9cc61" - ], - "blocking_timers": [ - "c33dc9cd-b82f-4649-b610-a70f3787facd" - ], - "transition_costs": 4575.2975690198045, - "transition_duration": 10950, - "abnormal_condition_only": false - } - ], - "timers": [ - { - "id": "033d2a04-a874-4537-a475-ecc5fc538282", - "diagnostic_label": "some-test-string380", - "duration": 28910 - } - ] -} - """ + json_str = """{ + "diagnostic_label": "some name of actuator", + "id": "3bdec96b-be3b-4ba9-afa0-c4a0632dded5", + "operation_modes": [{ + "abnormal_condition_only": false, + "diagnostic_label": "om1", + "id": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", + "elements": [{ "fill_level_range": {"start_of_range": 4.0, "end_of_range": 5.0}, + "fill_rate": {"start_of_range": 0.13, "end_of_range": 10342.569}, + "power_ranges": [{"start_of_range": 400, "end_of_range": 6000, "commodity_quantity": "HEAT.TEMPERATURE"}, + {"start_of_range": 500, "end_of_range": 7000, "commodity_quantity": "ELECTRIC.POWER.L1"}], + "running_costs": {"start_of_range": 4.3, "end_of_range": 4.6}}] + }], + "supported_commodities": ["HEAT", "ELECTRICITY"], + "timers": [{ + "diagnostic_label": "timer1", + "duration": 2300, + "id": "3bdec10b-be3b-4ba9-afa0-c4a0632ffed6" + }], + "transitions": [{ "id": "2bdec96b-be3b-4ba9-afa0-c4a0632cced3", + "from": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", + "to": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", + "start_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], + "blocking_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], + "transition_costs": 4.3, + "transition_duration": 1500, + "abnormal_condition_only": false}] + }""" # Act - frbc_actuator_description = FRBCActuatorDescription.from_json(json_str) + frbc_actuator_description: FRBCActuatorDescription = ( + FRBCActuatorDescription.from_json(json_str) + ) # Assert - self.assertEqual(frbc_actuator_description.id, uuid.UUID("d94c8444-fecd-4e2b-a753-3ba95c80a8b9")) - self.assertEqual(frbc_actuator_description.diagnostic_label, "some-test-string7020") - self.assertEqual(frbc_actuator_description.supported_commodities, [Commodity.GAS]) - self.assertEqual(frbc_actuator_description.operation_modes, [FRBCOperationMode(id=uuid.UUID("0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6"), diagnostic_label="some-test-string5647", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29629.998638216148, end_of_range=47173.64237787903), fill_rate=NumberRange(start_of_range=6928.475192465882, end_of_range=27602.436966784364), power_ranges=[PowerRange(start_of_range=4601.097096109952, end_of_range=40109.78028113443, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=32340.91070921078, end_of_range=68671.08362229214))], abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.transitions, [Transition(id=uuid.UUID("ba747269-ce4b-405c-bc8a-38cba20342a7"), from_=uuid.UUID("c8b76640-8eb1-4656-9477-2bc2afdca867"), to=uuid.UUID("e53e871e-b09e-4a81-9a6d-4b317b30ee04"), start_timers=[uuid.UUID("fce4d143-a7d4-4c53-a66c-da2faec9cc61")], blocking_timers=[uuid.UUID("c33dc9cd-b82f-4649-b610-a70f3787facd")], transition_costs=4575.2975690198045, transition_duration=Duration.from_timedelta(timedelta(milliseconds=10950)), abnormal_condition_only=False)]) - self.assertEqual(frbc_actuator_description.timers, [Timer(id=uuid.UUID("033d2a04-a874-4537-a475-ecc5fc538282"), diagnostic_label="some-test-string380", duration=Duration.from_timedelta(timedelta(milliseconds=28910)))]) + expected_timer = Timer( + id=uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"), + diagnostic_label="timer1", + duration=Duration.from_timedelta(timedelta(seconds=2.3)), + ) + + # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. + # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to + # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 + expected_transition = Transition( + **{ + "id": uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3"), + "from": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), + "to": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), + "start_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], + "blocking_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], + "transition_costs": 4.3, + "transition_duration": Duration.from_milliseconds(1500), + "abnormal_condition_only": False, + } + ) + expected_operation_mode_element = FRBCOperationModeElement( + fill_level_range=NumberRange(start_of_range=4.0, end_of_range=5.0), + fill_rate=NumberRange(start_of_range=0.13, end_of_range=10342.569), + power_ranges=[ + PowerRange( + start_of_range=400, + end_of_range=6000, + commodity_quantity=CommodityQuantity.HEAT_TEMPERATURE, + ), + PowerRange( + start_of_range=500, + end_of_range=7000, + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + ), + ], + running_costs=NumberRange(start_of_range=4.3, end_of_range=4.6), + ) + expected_operation_mode = FRBCOperationMode( + abnormal_condition_only=False, + diagnostic_label="om1", + id=uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), + elements=[expected_operation_mode_element], + ) + + self.assertEqual( + frbc_actuator_description.diagnostic_label, "some name of actuator" + ) + self.assertEqual( + frbc_actuator_description.id, + uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632dded5"), + ) + self.assertEqual( + frbc_actuator_description.supported_commodities, + [Commodity.HEAT, Commodity.ELECTRICITY], + ) + self.assertEqual( + frbc_actuator_description.operation_modes, [expected_operation_mode] + ) + self.assertEqual(frbc_actuator_description.timers, [expected_timer]) + self.assertEqual(frbc_actuator_description.transitions, [expected_transition]) - def test__to_json__happy_path_full(self): + def test__to_json__happy_path(self): # Arrange - frbc_actuator_description = FRBCActuatorDescription(id=uuid.UUID("d94c8444-fecd-4e2b-a753-3ba95c80a8b9"), diagnostic_label="some-test-string7020", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6"), diagnostic_label="some-test-string5647", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29629.998638216148, end_of_range=47173.64237787903), fill_rate=NumberRange(start_of_range=6928.475192465882, end_of_range=27602.436966784364), power_ranges=[PowerRange(start_of_range=4601.097096109952, end_of_range=40109.78028113443, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=32340.91070921078, end_of_range=68671.08362229214))], abnormal_condition_only=False)], transitions=[Transition(id=uuid.UUID("ba747269-ce4b-405c-bc8a-38cba20342a7"), from_=uuid.UUID("c8b76640-8eb1-4656-9477-2bc2afdca867"), to=uuid.UUID("e53e871e-b09e-4a81-9a6d-4b317b30ee04"), start_timers=[uuid.UUID("fce4d143-a7d4-4c53-a66c-da2faec9cc61")], blocking_timers=[uuid.UUID("c33dc9cd-b82f-4649-b610-a70f3787facd")], transition_costs=4575.2975690198045, transition_duration=Duration.from_timedelta(timedelta(milliseconds=10950)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("033d2a04-a874-4537-a475-ecc5fc538282"), diagnostic_label="some-test-string380", duration=Duration.from_timedelta(timedelta(milliseconds=28910)))]) + timer = Timer( + id=uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"), + diagnostic_label="timer1", + duration=Duration.from_timedelta(timedelta(seconds=2.3)), + ) + + # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. + # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to + # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 + transition = Transition( + **{ + "id": uuid.UUID("2bdec96b-be3b-4ba9-afa0-c4a0632cced3"), + "from": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), + "to": uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), + "start_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], + "blocking_timers": [uuid.UUID("3bdec10b-be3b-4ba9-afa0-c4a0632ffed6")], + "transition_costs": 4.3, + "transition_duration": Duration.from_milliseconds(1500), + "abnormal_condition_only": False, + } + ) + operation_mode_element = FRBCOperationModeElement( + fill_level_range=NumberRange(start_of_range=4.0, end_of_range=5.0), + fill_rate=NumberRange(start_of_range=0.13, end_of_range=10342.569), + power_ranges=[ + PowerRange( + start_of_range=400, + end_of_range=6000, + commodity_quantity=CommodityQuantity.HEAT_TEMPERATURE, + ), + PowerRange( + start_of_range=500, + end_of_range=7000, + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + ), + ], + running_costs=NumberRange(start_of_range=4.3, end_of_range=4.6), + ) + operation_mode = FRBCOperationMode( + abnormal_condition_only=False, + diagnostic_label="om1", + id=uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632ffed5"), + elements=[operation_mode_element], + ) + + frbc_actuator_description = FRBCActuatorDescription( + diagnostic_label="some name of actuator", + id=uuid.UUID("3bdec96b-be3b-4ba9-afa0-c4a0632dded5"), + supported_commodities=[Commodity.HEAT, Commodity.ELECTRICITY], + operation_modes=[operation_mode], + timers=[timer], + transitions=[transition], + ) # Act json_str = frbc_actuator_description.to_json() # Assert - expected_json = { 'diagnostic_label': 'some-test-string7020', - 'id': 'd94c8444-fecd-4e2b-a753-3ba95c80a8b9', - 'operation_modes': [ { 'abnormal_condition_only': False, - 'diagnostic_label': 'some-test-string5647', - 'elements': [ { 'fill_level_range': { 'end_of_range': 47173.64237787903, - 'start_of_range': 29629.998638216148}, - 'fill_rate': { 'end_of_range': 27602.436966784364, - 'start_of_range': 6928.475192465882}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 40109.78028113443, - 'start_of_range': 4601.097096109952}], - 'running_costs': { 'end_of_range': 68671.08362229214, - 'start_of_range': 32340.91070921078}}], - 'id': '0896dcbb-74d8-4875-8d46-3e2ffe0d6fc6'}], - 'supported_commodities': ['GAS'], - 'timers': [ { 'diagnostic_label': 'some-test-string380', - 'duration': 28910, - 'id': '033d2a04-a874-4537-a475-ecc5fc538282'}], - 'transitions': [ { 'abnormal_condition_only': False, - 'blocking_timers': [ 'c33dc9cd-b82f-4649-b610-a70f3787facd'], - 'from_': 'c8b76640-8eb1-4656-9477-2bc2afdca867', - 'id': 'ba747269-ce4b-405c-bc8a-38cba20342a7', - 'start_timers': [ 'fce4d143-a7d4-4c53-a66c-da2faec9cc61'], - 'to': 'e53e871e-b09e-4a81-9a6d-4b317b30ee04', - 'transition_costs': 4575.2975690198045, - 'transition_duration': 10950}]} + expected_json = { + "diagnostic_label": "some name of actuator", + "id": "3bdec96b-be3b-4ba9-afa0-c4a0632dded5", + "operation_modes": [ + { + "abnormal_condition_only": False, + "diagnostic_label": "om1", + "id": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", + "elements": [ + { + "fill_level_range": { + "start_of_range": 4.0, + "end_of_range": 5.0, + }, + "fill_rate": { + "start_of_range": 0.13, + "end_of_range": 10342.569, + }, + "power_ranges": [ + { + "start_of_range": 400, + "end_of_range": 6000, + "commodity_quantity": "HEAT.TEMPERATURE", + }, + { + "start_of_range": 500, + "end_of_range": 7000, + "commodity_quantity": "ELECTRIC.POWER.L1", + }, + ], + "running_costs": { + "start_of_range": 4.3, + "end_of_range": 4.6, + }, + } + ], + } + ], + "supported_commodities": ["HEAT", "ELECTRICITY"], + "timers": [ + { + "diagnostic_label": "timer1", + "duration": 2300, + "id": "3bdec10b-be3b-4ba9-afa0-c4a0632ffed6", + } + ], + "transitions": [ + { + "id": "2bdec96b-be3b-4ba9-afa0-c4a0632cced3", + "from": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", + "to": "3bdec96b-be3b-4ba9-afa0-c4a0632ffed5", + "start_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], + "blocking_timers": ["3bdec10b-be3b-4ba9-afa0-c4a0632ffed6"], + "transition_costs": 4.3, + "transition_duration": 1500, + "abnormal_condition_only": False, + } + ], + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_actuator_status_test.py b/tests/unit/frbc/frbc_actuator_status_test.py index ac47d8e..b3bb5c8 100644 --- a/tests/unit/frbc/frbc_actuator_status_test.py +++ b/tests/unit/frbc/frbc_actuator_status_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,13 +12,13 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "active_operation_mode_id": "395dcbc5-5c7f-415e-8727-e48fc53761bc", + "actuator_id": "1cee425e-861b-417a-8208-bb6d53aafb00", + "message_id": "07f3d559-63c5-4369-a9e0-deed4195f651", "message_type": "FRBC.ActuatorStatus", - "message_id": "4dbb1dee-cda6-46d8-92f7-98c1f0cd4157", - "actuator_id": "0b6552ac-7af4-4627-87da-30fef76d22b7", - "active_operation_mode_id": "b1ff587d-8257-4644-8adf-7e0a7787863e", - "operation_mode_factor": 674.4963166817159, - "previous_operation_mode_id": "7b2553e7-ae6c-4ce4-8059-3712e33b0648", - "transition_timestamp": "2022-11-24T09:56:49+08:00" + "operation_mode_factor": 6919.960475850124, + "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", + "transition_timestamp": "2020-01-02T07:56:46Z" } """ @@ -27,27 +26,68 @@ def test__from_json__happy_path_full(self): frbc_actuator_status = FRBCActuatorStatus.from_json(json_str) # Assert - self.assertEqual(frbc_actuator_status.message_type, FRBC.ActuatorStatus) - self.assertEqual(frbc_actuator_status.message_id, uuid.UUID("4dbb1dee-cda6-46d8-92f7-98c1f0cd4157")) - self.assertEqual(frbc_actuator_status.actuator_id, uuid.UUID("0b6552ac-7af4-4627-87da-30fef76d22b7")) - self.assertEqual(frbc_actuator_status.active_operation_mode_id, uuid.UUID("b1ff587d-8257-4644-8adf-7e0a7787863e")) - self.assertEqual(frbc_actuator_status.operation_mode_factor, 674.4963166817159) - self.assertEqual(frbc_actuator_status.previous_operation_mode_id, uuid.UUID("7b2553e7-ae6c-4ce4-8059-3712e33b0648")) - self.assertEqual(frbc_actuator_status.transition_timestamp, datetime(year=2022, month=11, day=24, hour=9, minute=56, second=49, tzinfo=offset(offset=timedelta(seconds=28800.0)))) + self.assertEqual( + frbc_actuator_status.active_operation_mode_id, + uuid.UUID("395dcbc5-5c7f-415e-8727-e48fc53761bc"), + ) + self.assertEqual( + frbc_actuator_status.actuator_id, + uuid.UUID("1cee425e-861b-417a-8208-bb6d53aafb00"), + ) + self.assertEqual( + frbc_actuator_status.message_id, + uuid.UUID("07f3d559-63c5-4369-a9e0-deed4195f651"), + ) + self.assertEqual(frbc_actuator_status.message_type, "FRBC.ActuatorStatus") + self.assertEqual(frbc_actuator_status.operation_mode_factor, 6919.960475850124) + self.assertEqual( + frbc_actuator_status.previous_operation_mode_id, + uuid.UUID("2ed8f7de-cbaa-4cab-9d25-6792317aa284"), + ) + self.assertEqual( + frbc_actuator_status.transition_timestamp, + datetime( + year=2020, + month=1, + day=2, + hour=7, + minute=56, + second=46, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_actuator_status = FRBCActuatorStatus(message_type=FRBC.ActuatorStatus, message_id=uuid.UUID("4dbb1dee-cda6-46d8-92f7-98c1f0cd4157"), actuator_id=uuid.UUID("0b6552ac-7af4-4627-87da-30fef76d22b7"), active_operation_mode_id=uuid.UUID("b1ff587d-8257-4644-8adf-7e0a7787863e"), operation_mode_factor=674.4963166817159, previous_operation_mode_id=uuid.UUID("7b2553e7-ae6c-4ce4-8059-3712e33b0648"), transition_timestamp=datetime(year=2022, month=11, day=24, hour=9, minute=56, second=49, tzinfo=offset(offset=timedelta(seconds=28800.0)))) + frbc_actuator_status = FRBCActuatorStatus( + active_operation_mode_id=uuid.UUID("395dcbc5-5c7f-415e-8727-e48fc53761bc"), + actuator_id=uuid.UUID("1cee425e-861b-417a-8208-bb6d53aafb00"), + 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"), + transition_timestamp=datetime( + year=2020, + month=1, + day=2, + hour=7, + minute=56, + second=46, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) # Act json_str = frbc_actuator_status.to_json() # Assert - expected_json = { 'active_operation_mode_id': 'b1ff587d-8257-4644-8adf-7e0a7787863e', - 'actuator_id': '0b6552ac-7af4-4627-87da-30fef76d22b7', - 'message_id': '4dbb1dee-cda6-46d8-92f7-98c1f0cd4157', - 'message_type': 'FRBC.ActuatorStatus', - 'operation_mode_factor': 674.4963166817159, - 'previous_operation_mode_id': '7b2553e7-ae6c-4ce4-8059-3712e33b0648', - 'transition_timestamp': '2022-11-24T09:56:49+08:00'} + expected_json = { + "active_operation_mode_id": "395dcbc5-5c7f-415e-8727-e48fc53761bc", + "actuator_id": "1cee425e-861b-417a-8208-bb6d53aafb00", + "message_id": "07f3d559-63c5-4369-a9e0-deed4195f651", + "message_type": "FRBC.ActuatorStatus", + "operation_mode_factor": 6919.960475850124, + "previous_operation_mode_id": "2ed8f7de-cbaa-4cab-9d25-6792317aa284", + "transition_timestamp": "2020-01-02T07:56:46Z", + } self.assertEqual(json.loads(json_str), expected_json) 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 678e9de..62f51df 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 @@ -1,11 +1,10 @@ - -from datetime import timedelta, datetime, timezone as offset +from datetime import timedelta import json from unittest import TestCase -import uuid from s2python.common import * from s2python.frbc import * +from s2python.s2_validation_error import S2ValidationError class FRBCFillLevelTargetProfileElementTest(TestCase): @@ -13,30 +12,50 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "duration": 20277, + "duration": 12950, "fill_level_range": { - "start_of_range": 32523.727089572538, - "end_of_range": 51457.69540766515 + "end_of_range": 8176, + "start_of_range": 6207 } } """ # Act - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json(json_str) + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement.from_json( + json_str + ) # Assert - self.assertEqual(frbc_fill_level_target_profile_element.duration, Duration.from_timedelta(timedelta(milliseconds=20277))) - self.assertEqual(frbc_fill_level_target_profile_element.fill_level_range, NumberRange(start_of_range=32523.727089572538, end_of_range=51457.69540766515)) + self.assertEqual( + frbc_fill_level_target_profile_element.duration, + Duration.from_timedelta(timedelta(milliseconds=12950)), + ) + self.assertEqual( + frbc_fill_level_target_profile_element.fill_level_range, + NumberRange(end_of_range=8176.0, start_of_range=6207.0), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=20277)), fill_level_range=NumberRange(start_of_range=32523.727089572538, end_of_range=51457.69540766515)) + frbc_fill_level_target_profile_element = FRBCFillLevelTargetProfileElement( + duration=Duration.from_timedelta(timedelta(milliseconds=12950)), + fill_level_range=NumberRange(end_of_range=8176, start_of_range=6207), + ) # Act json_str = frbc_fill_level_target_profile_element.to_json() # Assert - expected_json = { 'duration': 20277, - 'fill_level_range': { 'end_of_range': 51457.69540766515, - 'start_of_range': 32523.727089572538}} + expected_json = { + "duration": 12950, + "fill_level_range": {"end_of_range": 8176, "start_of_range": 6207}, + } self.assertEqual(json.loads(json_str), expected_json) + + def test__init__fill_level_range_end_is_smaller_than_start(self): + # Arrange / Act / Assert + with self.assertRaises(S2ValidationError): + FRBCFillLevelTargetProfileElement( + duration=Duration.from_timedelta(timedelta(milliseconds=12950)), + fill_level_range=NumberRange(end_of_range=6000, start_of_range=8176), + ) 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 8f47374..046a0bc 100644 --- a/tests/unit/frbc/frbc_fill_level_target_profile_test.py +++ b/tests/unit/frbc/frbc_fill_level_target_profile_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,18 +12,18 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "message_type": "FRBC.FillLevelTargetProfile", - "message_id": "8df9b5ac-de60-402c-9400-a4dd731fbd99", - "start_time": "2023-02-12T11:42:28+11:00", "elements": [ { - "duration": 26527, + "duration": 4704, "fill_level_range": { - "start_of_range": 4668.6876987963915, - "end_of_range": 25463.75618862868 + "end_of_range": 10800.98606857073545, + "start_of_range": 6891.19014440217 } } - ] + ], + "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", + "message_type": "FRBC.FillLevelTargetProfile", + "start_time": "2021-04-17T00:19:20Z" } """ @@ -32,23 +31,77 @@ def test__from_json__happy_path_full(self): frbc_fill_level_target_profile = FRBCFillLevelTargetProfile.from_json(json_str) # Assert - self.assertEqual(frbc_fill_level_target_profile.message_type, FRBC.FillLevelTargetProfile) - self.assertEqual(frbc_fill_level_target_profile.message_id, uuid.UUID("8df9b5ac-de60-402c-9400-a4dd731fbd99")) - self.assertEqual(frbc_fill_level_target_profile.start_time, datetime(year=2023, month=2, day=12, hour=11, minute=42, second=28, tzinfo=offset(offset=timedelta(seconds=39600.0)))) - self.assertEqual(frbc_fill_level_target_profile.elements, [FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=26527)), fill_level_range=NumberRange(start_of_range=4668.6876987963915, end_of_range=25463.75618862868))]) + self.assertEqual( + frbc_fill_level_target_profile.elements, + [ + FRBCFillLevelTargetProfileElement( + duration=Duration.from_timedelta(timedelta(milliseconds=4704)), + fill_level_range=NumberRange( + end_of_range=10800.98606857073545, + start_of_range=6891.19014440217, + ), + ) + ], + ) + self.assertEqual( + 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.start_time, + datetime( + year=2021, + month=4, + day=17, + hour=0, + minute=19, + second=20, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_fill_level_target_profile = FRBCFillLevelTargetProfile(message_type=FRBC.FillLevelTargetProfile, message_id=uuid.UUID("8df9b5ac-de60-402c-9400-a4dd731fbd99"), start_time=datetime(year=2023, month=2, day=12, hour=11, minute=42, second=28, tzinfo=offset(offset=timedelta(seconds=39600.0))), elements=[FRBCFillLevelTargetProfileElement(duration=Duration.from_timedelta(timedelta(milliseconds=26527)), fill_level_range=NumberRange(start_of_range=4668.6876987963915, end_of_range=25463.75618862868))]) + frbc_fill_level_target_profile = FRBCFillLevelTargetProfile( + elements=[ + FRBCFillLevelTargetProfileElement( + duration=Duration.from_timedelta(timedelta(milliseconds=4704)), + fill_level_range=NumberRange( + end_of_range=10800.98606857073545, + start_of_range=6891.19014440217, + ), + ) + ], + message_id=uuid.UUID("04a6c8af-ca8d-420c-9c11-e96a70fe82b1"), + message_type="FRBC.FillLevelTargetProfile", + start_time=datetime( + year=2021, + month=4, + day=17, + hour=0, + minute=19, + second=20, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) # Act json_str = frbc_fill_level_target_profile.to_json() # Assert - expected_json = { 'elements': [ { 'duration': 26527, - 'fill_level_range': { 'end_of_range': 25463.75618862868, - 'start_of_range': 4668.6876987963915}}], - 'message_id': '8df9b5ac-de60-402c-9400-a4dd731fbd99', - 'message_type': 'FRBC.FillLevelTargetProfile', - 'start_time': '2023-02-12T11:42:28+11:00'} + expected_json = { + "elements": [ + { + "duration": 4704, + "fill_level_range": { + "end_of_range": 10800.98606857073545, + "start_of_range": 6891.19014440217, + }, + } + ], + "message_id": "04a6c8af-ca8d-420c-9c11-e96a70fe82b1", + "message_type": "FRBC.FillLevelTargetProfile", + "start_time": "2021-04-17T00:19:20Z", + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_instruction_test.py b/tests/unit/frbc/frbc_instruction_test.py index 893ce46..901a711 100644 --- a/tests/unit/frbc/frbc_instruction_test.py +++ b/tests/unit/frbc/frbc_instruction_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,14 +12,14 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "abnormal_condition": true, + "actuator_id": "db7855dd-05c4-4ba8-81e2-d10001c5bc3f", + "execution_time": "2023-04-11T16:46:33+01:00", + "id": "9ffd68cd-b0e2-44a6-aded-4dce6c18247e", + "message_id": "bcb3e1da-e797-4951-86be-5e5d9136c63f", "message_type": "FRBC.Instruction", - "message_id": "e961f92e-32a7-4117-81f3-8868968d2d14", - "id": "933e2c9c-526a-4934-96de-6eae0ae1f941", - "actuator_id": "02979ffc-3083-437d-8540-925073dbb465", - "operation_mode": "90c03b06-728a-4f79-a54b-e01a6bad6f37", - "operation_mode_factor": 2038.574227328529, - "execution_time": "2020-07-02T06:03:25+07:00", - "abnormal_condition": true + "operation_mode": "e7bf29a7-4ebc-49c1-a1fb-20725f450c91", + "operation_mode_factor": 2303.58902271682 } """ @@ -28,29 +27,70 @@ def test__from_json__happy_path_full(self): frbc_instruction = FRBCInstruction.from_json(json_str) # Assert - self.assertEqual(frbc_instruction.message_type, FRBC.Instruction) - self.assertEqual(frbc_instruction.message_id, uuid.UUID("e961f92e-32a7-4117-81f3-8868968d2d14")) - self.assertEqual(frbc_instruction.id, uuid.UUID("933e2c9c-526a-4934-96de-6eae0ae1f941")) - self.assertEqual(frbc_instruction.actuator_id, uuid.UUID("02979ffc-3083-437d-8540-925073dbb465")) - self.assertEqual(frbc_instruction.operation_mode, uuid.UUID("90c03b06-728a-4f79-a54b-e01a6bad6f37")) - self.assertEqual(frbc_instruction.operation_mode_factor, 2038.574227328529) - self.assertEqual(frbc_instruction.execution_time, datetime(year=2020, month=7, day=2, hour=6, minute=3, second=25, tzinfo=offset(offset=timedelta(seconds=25200.0)))) self.assertEqual(frbc_instruction.abnormal_condition, True) + self.assertEqual( + frbc_instruction.actuator_id, + uuid.UUID("db7855dd-05c4-4ba8-81e2-d10001c5bc3f"), + ) + self.assertEqual( + frbc_instruction.execution_time, + datetime( + year=2023, + month=4, + day=11, + hour=16, + minute=46, + second=33, + tzinfo=offset(offset=timedelta(seconds=3600.0)), + ), + ) + self.assertEqual( + frbc_instruction.id, uuid.UUID("9ffd68cd-b0e2-44a6-aded-4dce6c18247e") + ) + self.assertEqual( + frbc_instruction.message_id, + uuid.UUID("bcb3e1da-e797-4951-86be-5e5d9136c63f"), + ) + self.assertEqual(frbc_instruction.message_type, "FRBC.Instruction") + self.assertEqual( + frbc_instruction.operation_mode, + uuid.UUID("e7bf29a7-4ebc-49c1-a1fb-20725f450c91"), + ) + self.assertEqual(frbc_instruction.operation_mode_factor, 2303.58902271682) def test__to_json__happy_path_full(self): # Arrange - frbc_instruction = FRBCInstruction(message_type=FRBC.Instruction, message_id=uuid.UUID("e961f92e-32a7-4117-81f3-8868968d2d14"), id=uuid.UUID("933e2c9c-526a-4934-96de-6eae0ae1f941"), actuator_id=uuid.UUID("02979ffc-3083-437d-8540-925073dbb465"), operation_mode=uuid.UUID("90c03b06-728a-4f79-a54b-e01a6bad6f37"), operation_mode_factor=2038.574227328529, execution_time=datetime(year=2020, month=7, day=2, hour=6, minute=3, second=25, tzinfo=offset(offset=timedelta(seconds=25200.0))), abnormal_condition=True) + frbc_instruction = FRBCInstruction( + abnormal_condition=True, + actuator_id=uuid.UUID("db7855dd-05c4-4ba8-81e2-d10001c5bc3f"), + execution_time=datetime( + year=2023, + month=4, + day=11, + hour=16, + minute=46, + second=33, + tzinfo=offset(offset=timedelta(seconds=3600.0)), + ), + id=uuid.UUID("9ffd68cd-b0e2-44a6-aded-4dce6c18247e"), + message_id=uuid.UUID("bcb3e1da-e797-4951-86be-5e5d9136c63f"), + message_type="FRBC.Instruction", + operation_mode=uuid.UUID("e7bf29a7-4ebc-49c1-a1fb-20725f450c91"), + operation_mode_factor=2303.58902271682, + ) # Act json_str = frbc_instruction.to_json() # Assert - expected_json = { 'abnormal_condition': True, - 'actuator_id': '02979ffc-3083-437d-8540-925073dbb465', - 'execution_time': '2020-07-02T06:03:25+07:00', - 'id': '933e2c9c-526a-4934-96de-6eae0ae1f941', - 'message_id': 'e961f92e-32a7-4117-81f3-8868968d2d14', - 'message_type': 'FRBC.Instruction', - 'operation_mode': '90c03b06-728a-4f79-a54b-e01a6bad6f37', - 'operation_mode_factor': 2038.574227328529} + expected_json = { + "abnormal_condition": True, + "actuator_id": "db7855dd-05c4-4ba8-81e2-d10001c5bc3f", + "execution_time": "2023-04-11T16:46:33+01:00", + "id": "9ffd68cd-b0e2-44a6-aded-4dce6c18247e", + "message_id": "bcb3e1da-e797-4951-86be-5e5d9136c63f", + "message_type": "FRBC.Instruction", + "operation_mode": "e7bf29a7-4ebc-49c1-a1fb-20725f450c91", + "operation_mode_factor": 2303.58902271682, + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py index 8c80f50..79f480a 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_element_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_element_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -6,6 +5,7 @@ from s2python.common import * from s2python.frbc import * +from s2python.s2_validation_error import S2ValidationError class FRBCLeakageBehaviourElementTest(TestCase): @@ -14,10 +14,10 @@ def test__from_json__happy_path_full(self): json_str = """ { "fill_level_range": { - "start_of_range": 13590.14671121342, - "end_of_range": 25393.13672324905 + "end_of_range": 40192.498918818455, + "start_of_range": 29234.82582981918 }, - "leakage_rate": 8822.586828251793 + "leakage_rate": 1170.4041485129987 } """ @@ -25,18 +25,40 @@ def test__from_json__happy_path_full(self): frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement.from_json(json_str) # Assert - self.assertEqual(frbc_leakage_behaviour_element.fill_level_range, NumberRange(start_of_range=13590.14671121342, end_of_range=25393.13672324905)) - self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 8822.586828251793) + self.assertEqual( + frbc_leakage_behaviour_element.fill_level_range, + NumberRange(end_of_range=40192.498918818455, start_of_range=29234.82582981918), + ) + self.assertEqual(frbc_leakage_behaviour_element.leakage_rate, 1170.4041485129987) def test__to_json__happy_path_full(self): # Arrange - frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=13590.14671121342, end_of_range=25393.13672324905), leakage_rate=8822.586828251793) + frbc_leakage_behaviour_element = FRBCLeakageBehaviourElement( + fill_level_range=NumberRange( + end_of_range=40192.498918818455, start_of_range=29234.82582981918 + ), + leakage_rate=1170.4041485129987, + ) # Act json_str = frbc_leakage_behaviour_element.to_json() # Assert - expected_json = { 'fill_level_range': { 'end_of_range': 25393.13672324905, - 'start_of_range': 13590.14671121342}, - 'leakage_rate': 8822.586828251793} + expected_json = { + "fill_level_range": { + "end_of_range": 40192.498918818455, + "start_of_range": 29234.82582981918, + }, + "leakage_rate": 1170.4041485129987, + } self.assertEqual(json.loads(json_str), expected_json) + + def test__init__fill_level_range_end_is_smaller_than_start(self): + # Arrange / Act / Assert + with self.assertRaises(S2ValidationError): + FRBCLeakageBehaviourElement( + fill_level_range=NumberRange( + end_of_range=29234.82582981918, start_of_range=40192.498918818455 + ), + leakage_rate=1170.4041485129987, + ) diff --git a/tests/unit/frbc/frbc_leakage_behaviour_test.py b/tests/unit/frbc/frbc_leakage_behaviour_test.py index 3e95596..ad290d1 100644 --- a/tests/unit/frbc/frbc_leakage_behaviour_test.py +++ b/tests/unit/frbc/frbc_leakage_behaviour_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,18 +12,18 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "message_type": "FRBC.LeakageBehaviour", - "message_id": "b77d33ab-1dce-4fde-859a-d22b40364c2e", - "valid_from": "2021-09-02T00:18:01+00:00", "elements": [ { "fill_level_range": { - "start_of_range": 8607.996640989155, - "end_of_range": 48389.84849678871 + "end_of_range": 31155.931914859895, + "start_of_range": 5727.722922773178 }, - "leakage_rate": 8425.146853345715 + "leakage_rate": 1225.9695121338086 } - ] + ], + "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", + "message_type": "FRBC.LeakageBehaviour", + "valid_from": "2022-05-26T15:02:32Z" } """ @@ -32,23 +31,77 @@ def test__from_json__happy_path_full(self): frbc_leakage_behaviour = FRBCLeakageBehaviour.from_json(json_str) # Assert - self.assertEqual(frbc_leakage_behaviour.message_type, FRBC.LeakageBehaviour) - self.assertEqual(frbc_leakage_behaviour.message_id, uuid.UUID("b77d33ab-1dce-4fde-859a-d22b40364c2e")) - self.assertEqual(frbc_leakage_behaviour.valid_from, datetime(year=2021, month=9, day=2, hour=0, minute=18, second=1, tzinfo=offset(offset=timedelta(seconds=0.0)))) - self.assertEqual(frbc_leakage_behaviour.elements, [FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=8607.996640989155, end_of_range=48389.84849678871), leakage_rate=8425.146853345715)]) + self.assertEqual( + frbc_leakage_behaviour.elements, + [ + FRBCLeakageBehaviourElement( + fill_level_range=NumberRange( + end_of_range=31155.931914859895, + start_of_range=5727.722922773178, + ), + leakage_rate=1225.9695121338086, + ) + ], + ) + self.assertEqual( + frbc_leakage_behaviour.message_id, + uuid.UUID("b3e9604a-1127-4ecc-9f9e-336047fde285"), + ) + self.assertEqual(frbc_leakage_behaviour.message_type, "FRBC.LeakageBehaviour") + self.assertEqual( + frbc_leakage_behaviour.valid_from, + datetime( + year=2022, + month=5, + day=26, + hour=15, + minute=2, + second=32, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_leakage_behaviour = FRBCLeakageBehaviour(message_type=FRBC.LeakageBehaviour, message_id=uuid.UUID("b77d33ab-1dce-4fde-859a-d22b40364c2e"), valid_from=datetime(year=2021, month=9, day=2, hour=0, minute=18, second=1, tzinfo=offset(offset=timedelta(seconds=0.0))), elements=[FRBCLeakageBehaviourElement(fill_level_range=NumberRange(start_of_range=8607.996640989155, end_of_range=48389.84849678871), leakage_rate=8425.146853345715)]) + frbc_leakage_behaviour = FRBCLeakageBehaviour( + elements=[ + FRBCLeakageBehaviourElement( + fill_level_range=NumberRange( + end_of_range=31155.931914859895, + start_of_range=5727.722922773178, + ), + leakage_rate=1225.9695121338086, + ) + ], + message_id=uuid.UUID("b3e9604a-1127-4ecc-9f9e-336047fde285"), + message_type="FRBC.LeakageBehaviour", + valid_from=datetime( + year=2022, + month=5, + day=26, + hour=15, + minute=2, + second=32, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) # Act json_str = frbc_leakage_behaviour.to_json() # Assert - expected_json = { 'elements': [ { 'fill_level_range': { 'end_of_range': 48389.84849678871, - 'start_of_range': 8607.996640989155}, - 'leakage_rate': 8425.146853345715}], - 'message_id': 'b77d33ab-1dce-4fde-859a-d22b40364c2e', - 'message_type': 'FRBC.LeakageBehaviour', - 'valid_from': '2021-09-02T00:18:01+00:00'} + expected_json = { + "elements": [ + { + "fill_level_range": { + "end_of_range": 31155.931914859895, + "start_of_range": 5727.722922773178, + }, + "leakage_rate": 1225.9695121338086, + } + ], + "message_id": "b3e9604a-1127-4ecc-9f9e-336047fde285", + "message_type": "FRBC.LeakageBehaviour", + "valid_from": "2022-05-26T15:02:32Z", + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_element_test.py b/tests/unit/frbc/frbc_operation_mode_element_test.py index 21301dc..d2f3455 100644 --- a/tests/unit/frbc/frbc_operation_mode_element_test.py +++ b/tests/unit/frbc/frbc_operation_mode_element_test.py @@ -1,11 +1,11 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase import uuid -from s2python.common import * -from s2python.frbc import * +from s2python.common import NumberRange, PowerRange +from s2python.frbc.frbc_operation_mode_element import FRBCOperationModeElement +from s2python.generated.gen_s2 import CommodityQuantity class FRBCOperationModeElementTest(TestCase): @@ -14,23 +14,23 @@ def test__from_json__happy_path_full(self): json_str = """ { "fill_level_range": { - "start_of_range": 17902.57617756065, - "end_of_range": 23155.498329551232 + "end_of_range": 51798.05122344172, + "start_of_range": 12901.48976850875 }, "fill_rate": { - "start_of_range": 24592.14185864383, - "end_of_range": 64041.99840646259 + "end_of_range": 35734.54630113551, + "start_of_range": 10740.443924585083 }, "power_ranges": [ { - "start_of_range": 32526.45719448619, - "end_of_range": 71172.61140295293, - "commodity_quantity": "ELECTRIC.POWER.L1" + "commodity_quantity": "ELECTRIC.POWER.L1", + "end_of_range": 69093.48993128976, + "start_of_range": 34859.59303603876 } ], "running_costs": { - "start_of_range": 34310.357669763165, - "end_of_range": 49896.43882374468 + "end_of_range": 47869.03540464825, + "start_of_range": 19009.60894672492 } } """ @@ -39,26 +39,79 @@ def test__from_json__happy_path_full(self): frbc_operation_mode_element = FRBCOperationModeElement.from_json(json_str) # Assert - self.assertEqual(frbc_operation_mode_element.fill_level_range, NumberRange(start_of_range=17902.57617756065, end_of_range=23155.498329551232)) - self.assertEqual(frbc_operation_mode_element.fill_rate, NumberRange(start_of_range=24592.14185864383, end_of_range=64041.99840646259)) - self.assertEqual(frbc_operation_mode_element.power_ranges, [PowerRange(start_of_range=32526.45719448619, end_of_range=71172.61140295293, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)]) - self.assertEqual(frbc_operation_mode_element.running_costs, NumberRange(start_of_range=34310.357669763165, end_of_range=49896.43882374468)) + self.assertEqual( + frbc_operation_mode_element.fill_level_range, + NumberRange( + end_of_range=51798.05122344172, start_of_range=12901.48976850875 + ), + ) + self.assertEqual( + frbc_operation_mode_element.fill_rate, + NumberRange( + end_of_range=35734.54630113551, start_of_range=10740.443924585083 + ), + ) + self.assertEqual( + frbc_operation_mode_element.power_ranges, + [ + PowerRange( + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + end_of_range=69093.48993128976, + start_of_range=34859.59303603876, + ) + ], + ) + self.assertEqual( + frbc_operation_mode_element.running_costs, + NumberRange( + end_of_range=47869.03540464825, start_of_range=19009.60894672492 + ), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_operation_mode_element = FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=17902.57617756065, end_of_range=23155.498329551232), fill_rate=NumberRange(start_of_range=24592.14185864383, end_of_range=64041.99840646259), power_ranges=[PowerRange(start_of_range=32526.45719448619, end_of_range=71172.61140295293, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=34310.357669763165, end_of_range=49896.43882374468)) + frbc_operation_mode_element = FRBCOperationModeElement( + fill_level_range=NumberRange( + end_of_range=51798.05122344172, start_of_range=12901.48976850875 + ), + fill_rate=NumberRange( + end_of_range=35734.54630113551, start_of_range=10740.443924585083 + ), + power_ranges=[ + PowerRange( + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + end_of_range=69093.48993128976, + start_of_range=34859.59303603876, + ) + ], + running_costs=NumberRange( + end_of_range=47869.03540464825, start_of_range=19009.60894672492 + ), + ) # Act json_str = frbc_operation_mode_element.to_json() # Assert - expected_json = { 'fill_level_range': { 'end_of_range': 23155.498329551232, - 'start_of_range': 17902.57617756065}, - 'fill_rate': { 'end_of_range': 64041.99840646259, - 'start_of_range': 24592.14185864383}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 71172.61140295293, - 'start_of_range': 32526.45719448619}], - 'running_costs': { 'end_of_range': 49896.43882374468, - 'start_of_range': 34310.357669763165}} + expected_json = { + "fill_level_range": { + "end_of_range": 51798.05122344172, + "start_of_range": 12901.48976850875, + }, + "fill_rate": { + "end_of_range": 35734.54630113551, + "start_of_range": 10740.443924585083, + }, + "power_ranges": [ + { + "commodity_quantity": "ELECTRIC.POWER.L1", + "end_of_range": 69093.48993128976, + "start_of_range": 34859.59303603876, + } + ], + "running_costs": { + "end_of_range": 47869.03540464825, + "start_of_range": 19009.60894672492, + }, + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_operation_mode_test.py b/tests/unit/frbc/frbc_operation_mode_test.py index 4e19053..0d97f27 100644 --- a/tests/unit/frbc/frbc_operation_mode_test.py +++ b/tests/unit/frbc/frbc_operation_mode_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,32 +12,32 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "id": "689ee211-83c6-4907-9bde-6ddd47996557", - "diagnostic_label": "some-test-string8118", + "abnormal_condition_only": true, + "diagnostic_label": "some-test-string7557", "elements": [ { "fill_level_range": { - "start_of_range": 29586.80244711599, - "end_of_range": 40811.76669416521 + "end_of_range": 34304.92092046668, + "start_of_range": 17579.18236077446 }, "fill_rate": { - "start_of_range": 36920.12023083362, - "end_of_range": 67087.76396982145 + "end_of_range": 41719.931165871916, + "start_of_range": 10542.600445486576 }, "power_ranges": [ { - "start_of_range": 33844.8053625118, - "end_of_range": 67624.38313311148, - "commodity_quantity": "ELECTRIC.POWER.L1" + "commodity_quantity": "ELECTRIC.POWER.L1", + "end_of_range": 44983.5145552435, + "start_of_range": 29337.138579372047 } ], "running_costs": { - "start_of_range": 26024.33483461767, - "end_of_range": 48053.179872036795 + "end_of_range": 62835.00070350196, + "start_of_range": 33318.34845926906 } } ], - "abnormal_condition_only": true + "id": "b1255236-475c-4dc7-a728-afb620a99ec8" } """ @@ -46,29 +45,95 @@ def test__from_json__happy_path_full(self): frbc_operation_mode = FRBCOperationMode.from_json(json_str) # Assert - self.assertEqual(frbc_operation_mode.id, uuid.UUID("689ee211-83c6-4907-9bde-6ddd47996557")) - self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string8118") - self.assertEqual(frbc_operation_mode.elements, [FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29586.80244711599, end_of_range=40811.76669416521), fill_rate=NumberRange(start_of_range=36920.12023083362, end_of_range=67087.76396982145), power_ranges=[PowerRange(start_of_range=33844.8053625118, end_of_range=67624.38313311148, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=26024.33483461767, end_of_range=48053.179872036795))]) self.assertEqual(frbc_operation_mode.abnormal_condition_only, True) + self.assertEqual(frbc_operation_mode.diagnostic_label, "some-test-string7557") + self.assertEqual( + frbc_operation_mode.elements, + [ + FRBCOperationModeElement( + fill_level_range=NumberRange( + end_of_range=34304.92092046668, start_of_range=17579.18236077446 + ), + fill_rate=NumberRange( + end_of_range=41719.931165871916, + start_of_range=10542.600445486576, + ), + power_ranges=[ + PowerRange( + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + end_of_range=44983.5145552435, + start_of_range=29337.138579372047, + ) + ], + running_costs=NumberRange( + end_of_range=62835.00070350196, start_of_range=33318.34845926906 + ), + ) + ], + ) + self.assertEqual( + frbc_operation_mode.id, uuid.UUID("b1255236-475c-4dc7-a728-afb620a99ec8") + ) def test__to_json__happy_path_full(self): # Arrange - frbc_operation_mode = FRBCOperationMode(id=uuid.UUID("689ee211-83c6-4907-9bde-6ddd47996557"), diagnostic_label="some-test-string8118", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=29586.80244711599, end_of_range=40811.76669416521), fill_rate=NumberRange(start_of_range=36920.12023083362, end_of_range=67087.76396982145), power_ranges=[PowerRange(start_of_range=33844.8053625118, end_of_range=67624.38313311148, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=26024.33483461767, end_of_range=48053.179872036795))], abnormal_condition_only=True) + frbc_operation_mode = FRBCOperationMode( + abnormal_condition_only=True, + diagnostic_label="some-test-string7557", + elements=[ + FRBCOperationModeElement( + fill_level_range=NumberRange( + end_of_range=34304.92092046668, start_of_range=17579.18236077446 + ), + fill_rate=NumberRange( + end_of_range=41719.931165871916, + start_of_range=10542.600445486576, + ), + power_ranges=[ + PowerRange( + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + end_of_range=44983.5145552435, + start_of_range=29337.138579372047, + ) + ], + running_costs=NumberRange( + end_of_range=62835.00070350196, start_of_range=33318.34845926906 + ), + ) + ], + id=uuid.UUID("b1255236-475c-4dc7-a728-afb620a99ec8"), + ) # Act json_str = frbc_operation_mode.to_json() # Assert - expected_json = { 'abnormal_condition_only': True, - 'diagnostic_label': 'some-test-string8118', - 'elements': [ { 'fill_level_range': { 'end_of_range': 40811.76669416521, - 'start_of_range': 29586.80244711599}, - 'fill_rate': { 'end_of_range': 67087.76396982145, - 'start_of_range': 36920.12023083362}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 67624.38313311148, - 'start_of_range': 33844.8053625118}], - 'running_costs': { 'end_of_range': 48053.179872036795, - 'start_of_range': 26024.33483461767}}], - 'id': '689ee211-83c6-4907-9bde-6ddd47996557'} + expected_json = { + "abnormal_condition_only": True, + "diagnostic_label": "some-test-string7557", + "elements": [ + { + "fill_level_range": { + "end_of_range": 34304.92092046668, + "start_of_range": 17579.18236077446, + }, + "fill_rate": { + "end_of_range": 41719.931165871916, + "start_of_range": 10542.600445486576, + }, + "power_ranges": [ + { + "commodity_quantity": "ELECTRIC.POWER.L1", + "end_of_range": 44983.5145552435, + "start_of_range": 29337.138579372047, + } + ], + "running_costs": { + "end_of_range": 62835.00070350196, + "start_of_range": 33318.34845926906, + }, + } + ], + "id": "b1255236-475c-4dc7-a728-afb620a99ec8", + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_description_test.py b/tests/unit/frbc/frbc_storage_description_test.py index ab7ae37..a1e8e2e 100644 --- a/tests/unit/frbc/frbc_storage_description_test.py +++ b/tests/unit/frbc/frbc_storage_description_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,15 +12,15 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "diagnostic_label": "some-test-string7988", - "fill_level_label": "some-test-string7290", - "provides_leakage_behaviour": false, - "provides_fill_level_target_profile": false, - "provides_usage_forecast": false, + "diagnostic_label": "some-test-string3063", + "fill_level_label": "some-test-string2323", "fill_level_range": { - "start_of_range": 10366.413731478093, - "end_of_range": 12149.613524580698 - } + "end_of_range": 14555.806367871957, + "start_of_range": 10409.397377840089 + }, + "provides_fill_level_target_profile": true, + "provides_leakage_behaviour": false, + "provides_usage_forecast": false } """ @@ -29,26 +28,50 @@ def test__from_json__happy_path_full(self): frbc_storage_description = FRBCStorageDescription.from_json(json_str) # Assert - self.assertEqual(frbc_storage_description.diagnostic_label, "some-test-string7988") - self.assertEqual(frbc_storage_description.fill_level_label, "some-test-string7290") + self.assertEqual( + frbc_storage_description.diagnostic_label, "some-test-string3063" + ) + self.assertEqual( + frbc_storage_description.fill_level_label, "some-test-string2323" + ) + self.assertEqual( + frbc_storage_description.fill_level_range, + NumberRange( + end_of_range=14555.806367871957, start_of_range=10409.397377840089 + ), + ) + self.assertEqual( + frbc_storage_description.provides_fill_level_target_profile, True + ) self.assertEqual(frbc_storage_description.provides_leakage_behaviour, False) - self.assertEqual(frbc_storage_description.provides_fill_level_target_profile, False) self.assertEqual(frbc_storage_description.provides_usage_forecast, False) - self.assertEqual(frbc_storage_description.fill_level_range, NumberRange(start_of_range=10366.413731478093, end_of_range=12149.613524580698)) def test__to_json__happy_path_full(self): # Arrange - frbc_storage_description = FRBCStorageDescription(diagnostic_label="some-test-string7988", fill_level_label="some-test-string7290", provides_leakage_behaviour=False, provides_fill_level_target_profile=False, provides_usage_forecast=False, fill_level_range=NumberRange(start_of_range=10366.413731478093, end_of_range=12149.613524580698)) + frbc_storage_description = FRBCStorageDescription( + diagnostic_label="some-test-string3063", + fill_level_label="some-test-string2323", + fill_level_range=NumberRange( + end_of_range=14555.806367871957, start_of_range=10409.397377840089 + ), + provides_fill_level_target_profile=True, + provides_leakage_behaviour=False, + provides_usage_forecast=False, + ) # Act json_str = frbc_storage_description.to_json() # Assert - expected_json = { 'diagnostic_label': 'some-test-string7988', - 'fill_level_label': 'some-test-string7290', - 'fill_level_range': { 'end_of_range': 12149.613524580698, - 'start_of_range': 10366.413731478093}, - 'provides_fill_level_target_profile': False, - 'provides_leakage_behaviour': False, - 'provides_usage_forecast': False} + expected_json = { + "diagnostic_label": "some-test-string3063", + "fill_level_label": "some-test-string2323", + "fill_level_range": { + "end_of_range": 14555.806367871957, + "start_of_range": 10409.397377840089, + }, + "provides_fill_level_target_profile": True, + "provides_leakage_behaviour": False, + "provides_usage_forecast": False, + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_storage_status_test.py b/tests/unit/frbc/frbc_storage_status_test.py index ceb2a11..c2b99ab 100644 --- a/tests/unit/frbc/frbc_storage_status_test.py +++ b/tests/unit/frbc/frbc_storage_status_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,9 +12,9 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "message_id": "6bad8186-9ebf-4647-ac45-1c6856511a2f", "message_type": "FRBC.StorageStatus", - "message_id": "2b7b06cb-b4a6-4997-9d0c-8ea075f9941a", - "present_fill_level": 226.70889257233483 + "present_fill_level": 2443.939298819414 } """ @@ -23,19 +22,28 @@ def test__from_json__happy_path_full(self): frbc_storage_status = FRBCStorageStatus.from_json(json_str) # Assert - self.assertEqual(frbc_storage_status.message_type, FRBC.StorageStatus) - self.assertEqual(frbc_storage_status.message_id, uuid.UUID("2b7b06cb-b4a6-4997-9d0c-8ea075f9941a")) - self.assertEqual(frbc_storage_status.present_fill_level, 226.70889257233483) + self.assertEqual( + frbc_storage_status.message_id, + uuid.UUID("6bad8186-9ebf-4647-ac45-1c6856511a2f"), + ) + self.assertEqual(frbc_storage_status.message_type, "FRBC.StorageStatus") + self.assertEqual(frbc_storage_status.present_fill_level, 2443.939298819414) def test__to_json__happy_path_full(self): # Arrange - frbc_storage_status = FRBCStorageStatus(message_type=FRBC.StorageStatus, message_id=uuid.UUID("2b7b06cb-b4a6-4997-9d0c-8ea075f9941a"), present_fill_level=226.70889257233483) + frbc_storage_status = FRBCStorageStatus( + message_id=uuid.UUID("6bad8186-9ebf-4647-ac45-1c6856511a2f"), + message_type="FRBC.StorageStatus", + present_fill_level=2443.939298819414, + ) # Act json_str = frbc_storage_status.to_json() # Assert - expected_json = { 'message_id': '2b7b06cb-b4a6-4997-9d0c-8ea075f9941a', - 'message_type': 'FRBC.StorageStatus', - 'present_fill_level': 226.70889257233483} + expected_json = { + "message_id": "6bad8186-9ebf-4647-ac45-1c6856511a2f", + "message_type": "FRBC.StorageStatus", + "present_fill_level": 2443.939298819414, + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_system_description_test.py b/tests/unit/frbc/frbc_system_description_test.py index 6f1f427..9950ea7 100644 --- a/tests/unit/frbc/frbc_system_description_test.py +++ b/tests/unit/frbc/frbc_system_description_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,82 +12,82 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "message_type": "FRBC.SystemDescription", - "message_id": "a2dfaa9c-e52c-435d-94e4-15007f208ec6", - "valid_from": "2021-07-13T15:48:07+00:00", "actuators": [ { - "id": "b6872218-3fb7-436e-bdcb-e393f8ef95f7", - "diagnostic_label": "some-test-string6489", - "supported_commodities": [ - "GAS" - ], + "diagnostic_label": "some-test-string2728", + "id": "a1061148-f19e-4b1b-8fe3-b506583ce61e", "operation_modes": [ { - "id": "052029e8-85e2-4b96-bd03-7f3345445e76", - "diagnostic_label": "some-test-string7949", + "abnormal_condition_only": false, + "diagnostic_label": "some-test-string2930", "elements": [ { "fill_level_range": { - "start_of_range": 17740.22962461282, - "end_of_range": 52507.59091283206 + "end_of_range": 36932.65171036228, + "start_of_range": 12649.272766336762 }, "fill_rate": { - "start_of_range": 35532.15914996989, - "end_of_range": 48265.232076205815 + "end_of_range": 34553.16163528188, + "start_of_range": 14377.963894945604 }, "power_ranges": [ { - "start_of_range": 38748.50842724231, - "end_of_range": 47741.69817105044, - "commodity_quantity": "ELECTRIC.POWER.L1" + "commodity_quantity": "ELECTRIC.POWER.L1", + "end_of_range": 46924.65023353163, + "start_of_range": 11888.235871902496 } ], "running_costs": { - "start_of_range": 35692.82350352573, - "end_of_range": 47884.66649499221 + "end_of_range": 42897.60731684277, + "start_of_range": 33997.56376994998 } } ], - "abnormal_condition_only": true + "id": "2795136c-eb30-4f8a-bdaa-61feba1e71b6" } ], - "transitions": [ + "supported_commodities": [ + "ELECTRICITY" + ], + "timers": [ { - "id": "b1ace854-611e-4064-8c9b-434b453879df", - "from_": "66c5b0af-36e0-425d-b5c3-9cf27944f555", - "to": "8aed333c-663c-4502-8dc2-45c21acbe366", - "start_timers": [ - "56258c00-182b-4299-b9f0-c44d533dfdcb" - ], - "blocking_timers": [ - "8dac5674-4428-46cd-bfad-c89bb4d23029" - ], - "transition_costs": 1403.4899643577787, - "transition_duration": 32794, - "abnormal_condition_only": false + "diagnostic_label": "some-test-string4315", + "duration": 14099, + "id": "e1ff9e58-935b-4765-92e3-5e7679f73eb6" } ], - "timers": [ + "transitions": [ { - "id": "e8a806df-eb45-4ed6-959d-db717d2462c8", - "diagnostic_label": "some-test-string3261", - "duration": 5659 + "abnormal_condition_only": true, + "blocking_timers": [ + "e1ff9e58-935b-4765-92e3-5e7679f73eb6" + ], + "from": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", + "id": "c32cc1d3-4722-41e3-a8de-55307c723611", + "start_timers": [ + "e1ff9e58-935b-4765-92e3-5e7679f73eb6" + ], + "to": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", + "transition_costs": 1018.4228054114793, + "transition_duration": 11988 } ] } ], + "message_id": "97256813-de70-4640-a992-9ae0b2d8e4d1", + "message_type": "FRBC.SystemDescription", "storage": { - "diagnostic_label": "some-test-string9735", - "fill_level_label": "some-test-string1238", - "provides_leakage_behaviour": true, - "provides_fill_level_target_profile": false, - "provides_usage_forecast": true, + "diagnostic_label": "some-test-string8418", + "fill_level_label": "some-test-string9512", "fill_level_range": { - "start_of_range": 8670.277473912942, - "end_of_range": 27152.584459033194 - } - } + "end_of_range": 20876.752745956997, + "start_of_range": 18324.0229135081 + }, + "provides_fill_level_target_profile": false, + "provides_leakage_behaviour": true, + "provides_usage_forecast": false + }, + "valid_from": "2020-10-07T06:30:55Z" } """ @@ -96,54 +95,257 @@ def test__from_json__happy_path_full(self): frbc_system_description = FRBCSystemDescription.from_json(json_str) # Assert - self.assertEqual(frbc_system_description.message_type, FRBC.SystemDescription) - self.assertEqual(frbc_system_description.message_id, uuid.UUID("a2dfaa9c-e52c-435d-94e4-15007f208ec6")) - self.assertEqual(frbc_system_description.valid_from, datetime(year=2021, month=7, day=13, hour=15, minute=48, second=7, tzinfo=offset(offset=timedelta(seconds=0.0)))) - self.assertEqual(frbc_system_description.actuators, [FRBCActuatorDescription(id=uuid.UUID("b6872218-3fb7-436e-bdcb-e393f8ef95f7"), diagnostic_label="some-test-string6489", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("052029e8-85e2-4b96-bd03-7f3345445e76"), diagnostic_label="some-test-string7949", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=17740.22962461282, end_of_range=52507.59091283206), fill_rate=NumberRange(start_of_range=35532.15914996989, end_of_range=48265.232076205815), power_ranges=[PowerRange(start_of_range=38748.50842724231, end_of_range=47741.69817105044, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=35692.82350352573, end_of_range=47884.66649499221))], abnormal_condition_only=True)], transitions=[Transition(id=uuid.UUID("b1ace854-611e-4064-8c9b-434b453879df"), from_=uuid.UUID("66c5b0af-36e0-425d-b5c3-9cf27944f555"), to=uuid.UUID("8aed333c-663c-4502-8dc2-45c21acbe366"), start_timers=[uuid.UUID("56258c00-182b-4299-b9f0-c44d533dfdcb")], blocking_timers=[uuid.UUID("8dac5674-4428-46cd-bfad-c89bb4d23029")], transition_costs=1403.4899643577787, transition_duration=Duration.from_timedelta(timedelta(milliseconds=32794)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("e8a806df-eb45-4ed6-959d-db717d2462c8"), diagnostic_label="some-test-string3261", duration=Duration.from_timedelta(timedelta(milliseconds=5659)))])]) - self.assertEqual(frbc_system_description.storage, FRBCStorageDescription(diagnostic_label="some-test-string9735", fill_level_label="some-test-string1238", provides_leakage_behaviour=True, provides_fill_level_target_profile=False, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=8670.277473912942, end_of_range=27152.584459033194))) + # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. + # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to + # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 + transition = Transition( + **{ + "id": uuid.UUID("c32cc1d3-4722-41e3-a8de-55307c723611"), + "from": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), + "to": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), + "start_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], + "blocking_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], + "transition_costs": 1018.4228054114793, + "transition_duration": Duration.from_milliseconds(11988), + "abnormal_condition_only": True, + } + ) + + self.assertEqual( + frbc_system_description.actuators, + [ + FRBCActuatorDescription( + diagnostic_label="some-test-string2728", + id=uuid.UUID("a1061148-f19e-4b1b-8fe3-b506583ce61e"), + operation_modes=[ + FRBCOperationMode( + abnormal_condition_only=False, + diagnostic_label="some-test-string2930", + elements=[ + FRBCOperationModeElement( + fill_level_range=NumberRange( + end_of_range=36932.65171036228, + start_of_range=12649.272766336762, + ), + fill_rate=NumberRange( + end_of_range=34553.16163528188, + start_of_range=14377.963894945604, + ), + power_ranges=[ + PowerRange( + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + end_of_range=46924.65023353163, + start_of_range=11888.235871902496, + ) + ], + running_costs=NumberRange( + end_of_range=42897.60731684277, + start_of_range=33997.56376994998, + ), + ) + ], + id=uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), + ) + ], + supported_commodities=[Commodity.ELECTRICITY], + timers=[ + Timer( + diagnostic_label="some-test-string4315", + duration=Duration.from_timedelta(timedelta(milliseconds=14099)), + id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), + ) + ], + transitions=[transition], + ) + ], + ) + self.assertEqual( + frbc_system_description.message_id, + uuid.UUID("97256813-de70-4640-a992-9ae0b2d8e4d1"), + ) + self.assertEqual(frbc_system_description.message_type, "FRBC.SystemDescription") + self.assertEqual( + frbc_system_description.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), + provides_fill_level_target_profile=False, + provides_leakage_behaviour=True, + provides_usage_forecast=False, + ), + ) + self.assertEqual( + frbc_system_description.valid_from, + datetime( + year=2020, + month=10, + day=7, + hour=6, + minute=30, + second=55, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_system_description = FRBCSystemDescription(message_type=FRBC.SystemDescription, message_id=uuid.UUID("a2dfaa9c-e52c-435d-94e4-15007f208ec6"), valid_from=datetime(year=2021, month=7, day=13, hour=15, minute=48, second=7, tzinfo=offset(offset=timedelta(seconds=0.0))), actuators=[FRBCActuatorDescription(id=uuid.UUID("b6872218-3fb7-436e-bdcb-e393f8ef95f7"), diagnostic_label="some-test-string6489", supported_commodities=[Commodity.GAS], operation_modes=[FRBCOperationMode(id=uuid.UUID("052029e8-85e2-4b96-bd03-7f3345445e76"), diagnostic_label="some-test-string7949", elements=[FRBCOperationModeElement(fill_level_range=NumberRange(start_of_range=17740.22962461282, end_of_range=52507.59091283206), fill_rate=NumberRange(start_of_range=35532.15914996989, end_of_range=48265.232076205815), power_ranges=[PowerRange(start_of_range=38748.50842724231, end_of_range=47741.69817105044, commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1)], running_costs=NumberRange(start_of_range=35692.82350352573, end_of_range=47884.66649499221))], abnormal_condition_only=True)], transitions=[Transition(id=uuid.UUID("b1ace854-611e-4064-8c9b-434b453879df"), from_=uuid.UUID("66c5b0af-36e0-425d-b5c3-9cf27944f555"), to=uuid.UUID("8aed333c-663c-4502-8dc2-45c21acbe366"), start_timers=[uuid.UUID("56258c00-182b-4299-b9f0-c44d533dfdcb")], blocking_timers=[uuid.UUID("8dac5674-4428-46cd-bfad-c89bb4d23029")], transition_costs=1403.4899643577787, transition_duration=Duration.from_timedelta(timedelta(milliseconds=32794)), abnormal_condition_only=False)], timers=[Timer(id=uuid.UUID("e8a806df-eb45-4ed6-959d-db717d2462c8"), diagnostic_label="some-test-string3261", duration=Duration.from_timedelta(timedelta(milliseconds=5659)))])], storage=FRBCStorageDescription(diagnostic_label="some-test-string9735", fill_level_label="some-test-string1238", provides_leakage_behaviour=True, provides_fill_level_target_profile=False, provides_usage_forecast=True, fill_level_range=NumberRange(start_of_range=8670.277473912942, end_of_range=27152.584459033194))) + # TODO We have to resort to using a dict as we HAVE to pass the 'from' key which is a Python reserved keyword. + # We will fix this by moving to pydantic v2 in which aliases have been fixed in which they may be used to + # assign values during init. See: https://github.com/flexiblepower/s2-ws-json-python/issues/10 + transition = Transition( + **{ + "id": uuid.UUID("c32cc1d3-4722-41e3-a8de-55307c723611"), + "from": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), + "to": uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), + "start_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], + "blocking_timers": [uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6")], + "transition_costs": 1018.4228054114793, + "transition_duration": Duration.from_milliseconds(11988), + "abnormal_condition_only": True, + } + ) + frbc_system_description = FRBCSystemDescription( + actuators=[ + FRBCActuatorDescription( + diagnostic_label="some-test-string2728", + id=uuid.UUID("a1061148-f19e-4b1b-8fe3-b506583ce61e"), + operation_modes=[ + FRBCOperationMode( + abnormal_condition_only=False, + diagnostic_label="some-test-string2930", + elements=[ + FRBCOperationModeElement( + fill_level_range=NumberRange( + end_of_range=36932.65171036228, + start_of_range=12649.272766336762, + ), + fill_rate=NumberRange( + end_of_range=34553.16163528188, + start_of_range=14377.963894945604, + ), + power_ranges=[ + PowerRange( + commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1, + end_of_range=46924.65023353163, + start_of_range=11888.235871902496, + ) + ], + running_costs=NumberRange( + end_of_range=42897.60731684277, + start_of_range=33997.56376994998, + ), + ) + ], + id=uuid.UUID("2795136c-eb30-4f8a-bdaa-61feba1e71b6"), + ) + ], + supported_commodities=[Commodity.ELECTRICITY], + timers=[ + Timer( + diagnostic_label="some-test-string4315", + duration=Duration.from_timedelta(timedelta(milliseconds=14099)), + id=uuid.UUID("e1ff9e58-935b-4765-92e3-5e7679f73eb6"), + ) + ], + transitions=[transition], + ) + ], + message_id=uuid.UUID("97256813-de70-4640-a992-9ae0b2d8e4d1"), + message_type="FRBC.SystemDescription", + 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), + provides_fill_level_target_profile=False, + provides_leakage_behaviour=True, + provides_usage_forecast=False, + ), + valid_from=datetime( + year=2020, + month=10, + day=7, + hour=6, + minute=30, + second=55, + tzinfo=offset(offset=timedelta(seconds=0.0)), + ), + ) # Act json_str = frbc_system_description.to_json() # Assert - expected_json = { 'actuators': [ { 'diagnostic_label': 'some-test-string6489', - 'id': 'b6872218-3fb7-436e-bdcb-e393f8ef95f7', - 'operation_modes': [ { 'abnormal_condition_only': True, - 'diagnostic_label': 'some-test-string7949', - 'elements': [ { 'fill_level_range': { 'end_of_range': 52507.59091283206, - 'start_of_range': 17740.22962461282}, - 'fill_rate': { 'end_of_range': 48265.232076205815, - 'start_of_range': 35532.15914996989}, - 'power_ranges': [ { 'commodity_quantity': 'ELECTRIC.POWER.L1', - 'end_of_range': 47741.69817105044, - 'start_of_range': 38748.50842724231}], - 'running_costs': { 'end_of_range': 47884.66649499221, - 'start_of_range': 35692.82350352573}}], - 'id': '052029e8-85e2-4b96-bd03-7f3345445e76'}], - 'supported_commodities': ['GAS'], - 'timers': [ { 'diagnostic_label': 'some-test-string3261', - 'duration': 5659, - 'id': 'e8a806df-eb45-4ed6-959d-db717d2462c8'}], - 'transitions': [ { 'abnormal_condition_only': False, - 'blocking_timers': [ '8dac5674-4428-46cd-bfad-c89bb4d23029'], - 'from_': '66c5b0af-36e0-425d-b5c3-9cf27944f555', - 'id': 'b1ace854-611e-4064-8c9b-434b453879df', - 'start_timers': [ '56258c00-182b-4299-b9f0-c44d533dfdcb'], - 'to': '8aed333c-663c-4502-8dc2-45c21acbe366', - 'transition_costs': 1403.4899643577787, - 'transition_duration': 32794}]}], - 'message_id': 'a2dfaa9c-e52c-435d-94e4-15007f208ec6', - 'message_type': 'FRBC.SystemDescription', - 'storage': { 'diagnostic_label': 'some-test-string9735', - 'fill_level_label': 'some-test-string1238', - 'fill_level_range': { 'end_of_range': 27152.584459033194, - 'start_of_range': 8670.277473912942}, - 'provides_fill_level_target_profile': False, - 'provides_leakage_behaviour': True, - 'provides_usage_forecast': True}, - 'valid_from': '2021-07-13T15:48:07+00:00'} + expected_json = { + "actuators": [ + { + "diagnostic_label": "some-test-string2728", + "id": "a1061148-f19e-4b1b-8fe3-b506583ce61e", + "operation_modes": [ + { + "abnormal_condition_only": False, + "diagnostic_label": "some-test-string2930", + "elements": [ + { + "fill_level_range": { + "end_of_range": 36932.65171036228, + "start_of_range": 12649.272766336762, + }, + "fill_rate": { + "end_of_range": 34553.16163528188, + "start_of_range": 14377.963894945604, + }, + "power_ranges": [ + { + "commodity_quantity": "ELECTRIC.POWER.L1", + "end_of_range": 46924.65023353163, + "start_of_range": 11888.235871902496, + } + ], + "running_costs": { + "end_of_range": 42897.60731684277, + "start_of_range": 33997.56376994998, + }, + } + ], + "id": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", + } + ], + "supported_commodities": ["ELECTRICITY"], + "timers": [ + { + "diagnostic_label": "some-test-string4315", + "duration": 14099, + "id": "e1ff9e58-935b-4765-92e3-5e7679f73eb6", + } + ], + "transitions": [ + { + "abnormal_condition_only": True, + "blocking_timers": ["e1ff9e58-935b-4765-92e3-5e7679f73eb6"], + "from": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", + "id": "c32cc1d3-4722-41e3-a8de-55307c723611", + "start_timers": ["e1ff9e58-935b-4765-92e3-5e7679f73eb6"], + "to": "2795136c-eb30-4f8a-bdaa-61feba1e71b6", + "transition_costs": 1018.4228054114793, + "transition_duration": 11988, + } + ], + } + ], + "message_id": "97256813-de70-4640-a992-9ae0b2d8e4d1", + "message_type": "FRBC.SystemDescription", + "storage": { + "diagnostic_label": "some-test-string8418", + "fill_level_label": "some-test-string9512", + "fill_level_range": { + "end_of_range": 20876.752745956997, + "start_of_range": 18324.0229135081, + }, + "provides_fill_level_target_profile": False, + "provides_leakage_behaviour": True, + "provides_usage_forecast": False, + }, + "valid_from": "2020-10-07T06:30:55Z", + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_timer_status_test.py b/tests/unit/frbc/frbc_timer_status_test.py index 44643ad..74e2924 100644 --- a/tests/unit/frbc/frbc_timer_status_test.py +++ b/tests/unit/frbc/frbc_timer_status_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,11 +12,11 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { + "actuator_id": "f2e1f540-0235-429f-a45c-4d5cbe65d33f", + "finished_at": "2020-11-03T12:57:27+02:00", + "message_id": "57240f00-0b91-49bb-a4b0-2107d062faec", "message_type": "FRBC.TimerStatus", - "message_id": "b9060ff4-322c-4ec4-a1d0-1db024dd2fe7", - "timer_id": "564ceeaa-5f4f-46f9-8986-8c7920f76c85", - "actuator_id": "5d0523a8-2d85-417e-a83c-ddc9a6694cc2", - "finished_at": "2021-10-18T02:07:15-02:00" + "timer_id": "bcb8e64f-ea4c-4b92-b4cb-20026a13d663" } """ @@ -25,23 +24,59 @@ def test__from_json__happy_path_full(self): frbc_timer_status = FRBCTimerStatus.from_json(json_str) # Assert - self.assertEqual(frbc_timer_status.message_type, FRBC.TimerStatus) - self.assertEqual(frbc_timer_status.message_id, uuid.UUID("b9060ff4-322c-4ec4-a1d0-1db024dd2fe7")) - self.assertEqual(frbc_timer_status.timer_id, uuid.UUID("564ceeaa-5f4f-46f9-8986-8c7920f76c85")) - self.assertEqual(frbc_timer_status.actuator_id, uuid.UUID("5d0523a8-2d85-417e-a83c-ddc9a6694cc2")) - self.assertEqual(frbc_timer_status.finished_at, datetime(year=2021, month=10, day=18, hour=2, minute=7, second=15, tzinfo=offset(offset=timedelta(seconds=-7200.0)))) + self.assertEqual( + frbc_timer_status.actuator_id, + uuid.UUID("f2e1f540-0235-429f-a45c-4d5cbe65d33f"), + ) + self.assertEqual( + frbc_timer_status.finished_at, + datetime( + year=2020, + month=11, + day=3, + hour=12, + minute=57, + second=27, + tzinfo=offset(offset=timedelta(seconds=7200.0)), + ), + ) + self.assertEqual( + frbc_timer_status.message_id, + uuid.UUID("57240f00-0b91-49bb-a4b0-2107d062faec"), + ) + self.assertEqual(frbc_timer_status.message_type, "FRBC.TimerStatus") + self.assertEqual( + frbc_timer_status.timer_id, + uuid.UUID("bcb8e64f-ea4c-4b92-b4cb-20026a13d663"), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_timer_status = FRBCTimerStatus(message_type=FRBC.TimerStatus, message_id=uuid.UUID("b9060ff4-322c-4ec4-a1d0-1db024dd2fe7"), timer_id=uuid.UUID("564ceeaa-5f4f-46f9-8986-8c7920f76c85"), actuator_id=uuid.UUID("5d0523a8-2d85-417e-a83c-ddc9a6694cc2"), finished_at=datetime(year=2021, month=10, day=18, hour=2, minute=7, second=15, tzinfo=offset(offset=timedelta(seconds=-7200.0)))) + frbc_timer_status = FRBCTimerStatus( + actuator_id=uuid.UUID("f2e1f540-0235-429f-a45c-4d5cbe65d33f"), + finished_at=datetime( + year=2020, + month=11, + day=3, + hour=12, + minute=57, + second=27, + tzinfo=offset(offset=timedelta(seconds=7200.0)), + ), + message_id=uuid.UUID("57240f00-0b91-49bb-a4b0-2107d062faec"), + message_type="FRBC.TimerStatus", + timer_id=uuid.UUID("bcb8e64f-ea4c-4b92-b4cb-20026a13d663"), + ) # Act json_str = frbc_timer_status.to_json() # Assert - expected_json = { 'actuator_id': '5d0523a8-2d85-417e-a83c-ddc9a6694cc2', - 'finished_at': '2021-10-18T02:07:15-02:00', - 'message_id': 'b9060ff4-322c-4ec4-a1d0-1db024dd2fe7', - 'message_type': 'FRBC.TimerStatus', - 'timer_id': '564ceeaa-5f4f-46f9-8986-8c7920f76c85'} + expected_json = { + "actuator_id": "f2e1f540-0235-429f-a45c-4d5cbe65d33f", + "finished_at": "2020-11-03T12:57:27+02:00", + "message_id": "57240f00-0b91-49bb-a4b0-2107d062faec", + "message_type": "FRBC.TimerStatus", + "timer_id": "bcb8e64f-ea4c-4b92-b4cb-20026a13d663", + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_element_test.py b/tests/unit/frbc/frbc_usage_forecast_element_test.py index 4865abd..3df7f63 100644 --- a/tests/unit/frbc/frbc_usage_forecast_element_test.py +++ b/tests/unit/frbc/frbc_usage_forecast_element_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,14 +12,14 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "duration": 5364, - "usage_rate_upper_limit": 2449.222540239615, - "usage_rate_upper_95PPR": 1833.1006975798746, - "usage_rate_upper_68PPR": 7339.63634801623, - "usage_rate_expected": 2939.725042558339, - "usage_rate_lower_68PPR": 4643.821202571105, - "usage_rate_lower_95PPR": 1244.2496151489897, - "usage_rate_lower_limit": 5214.934978137386 + "duration": 9317, + "usage_rate_expected": 866.9362374046218, + "usage_rate_lower_68PPR": 3496.6233093198375, + "usage_rate_lower_95PPR": 4206.0536932975065, + "usage_rate_lower_limit": 7353.272756502293, + "usage_rate_upper_68PPR": 5124.8129813156465, + "usage_rate_upper_95PPR": 264.3386978845277, + "usage_rate_upper_limit": 4474.174577002476 } """ @@ -28,29 +27,57 @@ def test__from_json__happy_path_full(self): frbc_usage_forecast_element = FRBCUsageForecastElement.from_json(json_str) # Assert - self.assertEqual(frbc_usage_forecast_element.duration, Duration.from_timedelta(timedelta(milliseconds=5364))) - self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_limit, 2449.222540239615) - self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_95PPR, 1833.1006975798746) - self.assertEqual(frbc_usage_forecast_element.usage_rate_upper_68PPR, 7339.63634801623) - self.assertEqual(frbc_usage_forecast_element.usage_rate_expected, 2939.725042558339) - self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_68PPR, 4643.821202571105) - self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_95PPR, 1244.2496151489897) - self.assertEqual(frbc_usage_forecast_element.usage_rate_lower_limit, 5214.934978137386) + self.assertEqual( + frbc_usage_forecast_element.duration, + Duration.from_timedelta(timedelta(milliseconds=9317)), + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_expected, 866.9362374046218 + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_lower_68PPR, 3496.6233093198375 + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_lower_95PPR, 4206.0536932975065 + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_lower_limit, 7353.272756502293 + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_upper_68PPR, 5124.8129813156465 + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_upper_95PPR, 264.3386978845277 + ) + self.assertEqual( + frbc_usage_forecast_element.usage_rate_upper_limit, 4474.174577002476 + ) def test__to_json__happy_path_full(self): # Arrange - frbc_usage_forecast_element = FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=5364)), usage_rate_upper_limit=2449.222540239615, usage_rate_upper_95PPR=1833.1006975798746, usage_rate_upper_68PPR=7339.63634801623, usage_rate_expected=2939.725042558339, usage_rate_lower_68PPR=4643.821202571105, usage_rate_lower_95PPR=1244.2496151489897, usage_rate_lower_limit=5214.934978137386) + frbc_usage_forecast_element = FRBCUsageForecastElement( + duration=Duration.from_timedelta(timedelta(milliseconds=9317)), + usage_rate_expected=866.9362374046218, + usage_rate_lower_68PPR=3496.6233093198375, + usage_rate_lower_95PPR=4206.0536932975065, + usage_rate_lower_limit=7353.272756502293, + usage_rate_upper_68PPR=5124.8129813156465, + usage_rate_upper_95PPR=264.3386978845277, + usage_rate_upper_limit=4474.174577002476, + ) # Act json_str = frbc_usage_forecast_element.to_json() # Assert - expected_json = { 'duration': 5364, - 'usage_rate_expected': 2939.725042558339, - 'usage_rate_lower_68PPR': 4643.821202571105, - 'usage_rate_lower_95PPR': 1244.2496151489897, - 'usage_rate_lower_limit': 5214.934978137386, - 'usage_rate_upper_68PPR': 7339.63634801623, - 'usage_rate_upper_95PPR': 1833.1006975798746, - 'usage_rate_upper_limit': 2449.222540239615} + expected_json = { + "duration": 9317, + "usage_rate_expected": 866.9362374046218, + "usage_rate_lower_68PPR": 3496.6233093198375, + "usage_rate_lower_95PPR": 4206.0536932975065, + "usage_rate_lower_limit": 7353.272756502293, + "usage_rate_upper_68PPR": 5124.8129813156465, + "usage_rate_upper_95PPR": 264.3386978845277, + "usage_rate_upper_limit": 4474.174577002476, + } self.assertEqual(json.loads(json_str), expected_json) diff --git a/tests/unit/frbc/frbc_usage_forecast_test.py b/tests/unit/frbc/frbc_usage_forecast_test.py index 6d40f99..a7f0da1 100644 --- a/tests/unit/frbc/frbc_usage_forecast_test.py +++ b/tests/unit/frbc/frbc_usage_forecast_test.py @@ -1,4 +1,3 @@ - from datetime import timedelta, datetime, timezone as offset import json from unittest import TestCase @@ -13,21 +12,21 @@ def test__from_json__happy_path_full(self): # Arrange json_str = """ { - "message_type": "FRBC.UsageForecast", - "message_id": "251e2ef9-31d5-40a0-bbf6-8331742f18ad", - "start_time": "2021-09-09T03:50:01-01:00", "elements": [ { - "duration": 1074, - "usage_rate_upper_limit": 3368.642281190435, - "usage_rate_upper_95PPR": 6948.368971520732, - "usage_rate_upper_68PPR": 1917.1869702535832, - "usage_rate_expected": 3804.5466318560825, - "usage_rate_lower_68PPR": 7641.102737197381, - "usage_rate_lower_95PPR": 1017.0167039060763, - "usage_rate_lower_limit": 1775.7219717302214 + "duration": 14010, + "usage_rate_expected": 8032.572599815139, + "usage_rate_lower_68PPR": 3910.197692207213, + "usage_rate_lower_95PPR": 6541.633895752248, + "usage_rate_lower_limit": 3419.1709124422173, + "usage_rate_upper_68PPR": 7146.0702352976305, + "usage_rate_upper_95PPR": 627.7040858037238, + "usage_rate_upper_limit": 8477.800850190179 } - ] + ], + "message_id": "4a91b4ab-21fb-42ae-b97d-6170f8b922cc", + "message_type": "FRBC.UsageForecast", + "start_time": "2023-03-25T13:48:35+02:00" } """ @@ -35,28 +34,86 @@ def test__from_json__happy_path_full(self): frbc_usage_forecast = FRBCUsageForecast.from_json(json_str) # Assert - self.assertEqual(frbc_usage_forecast.message_type, FRBC.UsageForecast) - self.assertEqual(frbc_usage_forecast.message_id, uuid.UUID("251e2ef9-31d5-40a0-bbf6-8331742f18ad")) - self.assertEqual(frbc_usage_forecast.start_time, datetime(year=2021, month=9, day=9, hour=3, minute=50, second=1, tzinfo=offset(offset=timedelta(seconds=-3600.0)))) - self.assertEqual(frbc_usage_forecast.elements, [FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=1074)), usage_rate_upper_limit=3368.642281190435, usage_rate_upper_95PPR=6948.368971520732, usage_rate_upper_68PPR=1917.1869702535832, usage_rate_expected=3804.5466318560825, usage_rate_lower_68PPR=7641.102737197381, usage_rate_lower_95PPR=1017.0167039060763, usage_rate_lower_limit=1775.7219717302214)]) + self.assertEqual( + frbc_usage_forecast.elements, + [ + FRBCUsageForecastElement( + duration=Duration.from_timedelta(timedelta(milliseconds=14010)), + usage_rate_expected=8032.572599815139, + usage_rate_lower_68PPR=3910.197692207213, + usage_rate_lower_95PPR=6541.633895752248, + usage_rate_lower_limit=3419.1709124422173, + usage_rate_upper_68PPR=7146.0702352976305, + usage_rate_upper_95PPR=627.7040858037238, + usage_rate_upper_limit=8477.800850190179, + ) + ], + ) + self.assertEqual( + frbc_usage_forecast.message_id, + uuid.UUID("4a91b4ab-21fb-42ae-b97d-6170f8b922cc"), + ) + self.assertEqual(frbc_usage_forecast.message_type, "FRBC.UsageForecast") + self.assertEqual( + frbc_usage_forecast.start_time, + datetime( + year=2023, + month=3, + day=25, + hour=13, + minute=48, + second=35, + tzinfo=offset(offset=timedelta(seconds=7200.0)), + ), + ) def test__to_json__happy_path_full(self): # Arrange - frbc_usage_forecast = FRBCUsageForecast(message_type=FRBC.UsageForecast, message_id=uuid.UUID("251e2ef9-31d5-40a0-bbf6-8331742f18ad"), start_time=datetime(year=2021, month=9, day=9, hour=3, minute=50, second=1, tzinfo=offset(offset=timedelta(seconds=-3600.0))), elements=[FRBCUsageForecastElement(duration=Duration.from_timedelta(timedelta(milliseconds=1074)), usage_rate_upper_limit=3368.642281190435, usage_rate_upper_95PPR=6948.368971520732, usage_rate_upper_68PPR=1917.1869702535832, usage_rate_expected=3804.5466318560825, usage_rate_lower_68PPR=7641.102737197381, usage_rate_lower_95PPR=1017.0167039060763, usage_rate_lower_limit=1775.7219717302214)]) + frbc_usage_forecast = FRBCUsageForecast( + elements=[ + FRBCUsageForecastElement( + duration=Duration.from_timedelta(timedelta(milliseconds=14010)), + usage_rate_expected=8032.572599815139, + usage_rate_lower_68PPR=3910.197692207213, + usage_rate_lower_95PPR=6541.633895752248, + usage_rate_lower_limit=3419.1709124422173, + usage_rate_upper_68PPR=7146.0702352976305, + usage_rate_upper_95PPR=627.7040858037238, + usage_rate_upper_limit=8477.800850190179, + ) + ], + message_id=uuid.UUID("4a91b4ab-21fb-42ae-b97d-6170f8b922cc"), + message_type="FRBC.UsageForecast", + start_time=datetime( + year=2023, + month=3, + day=25, + hour=13, + minute=48, + second=35, + tzinfo=offset(offset=timedelta(seconds=7200.0)), + ), + ) # Act json_str = frbc_usage_forecast.to_json() # Assert - expected_json = { 'elements': [ { 'duration': 1074, - 'usage_rate_expected': 3804.5466318560825, - 'usage_rate_lower_68PPR': 7641.102737197381, - 'usage_rate_lower_95PPR': 1017.0167039060763, - 'usage_rate_lower_limit': 1775.7219717302214, - 'usage_rate_upper_68PPR': 1917.1869702535832, - 'usage_rate_upper_95PPR': 6948.368971520732, - 'usage_rate_upper_limit': 3368.642281190435}], - 'message_id': '251e2ef9-31d5-40a0-bbf6-8331742f18ad', - 'message_type': 'FRBC.UsageForecast', - 'start_time': '2021-09-09T03:50:01-01:00'} + expected_json = { + "elements": [ + { + "duration": 14010, + "usage_rate_expected": 8032.572599815139, + "usage_rate_lower_68PPR": 3910.197692207213, + "usage_rate_lower_95PPR": 6541.633895752248, + "usage_rate_lower_limit": 3419.1709124422173, + "usage_rate_upper_68PPR": 7146.0702352976305, + "usage_rate_upper_95PPR": 627.7040858037238, + "usage_rate_upper_limit": 8477.800850190179, + } + ], + "message_id": "4a91b4ab-21fb-42ae-b97d-6170f8b922cc", + "message_type": "FRBC.UsageForecast", + "start_time": "2023-03-25T13:48:35+02:00", + } self.assertEqual(json.loads(json_str), expected_json) From db2cd802bee878923b87e6b03a60fd74a5009fc6 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 16:21:32 +0200 Subject: [PATCH 18/27] Update pylint configuration to disable W0511 for TODOs and fix type ignore comments in PPBCPowerSequenceContainerStatu --- .pylintrc | 3 ++- .../ppbc/ppbc_power_sequence_container_status.py | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.pylintrc b/.pylintrc index b0bfeed..7a21ddf 100644 --- a/.pylintrc +++ b/.pylintrc @@ -10,7 +10,8 @@ ignore-paths=src/s2python/generated/ # avoid hangs. jobs=1 -disable=missing-class-docstring,missing-module-docstring,too-few-public-methods,missing-function-docstring,no-member +# W0511: TODOs +disable=missing-class-docstring,missing-module-docstring,too-few-public-methods,missing-function-docstring,no-member, W0511 [Format] max-line-length=120 diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index 55c41f1..d6ca4fe 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -13,20 +13,20 @@ @catch_and_convert_exceptions class PPBCPowerSequenceContainerStatus( - GenPPBCPowerSequenceContainerStatus, S2Message["PPBCPowerProfileDefinitionStatus"] + GenPPBCPowerSequenceContainerStatus, S2Message["PPBCPowerSequenceContainerStatus"] ): model_config = GenPPBCPowerSequenceContainerStatus.model_config model_config["validate_assignment"] = True power_profile_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ - "power_profile_id" + "power_profile_id" # type: ignore[assignment] ] sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ - "sequence_container_id" + "sequence_container_id" # type: ignore[assignment] ] selected_sequence_id: uuid.UUID | None = ( - GenPPBCPowerSequenceContainerStatus.model_fields["selected_sequence_id"] + GenPPBCPowerSequenceContainerStatus.model_fields["selected_sequence_id"] # type: ignore[assignment] ) progress: Duration | None = GenPPBCPowerSequenceContainerStatus.model_fields[ - "progress" + "progress" # type: ignore[assignment] ] From a77bae7e6e1c8ef75c4b01fe20f014882869c295 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 16:24:12 +0200 Subject: [PATCH 19/27] Refactor type annotations in PPBCPowerSequenceContainerStatus to use Union for optional fields --- src/s2python/ppbc/ppbc_power_sequence_container_status.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index d6ca4fe..6959ee3 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -1,4 +1,5 @@ import uuid +from typing import Union from s2python.generated.gen_s2 import ( PPBCPowerSequenceContainerStatus as GenPPBCPowerSequenceContainerStatus, @@ -24,9 +25,9 @@ class PPBCPowerSequenceContainerStatus( sequence_container_id: uuid.UUID = GenPPBCPowerSequenceContainerStatus.model_fields[ "sequence_container_id" # type: ignore[assignment] ] - selected_sequence_id: uuid.UUID | None = ( + selected_sequence_id: Union[uuid.UUID, None] = ( GenPPBCPowerSequenceContainerStatus.model_fields["selected_sequence_id"] # type: ignore[assignment] ) - progress: Duration | None = GenPPBCPowerSequenceContainerStatus.model_fields[ + progress: Union[uuid.UUID, None] = GenPPBCPowerSequenceContainerStatus.model_fields[ "progress" # type: ignore[assignment] ] From 258c7ed7620dede31dababda40acae9b0aa1229d Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 16:27:19 +0200 Subject: [PATCH 20/27] Add type ignore comments for assignment in PPBC model fields --- src/s2python/ppbc/ppbc_end_interruption_instruction.py | 10 +++++----- src/s2python/ppbc/ppbc_power_profile_definition.py | 6 +++--- src/s2python/ppbc/ppbc_power_profile_status.py | 2 +- src/s2python/ppbc/ppbc_power_sequence.py | 10 +++++----- src/s2python/ppbc/ppbc_power_sequence_container.py | 4 ++-- src/s2python/ppbc/ppbc_power_sequence_element.py | 4 ++-- .../ppbc/ppbc_start_interruption_instruction.py | 10 +++++----- 7 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/s2python/ppbc/ppbc_end_interruption_instruction.py b/src/s2python/ppbc/ppbc_end_interruption_instruction.py index a27ccba..d53c527 100644 --- a/src/s2python/ppbc/ppbc_end_interruption_instruction.py +++ b/src/s2python/ppbc/ppbc_end_interruption_instruction.py @@ -17,16 +17,16 @@ class PPBCEndInterruptionInstruction( model_config = GenPPBCEndInterruptionInstruction.model_config model_config["validate_assignment"] = True - id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields["id"] + id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields["id"] # type: ignore[assignment] power_profile_id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields[ "power_profile_id" - ] + ] # type: ignore[assignment] sequence_container_id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields[ "sequence_container_id" - ] + ] # type: ignore[assignment] power_sequence_id: uuid.UUID = GenPPBCEndInterruptionInstruction.model_fields[ "power_sequence_id" - ] + ] # type: ignore[assignment] abnormal_condition: bool = GenPPBCEndInterruptionInstruction.model_fields[ "abnormal_condition" - ] + ] # type: ignore[assignment] diff --git a/src/s2python/ppbc/ppbc_power_profile_definition.py b/src/s2python/ppbc/ppbc_power_profile_definition.py index b57ccc0..cc4ba6a 100644 --- a/src/s2python/ppbc/ppbc_power_profile_definition.py +++ b/src/s2python/ppbc/ppbc_power_profile_definition.py @@ -20,8 +20,8 @@ class PPBCPowerProfileDefinition( model_config = GenPPBCPowerProfileDefinition.model_config model_config["validate_assignment"] = True - message_id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["message_id"] - id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["id"] + message_id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["message_id"] # type: ignore[assignment] + id: uuid.UUID = GenPPBCPowerProfileDefinition.model_fields["id"] # type: ignore[assignment] power_sequences_containers: List[PPBCPowerSequenceContainer] = ( - GenPPBCPowerProfileDefinition.model_fields["power_sequences_containers"] + GenPPBCPowerProfileDefinition.model_fields["power_sequences_containers"] # type: ignore[assignment] ) diff --git a/src/s2python/ppbc/ppbc_power_profile_status.py b/src/s2python/ppbc/ppbc_power_profile_status.py index d7e3635..d44f7e2 100644 --- a/src/s2python/ppbc/ppbc_power_profile_status.py +++ b/src/s2python/ppbc/ppbc_power_profile_status.py @@ -22,5 +22,5 @@ class PPBCPowerProfileStatus( model_config["validate_assignment"] = True sequence_container_status: List[PPBCPowerSequenceContainerStatus] = ( - GenPPBCPowerProfileStatus.model_fields["sequence_container_status"] + GenPPBCPowerProfileStatus.model_fields["sequence_container_status"] # type: ignore[assignment] ) diff --git a/src/s2python/ppbc/ppbc_power_sequence.py b/src/s2python/ppbc/ppbc_power_sequence.py index 2c22cd9..95e2758 100644 --- a/src/s2python/ppbc/ppbc_power_sequence.py +++ b/src/s2python/ppbc/ppbc_power_sequence.py @@ -19,12 +19,12 @@ class PPBCPowerSequence(GenPPBCPowerSequence, S2Message["PPBCPowerSequence"]): model_config = GenPPBCPowerSequence.model_config model_config["validate_assignment"] = True - id: uuid.UUID = GenPPBCPowerSequence.model_fields["id"] + id: uuid.UUID = GenPPBCPowerSequence.model_fields["id"] # type: ignore[assignment] elements: List[PPBCPowerSequenceElement] = GenPPBCPowerSequence.model_fields[ "elements" - ] - is_interruptible: bool = GenPPBCPowerSequence.model_fields["is_interruptible"] - max_pause_before: Duration = GenPPBCPowerSequence.model_fields["max_pause_before"] + ] # type: ignore[assignment] + is_interruptible: bool = GenPPBCPowerSequence.model_fields["is_interruptible"] # type: ignore[assignment] + max_pause_before: Duration = GenPPBCPowerSequence.model_fields["max_pause_before"] # type: ignore[assignment] abnormal_condition_only: bool = GenPPBCPowerSequence.model_fields[ "abnormal_condition_only" - ] + ] # type: ignore[assignment] diff --git a/src/s2python/ppbc/ppbc_power_sequence_container.py b/src/s2python/ppbc/ppbc_power_sequence_container.py index 44cb4d8..3a11163 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container.py @@ -21,7 +21,7 @@ class PPBCPowerSequenceContainer( model_config = GenPPBCPowerSequenceContainer.model_config model_config["validate_assignment"] = True - id: uuid.UUID = GenPPBCPowerSequenceContainer.model_fields["id"] + id: uuid.UUID = GenPPBCPowerSequenceContainer.model_fields["id"] # type: ignore[assignment] power_sequences: List[PPBCPowerSequence] = ( - GenPPBCPowerSequenceContainer.model_fields["power_sequences"] + GenPPBCPowerSequenceContainer.model_fields["power_sequences"] # type: ignore[assignment] ) diff --git a/src/s2python/ppbc/ppbc_power_sequence_element.py b/src/s2python/ppbc/ppbc_power_sequence_element.py index c3c858a..1372063 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_element.py +++ b/src/s2python/ppbc/ppbc_power_sequence_element.py @@ -19,7 +19,7 @@ class PPBCPowerSequenceElement( model_config = GenPPBCPowerSequenceElement.model_config model_config["validate_assignment"] = True - duration: Duration = GenPPBCPowerSequenceElement.model_fields["duration"] + duration: Duration = GenPPBCPowerSequenceElement.model_fields["duration"] # type: ignore[assignment] power_values: List[PowerForecastValue] = GenPPBCPowerSequenceElement.model_fields[ "power_values" - ] + ] # type: ignore[assignment] diff --git a/src/s2python/ppbc/ppbc_start_interruption_instruction.py b/src/s2python/ppbc/ppbc_start_interruption_instruction.py index 0390b49..f6d25ff 100644 --- a/src/s2python/ppbc/ppbc_start_interruption_instruction.py +++ b/src/s2python/ppbc/ppbc_start_interruption_instruction.py @@ -17,16 +17,16 @@ class PPBCStartInterruptionInstruction( model_config = GenPPBCStartInterruptionInstruction.model_config model_config["validate_assignment"] = True - id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields["id"] + id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields["id"] # type: ignore[assignment] power_profile_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ "power_profile_id" - ] + ] # type: ignore[assignment] sequence_container_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ "sequence_container_id" - ] + ] # type: ignore[assignment] power_sequence_id: uuid.UUID = GenPPBCStartInterruptionInstruction.model_fields[ "power_sequence_id" - ] + ] # type: ignore[assignment] abnormal_condition: bool = GenPPBCStartInterruptionInstruction.model_fields[ "abnormal_condition" - ] + ] # type: ignore[assignment] From 2f68190d2a332f2b8a561e50ec92e7e07c9e5090 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Sat, 4 Jan 2025 16:28:10 +0200 Subject: [PATCH 21/27] Remove unused import of Duration in ppbc_power_sequence_container_status.py --- src/s2python/ppbc/ppbc_power_sequence_container_status.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/s2python/ppbc/ppbc_power_sequence_container_status.py b/src/s2python/ppbc/ppbc_power_sequence_container_status.py index 6959ee3..624e4d6 100644 --- a/src/s2python/ppbc/ppbc_power_sequence_container_status.py +++ b/src/s2python/ppbc/ppbc_power_sequence_container_status.py @@ -9,7 +9,6 @@ S2Message, catch_and_convert_exceptions, ) -from s2python.common import Duration @catch_and_convert_exceptions From 24c4f5ba322e52244ce854c0454a0f16518f6a84 Mon Sep 17 00:00:00 2001 From: Felix Claessen <30658763+Flix6x@users.noreply.github.com> Date: Mon, 13 Jan 2025 12:37:48 +0100 Subject: [PATCH 22/27] Update src/s2python/s2_control_type.py --- src/s2python/s2_control_type.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index eaea081..49c9da4 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -40,9 +40,10 @@ def handle_instruction( @abc.abstractmethod def activate(self, conn: "S2Connection") -> None: ... - # TODO @abc.abstractmethod - def deactivate(self, conn: "S2Connection") -> None: ... + def deactivate(self, conn: "S2Connection") -> None: + """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type.""" + pass class PPBCControlType(S2ControlType): From 10cd8c2236b9d59230d89b1965d5454f76526f4d Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 13 Jan 2025 12:40:34 +0100 Subject: [PATCH 23/27] fix: replace generic TODOs with developer instructions Signed-off-by: F.N. Claessen --- src/s2python/s2_control_type.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index 49c9da4..e0fa15d 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -36,9 +36,10 @@ def handle_instruction( self, conn: "S2Connection", msg: S2Message, send_okay: typing.Callable[[], None] ) -> None: ... - # TODO @abc.abstractmethod - def activate(self, conn: "S2Connection") -> None: ... + def activate(self, conn: "S2Connection") -> None: + """Overwrite with the actual dctivation logic of your Resource Manager for this particular control type.""" + pass @abc.abstractmethod def deactivate(self, conn: "S2Connection") -> None: @@ -58,13 +59,15 @@ def handle_instruction( self, conn: "S2Connection", msg: S2Message, send_okay: typing.Callable[[], None] ) -> None: ... - # TODO @abc.abstractmethod - def activate(self, conn: "S2Connection") -> None: ... + def activate(self, conn: "S2Connection") -> None: + """Overwrite with the actual dctivation logic of your Resource Manager for this particular control type.""" + pass - # TODO @abc.abstractmethod - def deactivate(self, conn: "S2Connection") -> None: ... + def deactivate(self, conn: "S2Connection") -> None: + """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type.""" + pass class NoControlControlType(S2ControlType): From f2e8ffa5ff82f7b79979504807a5318a5b3fa367 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 13 Jan 2025 12:42:53 +0100 Subject: [PATCH 24/27] chore: check for generic TODOs Signed-off-by: F.N. Claessen --- .pylintrc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pylintrc b/.pylintrc index 7a21ddf..b0bfeed 100644 --- a/.pylintrc +++ b/.pylintrc @@ -10,8 +10,7 @@ ignore-paths=src/s2python/generated/ # avoid hangs. jobs=1 -# W0511: TODOs -disable=missing-class-docstring,missing-module-docstring,too-few-public-methods,missing-function-docstring,no-member, W0511 +disable=missing-class-docstring,missing-module-docstring,too-few-public-methods,missing-function-docstring,no-member [Format] max-line-length=120 From 3bec6e2459279764595422ca5d6d6fc69dace592 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 13 Jan 2025 12:47:01 +0100 Subject: [PATCH 25/27] chore: pylint W0107 Signed-off-by: F.N. Claessen --- src/s2python/s2_control_type.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index e0fa15d..f645e5a 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -39,12 +39,10 @@ def handle_instruction( @abc.abstractmethod def activate(self, conn: "S2Connection") -> None: """Overwrite with the actual dctivation logic of your Resource Manager for this particular control type.""" - pass @abc.abstractmethod def deactivate(self, conn: "S2Connection") -> None: """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type.""" - pass class PPBCControlType(S2ControlType): @@ -60,14 +58,12 @@ def handle_instruction( ) -> None: ... @abc.abstractmethod - def activate(self, conn: "S2Connection") -> None: + def activate(self, conn: "S2Connection") -> None: ... """Overwrite with the actual dctivation logic of your Resource Manager for this particular control type.""" - pass @abc.abstractmethod def deactivate(self, conn: "S2Connection") -> None: """Overwrite with the actual deactivation logic of your Resource Manager for this particular control type.""" - pass class NoControlControlType(S2ControlType): From 5518a22a64e79ebd4e09965b3828551e0f270453 Mon Sep 17 00:00:00 2001 From: "F.N. Claessen" Date: Mon, 13 Jan 2025 12:51:46 +0100 Subject: [PATCH 26/27] chore: pylint W0107 again Signed-off-by: F.N. Claessen --- src/s2python/s2_control_type.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index f645e5a..d99b8bc 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -39,10 +39,12 @@ def handle_instruction( @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 PPBCControlType(S2ControlType): @@ -58,12 +60,14 @@ def handle_instruction( ) -> None: ... @abc.abstractmethod - def activate(self, conn: "S2Connection") -> None: ... + 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 NoControlControlType(S2ControlType): From a79e921f542d830c0a06c364cb28b48a95867f44 Mon Sep 17 00:00:00 2001 From: Vlad Iftime Date: Mon, 13 Jan 2025 12:55:20 +0100 Subject: [PATCH 27/27] W2301 from pylint fix --- src/s2python/s2_control_type.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/s2python/s2_control_type.py b/src/s2python/s2_control_type.py index d99b8bc..43f3b8e 100644 --- a/src/s2python/s2_control_type.py +++ b/src/s2python/s2_control_type.py @@ -39,12 +39,10 @@ def handle_instruction( @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 PPBCControlType(S2ControlType): @@ -62,12 +60,10 @@ def handle_instruction( @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 NoControlControlType(S2ControlType):