From 9802982d735a74cc92f3b920eb6aa8f23266de72 Mon Sep 17 00:00:00 2001 From: Pierre Date: Tue, 10 Nov 2026 19:31:13 +0100 Subject: [PATCH] =?UTF-8?q?fix(alarmclock):=20fiabiliser=20le=20rafra?= =?UTF-8?q?=C3=AEchissement=20du=20calendrier=20ICS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajouter deux tentatives de reprise après 2 puis 5 minutes en cas d’échec. - Mémoriser le dernier succès et adapter le niveau des logs à l’ancienneté du calendrier. - Distinguer les erreurs réseau, HTTP et les données ICS invalides. - Définir les délais de connexion et de lecture et vérifier le statut HTTP. - Actualiser le statut du jour travaillé et l’heure du réveil après une reprise. --- apps/alarmclock.py | 89 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/apps/alarmclock.py b/apps/alarmclock.py index 95e3f1f..efa1f7a 100644 --- a/apps/alarmclock.py +++ b/apps/alarmclock.py @@ -32,7 +32,19 @@ class CalendarEvent: class AlarmClock(SmartObject): + CALENDAR_RETRY_DELAYS = (2 * 60, 5 * 60) + + @property + def calendar_last_success(self): + return self.dataset.get('calendar_last_success') + + @calendar_last_success.setter + def calendar_last_success(self, value): + self.dataset['calendar_last_success'] = value + def on_initialize_smart_object(self): + if self.dataset is None: + self.dataset = {} self.switch_alarmclock_main = self.args['switch_alarmclock_main'] self.switch_alarmclock_secondary = self.args['switch_alarmclock_secondary'] self.inputdate_alarmclock_main = self.args['inputdate_alarmclock_main'] @@ -46,6 +58,8 @@ class AlarmClock(SmartObject): self.handle_program_next_wakueup_cb = None self.handle_prewakueup_cb = None self.handle_wakueup_cb = None + self.calendar_retry_handle = None + self.calendar_retry_attempt = 0 self.handle_switch_reveil_principal = self.listen_state(self.on_alarm_clock_set, self.switch_alarmclock_main) self.handle_switch_reveil_secondaire = self.listen_state(self.on_alarm_clock_set, self.switch_alarmclock_secondary) self.listen_state(self.on_alarm_clock_set, self.inputdate_alarmclock_main) @@ -64,17 +78,64 @@ class AlarmClock(SmartObject): def refresh_cal(self): with self.log_group("refresh_cal"): - new_cal = list() + new_cal = list() #if self.load_cal_from_google_calendar(new_cal): - if self.load_cal_from_ics(new_cal): + if self.load_cal_from_ics(new_cal): + self.calendar_last_success = datetime.datetime.now(datetime.timezone.utc) + self.calendar_retry_attempt = 0 + self._cancel_calendar_retry() if not self.are_cal_identical(self.calendar,new_cal): self.calendar = new_cal self.log_info("Calendar has been updated!") self.print_cal(self.calendar) return True + else: + self._schedule_calendar_retry() + return False + def _log_calendar_failure(self, reason): + last_success = self.calendar_last_success + if last_success is None: + log = self.log_error + freshness = 'no successful refresh recorded' + else: + age = datetime.datetime.now(datetime.timezone.utc) - last_success + if age < datetime.timedelta(hours=24): + log = self.log_info + elif age < datetime.timedelta(hours=48): + log = self.log_warning + else: + log = self.log_error + freshness = (f'last_success={last_success.isoformat()}, ' + f'age={age.total_seconds() / 3600:.2f}h') + log(f"Couldn't load calendar: {reason}; {freshness}") + + def _schedule_calendar_retry(self): + if self.calendar_retry_handle is not None: + return + if self.calendar_retry_attempt >= len(self.CALENDAR_RETRY_DELAYS): + return + delay = self.CALENDAR_RETRY_DELAYS[self.calendar_retry_attempt] + self.calendar_retry_attempt += 1 + self.calendar_retry_handle = self.run_in(self.on_calendar_retry, delay) + self.log_info(f"Calendar refresh retry scheduled in {delay // 60} minutes") + + def _cancel_calendar_retry(self): + if self.calendar_retry_handle is None: + return + if self.timer_running(self.calendar_retry_handle): + self.cancel_timer(self.calendar_retry_handle) + self.calendar_retry_handle = None + + def on_calendar_retry(self, kwargs): + self.calendar_retry_handle = None + calendar_changed = self.refresh_cal() + self.update_workday_status() + if calendar_changed: + self.update_wakeup_time() + def are_cal_identical(self,calendar1,calendar2): if len(calendar1) != len(calendar2): return False @@ -93,7 +154,8 @@ class AlarmClock(SmartObject): try: self.log_info(f"Retrieving calengar from outlook.office365.com") - r = get("https://outlook.office365.com/owa/calendar/0c64359c70eb41cd9713fe92c470d3df@ubisoft.com/5edb3fb5150f4203b66d9cf8db3184be11626033572842691749/calendar.ics", timeout=10)#, headers=headers, verify=False) + r = get("https://outlook.office365.com/owa/calendar/0c64359c70eb41cd9713fe92c470d3df@ubisoft.com/5edb3fb5150f4203b66d9cf8db3184be11626033572842691749/calendar.ics", timeout=(3, 5))#, headers=headers, verify=False) + r.raise_for_status() #self.log_info(f"Processing Calendar") today = datetime.datetime.combine(datetime.date.today(), datetime.time(0,0, 0)) @@ -101,7 +163,7 @@ class AlarmClock(SmartObject): end_load_date = today + datetime.timedelta(days=2) #self.log_info("[" + str(start_load_date) + "]-[" + str(end_load_date) + "]") - calendar_ubi = icalendar.Calendar.from_ical(r.text) + calendar_ubi = icalendar.Calendar.from_ical(r.content) for component in calendar_ubi.walk(): if component.name == "VEVENT": event_summary = component.get('summary') @@ -137,11 +199,20 @@ class AlarmClock(SmartObject): calendar.append(CalendarEvent(start_datetime,end_datetime,event_type)) else: self.log_warning(f"[Unknow event : {event_summary}]Start at {start_datetime},Finish at {end_datetime}") - except ValueError: - self.log_error("Couldn't load calendar") + except requests.exceptions.Timeout as error: + self._log_calendar_failure(f"request timed out ({type(error).__name__})") return False - except (requests.exceptions.ConnectionError,requests.exceptions.ReadTimeout): - self.log_warning("Couldn't load calendar, request Timeout") + except requests.exceptions.ConnectionError: + self._log_calendar_failure("connection failed") + return False + except requests.exceptions.HTTPError as error: + self._log_calendar_failure(f"HTTP {error.response.status_code}") + return False + except requests.exceptions.RequestException as error: + self._log_calendar_failure(type(error).__name__) + return False + except (ValueError, TypeError, KeyError): + self._log_calendar_failure("invalid ICS data") return False #self.log_info(f"Calendar loaded") @@ -151,6 +222,8 @@ class AlarmClock(SmartObject): self.update_wakeup_time() def on_refresh_cal(self, kwargs): + self._cancel_calendar_retry() + self.calendar_retry_attempt = 0 calendar_changed = self.refresh_cal() self.update_workday_status() if calendar_changed: