|
| 1 | +from s2python.s2_asset_details import AssetDetails |
| 2 | +from s2python.s2_message_handlers import S2MessageHandler, SendOkay, MessageHandlers |
| 3 | + |
| 4 | +__all__ = ["AssetDetails", "S2MessageHandler", "SendOkay", "MessageHandlers", "S2Connection"] # re-export for backward compatibility |
| 5 | + |
1 | 6 | try: |
2 | 7 | import websockets |
3 | 8 | except ImportError as exc: |
|
12 | 17 | import threading |
13 | 18 | import uuid |
14 | 19 | import ssl |
15 | | -from dataclasses import dataclass |
16 | | -from typing import Any, Optional, List, Type, Dict, Callable, Awaitable, Union |
| 20 | +from typing import Any, Optional, List, Dict, Awaitable |
17 | 21 |
|
18 | 22 | from websockets.asyncio.client import ( |
19 | 23 | ClientConnection as WSConnection, |
|
25 | 29 | ReceptionStatus, |
26 | 30 | Handshake, |
27 | 31 | EnergyManagementRole, |
28 | | - Role, |
29 | 32 | HandshakeResponse, |
30 | | - ResourceManagerDetails, |
31 | | - Duration, |
32 | | - Currency, |
33 | 33 | SelectControlType, |
34 | 34 | ) |
35 | | -from s2python.generated.gen_s2 import CommodityQuantity |
36 | 35 | from s2python.reception_status_awaiter import ReceptionStatusAwaiter |
37 | 36 | from s2python.s2_control_type import S2ControlType |
38 | 37 | from s2python.s2_parser import S2Parser |
|
43 | 42 | logger = logging.getLogger("s2python") |
44 | 43 |
|
45 | 44 |
|
46 | | -@dataclass |
47 | | -class AssetDetails: # pylint: disable=too-many-instance-attributes |
48 | | - resource_id: uuid.UUID |
49 | | - |
50 | | - provides_forecast: bool |
51 | | - provides_power_measurements: List[CommodityQuantity] |
52 | | - |
53 | | - instruction_processing_delay: Duration |
54 | | - roles: List[Role] |
55 | | - currency: Optional[Currency] = None |
56 | | - |
57 | | - name: Optional[str] = None |
58 | | - manufacturer: Optional[str] = None |
59 | | - model: Optional[str] = None |
60 | | - firmware_version: Optional[str] = None |
61 | | - serial_number: Optional[str] = None |
62 | | - |
63 | | - def to_resource_manager_details( |
64 | | - self, control_types: List[S2ControlType] |
65 | | - ) -> ResourceManagerDetails: |
66 | | - return ResourceManagerDetails( |
67 | | - available_control_types=[ |
68 | | - control_type.get_protocol_control_type() |
69 | | - for control_type in control_types |
70 | | - ], |
71 | | - currency=self.currency, |
72 | | - firmware_version=self.firmware_version, |
73 | | - instruction_processing_delay=self.instruction_processing_delay, |
74 | | - manufacturer=self.manufacturer, |
75 | | - message_id=uuid.uuid4(), |
76 | | - model=self.model, |
77 | | - name=self.name, |
78 | | - provides_forecast=self.provides_forecast, |
79 | | - provides_power_measurement_types=self.provides_power_measurements, |
80 | | - resource_id=self.resource_id, |
81 | | - roles=self.roles, |
82 | | - serial_number=self.serial_number, |
83 | | - ) |
84 | | - |
85 | | - |
86 | | -S2MessageHandler = Union[ |
87 | | - Callable[["S2Connection", S2Message, Callable[[], None]], None], |
88 | | - Callable[["S2Connection", S2Message, Awaitable[None]], Awaitable[None]], |
89 | | -] |
90 | | - |
91 | | - |
92 | | -class SendOkay: |
93 | | - status_is_send: threading.Event |
94 | | - connection: "S2Connection" |
95 | | - subject_message_id: uuid.UUID |
96 | | - |
97 | | - def __init__(self, connection: "S2Connection", subject_message_id: uuid.UUID): |
98 | | - self.status_is_send = threading.Event() |
99 | | - self.connection = connection |
100 | | - self.subject_message_id = subject_message_id |
101 | | - |
102 | | - async def run_async(self) -> None: |
103 | | - self.status_is_send.set() |
104 | | - |
105 | | - await self.connection._respond_with_reception_status( # pylint: disable=protected-access |
106 | | - subject_message_id=self.subject_message_id, |
107 | | - status=ReceptionStatusValues.OK, |
108 | | - diagnostic_label="Processed okay.", |
109 | | - ) |
110 | | - |
111 | | - def run_sync(self) -> None: |
112 | | - self.status_is_send.set() |
113 | | - |
114 | | - self.connection.respond_with_reception_status_sync( |
115 | | - subject_message_id=self.subject_message_id, |
116 | | - status=ReceptionStatusValues.OK, |
117 | | - diagnostic_label="Processed okay.", |
118 | | - ) |
119 | | - |
120 | | - async def ensure_send_async(self, type_msg: Type[S2Message]) -> None: |
121 | | - if not self.status_is_send.is_set(): |
122 | | - logger.warning( |
123 | | - "Handler for message %s %s did not call send_okay / function to send the ReceptionStatus. " |
124 | | - "Sending it now.", |
125 | | - type_msg, |
126 | | - self.subject_message_id, |
127 | | - ) |
128 | | - await self.run_async() |
129 | | - |
130 | | - def ensure_send_sync(self, type_msg: Type[S2Message]) -> None: |
131 | | - if not self.status_is_send.is_set(): |
132 | | - logger.warning( |
133 | | - "Handler for message %s %s did not call send_okay / function to send the ReceptionStatus. " |
134 | | - "Sending it now.", |
135 | | - type_msg, |
136 | | - self.subject_message_id, |
137 | | - ) |
138 | | - self.run_sync() |
139 | | - |
140 | | - |
141 | | -class MessageHandlers: |
142 | | - handlers: Dict[Type[S2Message], S2MessageHandler] |
143 | | - |
144 | | - def __init__(self) -> None: |
145 | | - self.handlers = {} |
146 | | - |
147 | | - async def handle_message(self, connection: "S2Connection", msg: S2Message) -> None: |
148 | | - """Handle the S2 message using the registered handler. |
149 | | -
|
150 | | - :param connection: The S2 conncetion the `msg` is received from. |
151 | | - :param msg: The S2 message |
152 | | - """ |
153 | | - handler = self.handlers.get(type(msg)) |
154 | | - if handler is not None: |
155 | | - send_okay = SendOkay(connection, msg.message_id) # type: ignore[attr-defined, union-attr] |
156 | | - |
157 | | - try: |
158 | | - if asyncio.iscoroutinefunction(handler): |
159 | | - await handler(connection, msg, send_okay.run_async()) # type: ignore[arg-type] |
160 | | - await send_okay.ensure_send_async(type(msg)) |
161 | | - else: |
162 | | - |
163 | | - def do_message() -> None: |
164 | | - handler(connection, msg, send_okay.run_sync) # type: ignore[arg-type] |
165 | | - send_okay.ensure_send_sync(type(msg)) |
166 | | - |
167 | | - eventloop = asyncio.get_event_loop() |
168 | | - await eventloop.run_in_executor(executor=None, func=do_message) |
169 | | - except Exception: |
170 | | - if not send_okay.status_is_send.is_set(): |
171 | | - await connection._respond_with_reception_status( # pylint: disable=protected-access |
172 | | - subject_message_id=msg.message_id, # type: ignore[attr-defined, union-attr] |
173 | | - status=ReceptionStatusValues.PERMANENT_ERROR, |
174 | | - diagnostic_label=f"While processing message {msg.message_id} " # type: ignore[attr-defined, union-attr] # pylint: disable=line-too-long |
175 | | - f"an unrecoverable error occurred.", |
176 | | - ) |
177 | | - raise |
178 | | - else: |
179 | | - logger.warning( |
180 | | - "Received a message of type %s but no handler is registered. Ignoring the message.", |
181 | | - type(msg), |
182 | | - ) |
183 | | - |
184 | | - def register_handler( |
185 | | - self, msg_type: Type[S2Message], handler: S2MessageHandler |
186 | | - ) -> None: |
187 | | - """Register a coroutine function or a normal function as the handler for a specific S2 message type. |
188 | | -
|
189 | | - :param msg_type: The S2 message type to attach the handler to. |
190 | | - :param handler: The function (asynchronuous or normal) which should handle the S2 message. |
191 | | - """ |
192 | | - self.handlers[msg_type] = handler |
193 | | - |
194 | | - |
195 | 45 | class S2Connection: # pylint: disable=too-many-instance-attributes |
196 | 46 | url: str |
197 | 47 | reconnect: bool |
|
0 commit comments