Skip to content

Commit eeb035d

Browse files
committed
Improve error resistancy
1 parent 51fd50c commit eeb035d

File tree

1 file changed

+106
-59
lines changed

1 file changed

+106
-59
lines changed

custom_components/nysse/sensor.py

Lines changed: 106 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -81,47 +81,59 @@ def __init__(self, stop_code, maximum, timelimit, lines) -> None:
8181
self._last_update_time = None
8282

8383
def _remove_unwanted_departures(self, departures):
84-
removed_departures_count = 0
84+
try:
85+
removed_departures_count = 0
8586

86-
# Remove unwanted departures based on departure time and line number
87-
for departure in departures[:]:
88-
departure_local = dt_util.as_local(
89-
parser.parse(departure["departure_time"])
90-
)
91-
if (
92-
departure_local
93-
< self._last_update_time + timedelta(minutes=self._timelimit)
94-
or departure["route_id"] not in self._lines
95-
):
96-
departures.remove(departure)
97-
removed_departures_count += 1
98-
99-
if removed_departures_count > 0:
100-
_LOGGER.debug(
101-
"%s: Removed %s stale or unwanted departures",
87+
# Remove unwanted departures based on departure time and line number
88+
for departure in departures[:]:
89+
departure_local = dt_util.as_local(
90+
parser.parse(departure["departure_time"])
91+
)
92+
if (
93+
departure_local
94+
< self._last_update_time + timedelta(minutes=self._timelimit)
95+
or departure["route_id"] not in self._lines
96+
):
97+
departures.remove(departure)
98+
removed_departures_count += 1
99+
100+
if removed_departures_count > 0:
101+
_LOGGER.debug(
102+
"%s: Removed %s stale or unwanted departures",
103+
self._stop_code,
104+
removed_departures_count,
105+
)
106+
107+
return departures[: self._max_items]
108+
except (KeyError, OSError) as err:
109+
_LOGGER.info(
110+
"%s: Failed to process realtime departures: %s",
102111
self._stop_code,
103-
removed_departures_count,
112+
err,
104113
)
105-
106-
return departures[: self._max_items]
114+
return []
107115

108116
async def _fetch_departures(self):
109-
url = STOP_URL.format(self._stop_code)
110-
_LOGGER.debug(
111-
"%s: Fectching departures from %s",
112-
self._stop_code,
113-
url + "&indent=yes",
114-
)
115-
data = await get(url)
116-
if not data:
117-
_LOGGER.warning(
118-
"%s: Nysse API error: failed to fetch realtime data: no data received from %s",
117+
try:
118+
url = STOP_URL.format(self._stop_code)
119+
_LOGGER.debug(
120+
"%s: Fectching departures from %s",
119121
self._stop_code,
120-
url,
122+
url + "&indent=yes",
121123
)
122-
return
123-
unformatted_departures = json.loads(data)
124-
return self._format_departures(unformatted_departures)
124+
data = await get(url)
125+
if not data:
126+
_LOGGER.warning(
127+
"%s: Nysse API error: failed to fetch realtime data: no data received from %s",
128+
self._stop_code,
129+
url,
130+
)
131+
return
132+
unformatted_departures = json.loads(data)
133+
return self._format_departures(unformatted_departures)
134+
except OSError as err:
135+
_LOGGER.error("%s: Failed to fetch realtime data: %s", self._stop_code, err)
136+
return []
125137

126138
def _format_departures(self, departures):
127139
try:
@@ -158,12 +170,19 @@ def _format_departures(self, departures):
158170
err,
159171
)
160172
return []
173+
except OSError as err:
174+
_LOGGER.info(
175+
"%s: failed to process realtime data: %s",
176+
self._stop_code,
177+
err,
178+
)
179+
return []
161180

162181
async def async_update(self) -> None:
163182
"""Fetch new state data for the sensor."""
164-
self._last_update_time = dt_util.now()
165-
166183
try:
184+
self._last_update_time = dt_util.now()
185+
167186
if len(self._stops) == 0:
168187
_LOGGER.debug("Getting stops")
169188
self._stops = await get_stops()
@@ -203,36 +222,60 @@ async def async_update(self) -> None:
203222
_LOGGER.error("%s: Failed to update sensor: %s", self._stop_code, err)
204223

205224
def _data_to_display_format(self, data):
206-
formatted_data = []
207-
for item in data:
208-
departure = {
209-
"destination": item["trip_headsign"],
210-
"line": item["route_id"],
211-
"departure": parser.parse(item["departure_time"]).strftime("%H:%M"),
212-
"time_to_station": self._time_to_station(item),
213-
"icon": self._get_line_icon(item["route_id"]),
214-
"realtime": item["realtime"] if "realtime" in item else False,
215-
}
216-
formatted_data.append(departure)
217-
return sorted(formatted_data, key=lambda x: x["time_to_station"])
225+
try:
226+
formatted_data = []
227+
for item in data:
228+
departure = {
229+
"destination": item["trip_headsign"],
230+
"line": item["route_id"],
231+
"departure": parser.parse(item["departure_time"]).strftime("%H:%M"),
232+
"time_to_station": self._time_to_station(item),
233+
"icon": self._get_line_icon(item["route_id"]),
234+
"realtime": item["realtime"] if "realtime" in item else False,
235+
}
236+
formatted_data.append(departure)
237+
return sorted(formatted_data, key=lambda x: x["time_to_station"])
238+
except OSError as err:
239+
_LOGGER.debug("%s: Failed to format data: %s", self._stop_code, err)
240+
return []
218241

219242
def _get_line_icon(self, line_no):
220243
if line_no in TRAM_LINES:
221244
return "mdi:tram"
222245
return "mdi:bus"
223246

224247
def _time_to_station(self, item):
225-
departure_local = dt_util.as_local(parser.parse(item["departure_time"]))
226-
if "delta_days" in item:
227-
departure_local += timedelta(days=item["delta_days"])
228-
next_departure_time = (departure_local - self._last_update_time).seconds
229-
return int(next_departure_time / 60)
248+
try:
249+
departure_local = dt_util.as_local(parser.parse(item["departure_time"]))
250+
if "delta_days" in item:
251+
departure_local += timedelta(days=item["delta_days"])
252+
next_departure_time = (departure_local - self._last_update_time).seconds
253+
return int(next_departure_time / 60)
254+
except OSError as err:
255+
_LOGGER.debug(
256+
"%s: Failed to calculate time to station: %s",
257+
self._stop_code,
258+
err,
259+
)
260+
return 0
230261

231262
def _get_stop_name(self, stop_id):
232-
return next(
233-
(stop["stop_name"] for stop in self._stops if stop["stop_id"] == stop_id),
234-
"unknown stop",
235-
)
263+
try:
264+
return next(
265+
(
266+
stop["stop_name"]
267+
for stop in self._stops
268+
if stop["stop_id"] == stop_id
269+
),
270+
"unknown stop",
271+
)
272+
except (OSError, KeyError) as err:
273+
_LOGGER.debug(
274+
"%s: Failed to get stop name: %s",
275+
self._stop_code,
276+
err,
277+
)
278+
return "unknown stop"
236279

237280
@property
238281
def unique_id(self) -> str:
@@ -278,8 +321,12 @@ def __init__(self) -> None:
278321
self._empty_response_counter = 0
279322

280323
def _timestamp_to_local(self, timestamp):
281-
utc = dt_util.utc_from_timestamp(int(str(timestamp)[:10]))
282-
return dt_util.as_local(utc)
324+
try:
325+
utc = dt_util.utc_from_timestamp(int(str(timestamp)[:10]))
326+
return dt_util.as_local(utc)
327+
except OSError as err:
328+
_LOGGER.error("Failed to convert timestamp to local time: %s", err)
329+
return ""
283330

284331
def _conditionally_clear_alerts(self):
285332
# TODO: Individual alerts may never be removed

0 commit comments

Comments
 (0)