1717from frequenz .api .microgrid .common_pb2 import ErrorLevel
1818from frequenz .api .microgrid .inverter_pb2 import ComponentState as InverterComponentState
1919from frequenz .channels import Receiver , Sender
20- from frequenz .channels .util import Select , Timer
20+ from frequenz .channels .util import Timer , select , selected_from
2121
2222from frequenz .sdk ._internal ._asyncio import cancel_and_await
2323from frequenz .sdk .microgrid import connection_manager
@@ -197,14 +197,15 @@ def __init__( # pylint: disable=too-many-arguments
197197 raise RuntimeError (f"Can't find inverter adjacent to battery: { battery_id } " )
198198
199199 self ._battery : _ComponentStreamStatus = _ComponentStreamStatus (
200- battery_id , data_recv_timer = Timer (max_data_age_sec )
200+ battery_id ,
201+ data_recv_timer = Timer .timeout (timedelta (max_data_age_sec )),
201202 )
202203 self ._inverter : _ComponentStreamStatus = _ComponentStreamStatus (
203- inverter_id , data_recv_timer = Timer (max_data_age_sec )
204+ inverter_id ,
205+ data_recv_timer = Timer .timeout (timedelta (max_data_age_sec )),
204206 )
205207
206208 # Select needs receivers that can be get in async way only.
207- self ._select : Select | None = None
208209
209210 self ._task : asyncio .Task [None ] = asyncio .create_task (
210211 self ._run (status_sender , set_power_result_receiver )
@@ -223,8 +224,70 @@ async def stop(self) -> None:
223224 """Stop tracking battery status."""
224225 await cancel_and_await (self ._task )
225226
226- if self ._select is not None :
227- await self ._select .stop ()
227+ def _handle_status_battery (self , bat_data : BatteryData ) -> None :
228+ self ._battery .last_msg_correct = (
229+ self ._is_message_reliable (bat_data )
230+ and self ._is_battery_state_correct (bat_data )
231+ and self ._no_critical_error (bat_data )
232+ and self ._is_capacity_present (bat_data )
233+ )
234+ self ._battery .last_msg_timestamp = bat_data .timestamp
235+ self ._battery .data_recv_timer .reset ()
236+
237+ def _handle_status_inverter (self , inv_data : InverterData ) -> None :
238+ self ._inverter .last_msg_correct = (
239+ self ._is_message_reliable (inv_data )
240+ and self ._is_inverter_state_correct (inv_data )
241+ and self ._no_critical_error (inv_data )
242+ )
243+ self ._inverter .last_msg_timestamp = inv_data .timestamp
244+ self ._inverter .data_recv_timer .reset ()
245+
246+ def _handle_status_set_power_result (self , result : SetPowerResult ) -> None :
247+ if self .battery_id in result .succeed :
248+ self ._blocking_status .unblock ()
249+
250+ elif (
251+ self .battery_id in result .failed and self ._last_status != Status .NOT_WORKING
252+ ):
253+ duration = self ._blocking_status .block ()
254+
255+ if duration > 0 :
256+ _logger .warning (
257+ "battery %d failed last response. block it for %f sec" ,
258+ self .battery_id ,
259+ duration ,
260+ )
261+
262+ def _handle_status_battery_timer (self ) -> None :
263+ if self ._battery .last_msg_correct :
264+ self ._battery .last_msg_correct = False
265+ _logger .warning (
266+ "Battery %d stopped sending data, last timestamp: %s" ,
267+ self ._battery .component_id ,
268+ self ._battery .last_msg_timestamp ,
269+ )
270+
271+ def _handle_status_inverter_timer (self ) -> None :
272+ if self ._inverter .last_msg_correct :
273+ self ._inverter .last_msg_correct = False
274+ _logger .warning (
275+ "Inverter %d stopped sending data, last timestamp: %s" ,
276+ self ._inverter .component_id ,
277+ self ._inverter .last_msg_timestamp ,
278+ )
279+
280+ def _get_new_status_if_changed (self ) -> Optional [Status ]:
281+ current_status = self ._get_current_status ()
282+ if self ._last_status != current_status :
283+ self ._last_status = current_status
284+ _logger .info (
285+ "battery %d changed status %s" ,
286+ self .battery_id ,
287+ str (self ._last_status ),
288+ )
289+ return current_status
290+ return None
228291
229292 async def _run (
230293 self ,
@@ -245,93 +308,48 @@ async def _run(
245308 battery_receiver = await api_client .battery_data (self ._battery .component_id )
246309 inverter_receiver = await api_client .inverter_data (self ._inverter .component_id )
247310
248- self ._select = Select (
249- battery = battery_receiver ,
250- battery_timer = self ._battery .data_recv_timer ,
251- inverter_timer = self ._inverter .data_recv_timer ,
252- inverter = inverter_receiver ,
253- set_power_result = set_power_result_receiver ,
254- )
311+ battery = battery_receiver
312+ battery_timer = self ._battery .data_recv_timer
313+ inverter_timer = self ._inverter .data_recv_timer
314+ inverter = inverter_receiver
315+ set_power_result = set_power_result_receiver
255316
256317 while True :
257318 try :
258- while await self ._select .ready ():
259- new_status = self ._update_status (self ._select )
319+ async for selected in select (
320+ battery ,
321+ battery_timer ,
322+ inverter_timer ,
323+ inverter ,
324+ set_power_result ,
325+ ):
326+ new_status = None
260327
261- if new_status is not None :
262- await status_sender . send ( new_status )
328+ if selected_from ( selected , battery ) :
329+ self . _handle_status_battery ( selected . value )
263330
264- except Exception as err : # pylint: disable=broad-except
265- _logger . exception ( "BatteryStatusTracker crashed with error: %s" , err )
331+ elif selected_from ( selected , inverter ):
332+ self . _handle_status_inverter ( selected . value )
266333
267- def _update_status (self , select : Select ) -> Optional [Status ]:
268- if msg := select .battery :
269- self ._battery .last_msg_correct = (
270- self ._is_message_reliable (msg .inner )
271- and self ._is_battery_state_correct (msg .inner )
272- and self ._no_critical_error (msg .inner )
273- and self ._is_capacity_present (msg .inner )
274- )
275- self ._battery .last_msg_timestamp = msg .inner .timestamp
276- self ._battery .data_recv_timer .reset ()
277-
278- elif msg := select .inverter :
279- self ._inverter .last_msg_correct = (
280- self ._is_message_reliable (msg .inner )
281- and self ._is_inverter_state_correct (msg .inner )
282- and self ._no_critical_error (msg .inner )
283- )
284- self ._inverter .last_msg_timestamp = msg .inner .timestamp
285- self ._inverter .data_recv_timer .reset ()
286-
287- elif msg := select .set_power_result :
288- result : SetPowerResult = msg .inner
289- if self .battery_id in result .succeed :
290- self ._blocking_status .unblock ()
291-
292- elif (
293- self .battery_id in result .failed
294- and self ._last_status != Status .NOT_WORKING
295- ):
296- duration = self ._blocking_status .block ()
297-
298- if duration > 0 :
299- _logger .warning (
300- "battery %d failed last response. block it for %f sec" ,
301- self .battery_id ,
302- duration ,
303- )
304- elif msg := select .battery_timer :
305- if self ._battery .last_msg_correct :
306- self ._battery .last_msg_correct = False
307- _logger .warning (
308- "Battery %d stopped sending data, last timestamp: %s" ,
309- self ._battery .component_id ,
310- self ._battery .last_msg_timestamp ,
311- )
312- elif msg := select .inverter_timer :
313- if self ._inverter .last_msg_correct :
314- self ._inverter .last_msg_correct = False
315- _logger .warning (
316- "Inverter %d stopped sending data, last timestamp: %s" ,
317- self ._inverter .component_id ,
318- self ._inverter .last_msg_timestamp ,
319- )
334+ elif selected_from (selected , set_power_result ):
335+ self ._handle_status_set_power_result (selected .value )
320336
321- else :
322- _logger . error ( "Unknown message returned from select" )
337+ elif selected_from ( selected , battery_timer ) :
338+ self . _handle_status_battery_timer ( )
323339
324- current_status = self ._get_current_status ()
325- if self ._last_status != current_status :
326- self ._last_status = current_status
327- _logger .info (
328- "battery %d changed status %s" ,
329- self .battery_id ,
330- str (self ._last_status ),
331- )
332- return current_status
340+ elif selected_from (selected , inverter_timer ):
341+ self ._handle_status_inverter_timer ()
333342
334- return None
343+ else :
344+ _logger .error ("Unknown message returned from select" )
345+
346+ new_status = self ._get_new_status_if_changed ()
347+
348+ if new_status is not None :
349+ await status_sender .send (new_status )
350+
351+ except Exception as err : # pylint: disable=broad-except
352+ _logger .exception ("BatteryStatusTracker crashed with error: %s" , err )
335353
336354 def _get_current_status (self ) -> Status :
337355 """Get current battery status.
0 commit comments