1616from frequenz .api .microgrid .common_pb2 import ErrorLevel
1717from frequenz .api .microgrid .inverter_pb2 import ComponentState as InverterComponentState
1818from frequenz .channels import Receiver , Sender
19- from frequenz .channels .util import Select
19+ from frequenz .channels .util import Select , Timer
2020
2121from frequenz .sdk ._internal .asyncio import cancel_and_await
2222from frequenz .sdk .microgrid import get as get_microgrid
@@ -62,8 +62,16 @@ class SetPowerResult:
6262@dataclass
6363class _ComponentStreamStatus :
6464 component_id : int
65+ """Component id."""
66+
67+ data_recv_timer : Timer
68+ """Timer that is set when no component data has been received for some time."""
69+
6570 last_msg_timestamp : datetime = datetime .now (tz = timezone .utc )
71+ """Timestamp of the last message from the component."""
72+
6673 last_msg_correct : bool = False
74+ """Flag whether last message was correct or not."""
6775
6876
6977@dataclass
@@ -156,7 +164,7 @@ def __init__( # pylint: disable=too-many-arguments
156164 max_data_age_sec : float ,
157165 max_blocking_duration_sec : float ,
158166 status_sender : Sender [Status ],
159- request_result_receiver : Receiver [SetPowerResult ],
167+ set_power_result_receiver : Receiver [SetPowerResult ],
160168 ) -> None :
161169 """Create class instance.
162170
@@ -169,7 +177,7 @@ def __init__( # pylint: disable=too-many-arguments
169177 max_blocking_duration_sec: This value tell what should be the maximum
170178 timeout used for blocking failing component.
171179 status_sender: Channel to send status updates.
172- request_result_receiver : Channel to receive results of the requests to the
180+ set_power_result_receiver : Channel to receive results of the requests to the
173181 components.
174182
175183 Raises:
@@ -185,14 +193,18 @@ def __init__( # pylint: disable=too-many-arguments
185193 if inverter_id is None :
186194 raise RuntimeError (f"Can't find inverter adjacent to battery: { battery_id } " )
187195
188- self ._battery = _ComponentStreamStatus (battery_id )
189- self ._inverter = _ComponentStreamStatus (inverter_id )
196+ self ._battery = _ComponentStreamStatus (
197+ battery_id , data_recv_timer = Timer (max_data_age_sec )
198+ )
199+ self ._inverter = _ComponentStreamStatus (
200+ inverter_id , data_recv_timer = Timer (max_data_age_sec )
201+ )
190202
191203 # Select needs receivers that can be get in async way only.
192204 self ._select = None
193205
194206 self ._task = asyncio .create_task (
195- self ._run (status_sender , request_result_receiver )
207+ self ._run (status_sender , set_power_result_receiver )
196208 )
197209
198210 @property
@@ -214,15 +226,15 @@ async def stop(self) -> None:
214226 async def _run (
215227 self ,
216228 status_sender : Sender [Status ],
217- request_result_receiver : Receiver [SetPowerResult ],
229+ set_power_result_receiver : Receiver [SetPowerResult ],
218230 ) -> None :
219- """Process data from the components and request_result_receiver .
231+ """Process data from the components and set_power_result_receiver .
220232
221233 New status is send only when it change.
222234
223235 Args:
224236 status_sender: Channel to send status updates.
225- request_result_receiver : Channel to receive results of the requests to the
237+ set_power_result_receiver : Channel to receive results of the requests to the
226238 components.
227239 """
228240 api_client = get_microgrid ().api_client
@@ -232,8 +244,10 @@ async def _run(
232244
233245 self ._select = Select (
234246 battery = battery_receiver ,
247+ battery_timer = self ._battery .data_recv_timer ,
248+ inverter_timer = self ._inverter .data_recv_timer ,
235249 inverter = inverter_receiver ,
236- request_result = request_result_receiver ,
250+ set_power_result = set_power_result_receiver ,
237251 )
238252
239253 while True :
@@ -255,6 +269,7 @@ def _update_status(self, select: Select) -> Optional[Status]:
255269 and self ._no_critical_error (msg .inner )
256270 )
257271 self ._battery .last_msg_timestamp = msg .inner .timestamp
272+ self ._battery .data_recv_timer .reset ()
258273
259274 elif msg := select .inverter :
260275 self ._inverter .last_msg_correct = (
@@ -263,37 +278,42 @@ def _update_status(self, select: Select) -> Optional[Status]:
263278 and self ._no_critical_error (msg .inner )
264279 )
265280 self ._inverter .last_msg_timestamp = msg .inner .timestamp
281+ self ._inverter .data_recv_timer .reset ()
266282
267- elif msg := select .request_result :
283+ elif msg := select .set_power_result :
268284 result : SetPowerResult = msg .inner
269285 if self .battery_id in result .succeed :
270286 self ._blocking_status .unblock ()
271287
272- elif self .battery_id in result .failed :
273- # check if component stopped sending data
274- if self ._battery .last_msg_correct and self ._is_timestamp_outdated (
275- self ._battery .last_msg_timestamp
276- ):
277- self ._battery .last_msg_correct = False
278-
279- if self ._inverter .last_msg_correct and self ._is_timestamp_outdated (
280- self ._inverter .last_msg_timestamp
281- ):
282- self ._inverter .last_msg_correct = False
283-
284- component_correct = (
285- self ._battery .last_msg_correct and self ._inverter .last_msg_correct
288+ elif (
289+ self .battery_id in result .failed
290+ and self ._last_status != Status .NOT_WORKING
291+ ):
292+ duration = self ._blocking_status .block ()
293+
294+ if duration > 0 :
295+ _logger .warning (
296+ "battery %d failed last response. block it for %f sec" ,
297+ self .battery_id ,
298+ duration ,
299+ )
300+ elif msg := select .battery_timer :
301+ if self ._battery .last_msg_correct :
302+ self ._battery .last_msg_correct = False
303+ _logger .warning (
304+ "Battery %d stopped sending data, last timestamp: %s" ,
305+ self ._battery .component_id ,
306+ self ._battery .last_msg_timestamp ,
286307 )
287- if component_correct :
288- # if component is sending data, but request fails, then block it
289- # for some time
290- duration = self ._blocking_status .block ()
291- if duration > 0 :
292- _logger .warning (
293- "battery %d failed last response. block it for %f sec" ,
294- self .battery_id ,
295- duration ,
296- )
308+ elif msg := select .inverter_timer :
309+ if self ._inverter .last_msg_correct :
310+ self ._inverter .last_msg_correct = False
311+ _logger .warning (
312+ "Inverter %d stopped sending data, last timestamp: %s" ,
313+ self ._inverter .component_id ,
314+ self ._inverter .last_msg_timestamp ,
315+ )
316+
297317 else :
298318 _logger .error ("Unknown message returned from select" )
299319
0 commit comments