3333from .const import (
3434 ATTR_ACCESSIBLE ,
3535 ATTR_AIMED ,
36+ ATTR_ALERT_CAUSE ,
37+ ATTR_ALERT_COUNT ,
38+ ATTR_ALERT_DESCRIPTION ,
39+ ATTR_ALERT_EFFECT ,
40+ ATTR_ALERT_HEADER ,
41+ ATTR_ALERT_SEVERITY_LEVEL ,
42+ ATTR_ALERT_URL ,
43+ ATTR_ALERT ,
44+ ATTR_CAUSE ,
3645 ATTR_DELAY ,
3746 ATTR_DEPARTURE ,
3847 ATTR_DEPARTURES ,
48+ ATTR_DESCRIPTION_TEXT ,
3949 ATTR_DESCRIPTION ,
40- ATTR_DESTINATION ,
4150 ATTR_DESTINATION_ID ,
51+ ATTR_DESTINATION ,
52+ ATTR_EFFECT ,
53+ ATTR_ENTITY ,
4254 ATTR_EXPECTED ,
55+ ATTR_HEADER_TEXT ,
56+ ATTR_INFORMED_ENTITY ,
57+ ATTR_LANGUAGE ,
4358 ATTR_MONITORED ,
4459 ATTR_NAME ,
4560 ATTR_OPERATOR ,
4661 ATTR_SERVICE ,
62+ ATTR_SEVERITY_LEVEL ,
4763 ATTR_STATUS ,
48- ATTR_STOP ,
4964 ATTR_STOP_NAME ,
65+ ATTR_STOP ,
66+ ATTR_TEXT ,
67+ ATTR_TRANSLATION ,
68+ ATTR_TRIP_ID ,
69+ ATTR_TRIP ,
70+ ATTR_URL ,
5071 ATTR_VEHICLE ,
5172 ATTRIBUTION ,
5273 CONF_DEST ,
5576 CONF_STOP_ID ,
5677 CONF_STOPS ,
5778 DOMAIN ,
79+ LANG ,
5880)
5981
6082_LOGGER = logging .getLogger (__name__ )
@@ -129,6 +151,12 @@ def metlink_unique_id(d: Dict):
129151 uid = uid + "_d" + slug (d ["dest_filter" ])
130152 return uid
131153
154+ def get_translation (translations : Dict ) -> str :
155+ for translation in translations .get (ATTR_TRANSLATION , {}):
156+ if translation .get (ATTR_LANGUAGE ) == LANG :
157+ return translation .get (ATTR_TEXT , "" )
158+
159+ return ""
132160
133161class MetlinkSensor (Entity ):
134162 """Representation of a Metlink Stop sensor."""
@@ -194,6 +222,7 @@ async def async_update(self):
194222
195223 num = 0
196224 try :
225+ alerts = await self .metlink .get_service_alerts ()
197226 data = await self .metlink .get_predictions (self .stop_id )
198227
199228 for departure in data [ATTR_DEPARTURES ]:
@@ -214,6 +243,16 @@ async def async_update(self):
214243 if time is None :
215244 time = departure [ATTR_DEPARTURE ].get (ATTR_AIMED )
216245
246+ # enumerate the service alerts to find any that are relevant to the trip.
247+ trip_alerts = []
248+ if ATTR_TRIP_ID in departure :
249+ trip_id = departure [ATTR_TRIP_ID ]
250+ for entity in alerts [ATTR_ENTITY ]:
251+ alert = entity [ATTR_ALERT ]
252+ informed_entities = alert [ATTR_INFORMED_ENTITY ]
253+ if any (informed_entity .get (ATTR_TRIP , {}).get (ATTR_TRIP_ID ) == trip_id for informed_entity in informed_entities ):
254+ trip_alerts .append (alert )
255+
217256 name = f"{ departure [ATTR_SERVICE ]} { dest } "
218257 if num == 1 :
219258 # First record is the next departure, so use that
@@ -274,6 +313,44 @@ async def async_update(self):
274313 self .attrs [ATTR_MONITORED + suffix ] = departure [ATTR_MONITORED ]
275314 self .attrs [ATTR_VEHICLE + suffix ] = departure [ATTR_VEHICLE ]
276315
316+ # Trip alerts
317+ self .attrs [ATTR_ALERT_COUNT + suffix ] = len (trip_alerts )
318+ num_alert = 0
319+ for alert in trip_alerts :
320+ alert_suffix = f"_{ num_alert } "
321+
322+ self .attrs [ATTR_ALERT_HEADER + suffix + alert_suffix ] = get_translation (alert .get (ATTR_HEADER_TEXT , {}))
323+ self .attrs [ATTR_ALERT_DESCRIPTION + suffix + alert_suffix ] = get_translation (alert .get (ATTR_DESCRIPTION_TEXT , {}))
324+ self .attrs [ATTR_ALERT_URL + suffix + alert_suffix ] = get_translation (alert .get (ATTR_URL , {}))
325+ self .attrs [ATTR_ALERT_CAUSE + suffix + alert_suffix ] = alert .get (ATTR_CAUSE , "" )
326+ self .attrs [ATTR_ALERT_EFFECT + suffix + alert_suffix ] = alert .get (ATTR_EFFECT , "" )
327+ self .attrs [ATTR_ALERT_SEVERITY_LEVEL + suffix + alert_suffix ] = alert .get (ATTR_SEVERITY_LEVEL , "" )
328+
329+ num_alert += 1
330+
331+ # Clear out old alerts
332+ to_remove = []
333+ for alert_prefix in [
334+ ATTR_ALERT_HEADER ,
335+ ATTR_ALERT_DESCRIPTION ,
336+ ATTR_ALERT_URL ,
337+ ATTR_ALERT_CAUSE ,
338+ ATTR_ALERT_EFFECT ,
339+ ATTR_ALERT_SEVERITY_LEVEL ,
340+ ]:
341+ prefix = f"{ alert_prefix } { suffix } _"
342+ for attr in self .attrs :
343+ if attr .startswith (prefix ):
344+ try :
345+ if int (attr .removeprefix (prefix )) >= len (trip_alerts ):
346+ # we have an attribute outside of the range of the current alerts
347+ to_remove .append (attr )
348+ except ValueError as ex :
349+ pass
350+
351+ for attr in to_remove :
352+ self .attrs .pop (attr )
353+
277354 self ._available = True
278355 # Clear out the unused slots
279356 for i in range (num , self .num_departures ):
@@ -295,6 +372,23 @@ async def async_update(self):
295372 self .attrs .pop (ATTR_DESTINATION_ID + suffix , None )
296373 self .attrs .pop (ATTR_ACCESSIBLE + suffix , None )
297374 self .attrs .pop (ATTR_DELAY + suffix , None )
375+ self .attrs .pop (ATTR_ALERT_COUNT + suffix , None )
376+ to_remove = []
377+ for alert_prefix in [
378+ ATTR_ALERT_HEADER ,
379+ ATTR_ALERT_DESCRIPTION ,
380+ ATTR_ALERT_URL ,
381+ ATTR_ALERT_CAUSE ,
382+ ATTR_ALERT_EFFECT ,
383+ ATTR_ALERT_SEVERITY_LEVEL ,
384+ ]:
385+ prefix = f"{ alert_prefix } { suffix } "
386+ for attr in self .attrs :
387+ if attr .startswith (prefix ):
388+ to_remove .append (attr )
389+
390+ for attr in to_remove :
391+ self .attrs .pop (attr )
298392
299393 # set the sensor to unavailable on errors, but leave previous data in
300394 # attributes, so temporary network issues do not cause glitches.
0 commit comments