Compare commits

...
12 Commits
Author SHA1 Message Date
Pierre 9802982d73 fix(alarmclock): fiabiliser le rafraîchissement du calendrier ICS
- 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.
2026-11-10 19:31:13 +01:00
Pierre ff39f4cb2f fix(vertex2): fiabiliser les lectures +5V et enrichir les diagnostics TCP
- Regrouper les demandes de lecture +5V et ignorer les callbacks obsolètes.
- Retenter la lecture après 5 secondes au premier échec et journaliser le rétablissement.
- Annuler les lectures planifiées à la réinitialisation, à la suspension et à l’arrêt.
- Détailler les timeouts TCP : champs reçus et manquants, octets reçus, interruptions et dernières lignes.
2026-11-10 19:30:18 +01:00
Pierre b76f107f01 fix christmas tree commit 2026-11-10 19:14:27 +01:00
Pierre f5a586c379 upgrade notificaiton manager to use LoggerInterface 2026-11-10 19:13:47 +01:00
Pierre 94754e6285 New christmas tree 2026-11-10 19:12:06 +01:00
Pierre 10c5b9e043 fixed error caused by sensor change in the last P1P update 2026-11-10 19:11:28 +01:00
Pierre 9f32a666c3 temperature tweak 2026-11-10 19:10:32 +01:00
Pierre 3130df69ce latest appdaemon version came now from docker image 2026-11-08 10:48:50 +01:00
Pierre 35856b2ba9 ignore code stuff from git 2026-11-08 10:48:03 +01:00
Pierre a5258b1985 Tweaked desk presence 2026-11-06 21:55:24 +01:00
Pierre 42ae8f4bc4 add main power off notification 2026-11-05 22:33:40 +01:00
Pierre 2c87c6a6f3 added a notification for commut rain 2026-11-05 18:45:37 +01:00
12 changed files with 221 additions and 50 deletions

No files matched your search

+1
View File
@@ -5,3 +5,4 @@ __pycache__/
logs/
apps/data/
secrets.yaml
.codex/
+81 -8
View File
@@ -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:
+1 -1
View File
@@ -115,7 +115,7 @@ bedroom_heatpump:
auto: binary_sensor.heatpump_winter_mode or room_temperature > target_temperature + 1
quiet: True
target_temperature: sensor.target_temperature_heatpump - 0.5
target_temperature: "sensor.target_temperature_heatpump - (binary_sensor.heatpump_winter_mode ? 1 : 0.5)"
hvac_mode: "binary_sensor.heatpump_winter_mode ? 'heat' : 'cool'"
fan_mode: sensor.bedroom_heatpump_fan_mode
+24
View File
@@ -27,3 +27,27 @@ notifications_manager:
message:
content: Good Bye
timeout: 600
commut_rain_notification:
recipients:
pierre:
services: notify/mobile_app_iphone_de_pierre
conditions: binary_sensor.commut_rain
message:
title: Pluie prévue sur votre trajet
content: "Il va pleuvoir dans {sensor.commut_rain_minutes_until_rain} minutes"
power_off_notification:
recipients:
all:
services:
- notify/mobile_app_iphone_de_pierre
- notify/mobile_app_mae
conditions: binary_sensor.main_power_off and not binary_sensor.someone_home
message:
title: Coupure de courant
content: Le courant a été coupé
auto_clear: true
+3 -7
View File
@@ -60,10 +60,6 @@ occupancy_sensors:
binary_sensor.guestroom_occupancy:
trigger_conditions: binary_sensor.is_guestroom_sleeping or binary_sensor.day_interval_night
blocking_conditions: not binary_sensor.someone_home or not input_boolean.guestroom_occupied_tonight
retain_condition.desk_occupancy:
retain_time: 5 * 60
conditions:
trigger_conditions:
- sensor.desk_last_motion <= 5
#- binary_sensor.alienware_r10
#- sensor.macbook_m3_pro_de_pierre_frontmost_app != 'loginwindow' and sensor.macbook_m3_pro_de_pierre_displays == 2
binary_sensor.desk_occupancy:
trigger_conditions: sensor.desk_last_motion <= 5 or binary_sensor.is_desk_sleeping
blocking_conditions: not binary_sensor.someone_home
+3 -1
View File
@@ -82,6 +82,8 @@ virtual_sensors:
event_data:
entity_id: sensor.shelly_button_red_signal_strength
binary_sensor.main_power_off: sensor.ups_code_d_etat != 'OL'
# living room sensors
value_selector.living_room_ambiance_light:
values:
@@ -137,7 +139,7 @@ virtual_sensors:
binary_sensor.p1p_is_active:
trigger_conditions:
- binary_sensor.p1p_is_printing
- fan.garage_p1p_ventilateur_de_la_chambre or fan.garage_p1p_ventilateur_de_refroidissement
- sensor.garage_p1p_vitesse_du_ventilateur_de_la_chambre or sensor.garage_p1p_vitesse_du_ventilateur_de_refroidissement
- sensor.garage_p1p_temperature_cible_de_la_buse > 0 or sensor.garage_p1p_temperature_cible_du_lit > 0
blocking_conditions: not switch.garage_p1p_plug
+22 -8
View File
@@ -4,11 +4,14 @@ from importlib import metadata
import json
import pickle
import os
import re
from urllib.request import urlopen
class InformationsCollector(SmartObject):
APPDAEMON_PYPI_URL = "https://pypi.org/pypi/appdaemon/json"
APPDAEMON_DOCKER_TAGS_URL = (
"https://hub.docker.com/v2/repositories/acockburn/appdaemon/tags?page_size=100"
)
def on_initialize_smart_object(self):
super().on_initialize_smart_object()
@@ -26,7 +29,7 @@ class InformationsCollector(SmartObject):
sensor_name,
state='unknown',
attributes={
'latest_official_version': self.dataset.get('appdaemon_latest_official_version'),
'latest_official_version': self.dataset.get('appdaemon_latest_docker_version'),
'update_available': None,
},
icon='mdi:package-up'
@@ -143,9 +146,9 @@ class InformationsCollector(SmartObject):
latest_official_version = self._get_latest_official_appdaemon_version()
if latest_official_version is not None:
self.dataset['appdaemon_latest_official_version'] = latest_official_version
self.dataset['appdaemon_latest_docker_version'] = latest_official_version
else:
latest_official_version = self.dataset.get('appdaemon_latest_official_version')
latest_official_version = self.dataset.get('appdaemon_latest_docker_version')
update_available = self._is_appdaemon_update_available(installed_version, latest_official_version)
@@ -168,11 +171,22 @@ class InformationsCollector(SmartObject):
def _get_latest_official_appdaemon_version(self):
try:
with urlopen(self.APPDAEMON_PYPI_URL, timeout=10) as response:
payload = json.load(response)
return payload.get('info', {}).get('version')
url = self.APPDAEMON_DOCKER_TAGS_URL
versions = {}
while url:
with urlopen(url, timeout=10) as response:
payload = json.load(response)
for tag in payload['results']:
name = tag.get('name', '')
# Only stable version tags with published container images.
if re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+', name) and tag.get('images'):
versions[tuple(map(int, name.split('.')))] = name
url = payload.get('next')
if not versions:
raise ValueError("No stable AppDaemon Docker image found")
return versions[max(versions)]
except Exception as err:
self.log_warning(f"Unable to fetch latest official AppDaemon version: {err}")
self.log_warning(f"Unable to fetch latest official AppDaemon Docker version: {err}")
return None
def _is_appdaemon_update_available(self, installed_version, latest_official_version):
+13 -16
View File
@@ -3,7 +3,8 @@ import appdaemon.exceptions as ad_exception
import ad_toolbox.smartcondition as SmartCondition
import re
import uuid
from expressionparser import ParsingException
from ad_toolbox.expressionparser import ParsingException
from ad_toolbox.logger_interface import LoggerInterface
class Notification:
ENTITY_PLACEHOLDER_PATTERN = re.compile(r"\{([^{}]+)\}")
@@ -18,7 +19,7 @@ class Notification:
for recipient in config["recipients"]:
try: self.recipients[recipient] = SmartCondition.Evaluator(self.ad_api,self.config["recipients"][recipient]['conditions'],condition_name = name,on_succeed_cb = self.on_condition_succeed, on_fail_cb = self.on_conditon_fail,pass_condition_name_to_cb = recipient, trigger_callback_on_activation = False)
except ParsingException as e:
self.log_error(str(e))
self.ad_api.log_error(str(e))
continue
def on_conditon_fail(self, recipient):
@@ -42,9 +43,9 @@ class Notification:
data = { 'tag' : tag }
def clear_notification(service,tag):
self.ad_api.log(f"Clear notification {data} from {service}")
self.ad_api.log_info(f"Clear notification {data} from {service}")
try: self.ad_api.call_service(service,message = message, data = data)
except ad_exception.ServiceException: self.log_error(f"Invalid service: {service}")
except ad_exception.ServiceException: self.ad_api.log_error(f"Invalid service: {service}")
recipient_yaml = self.config["recipients"][recipient]
if isinstance(recipient_yaml['services'], str):
@@ -65,11 +66,11 @@ class Notification:
try:
state = self.ad_api.get_state(entity_id)
except Exception as e:
self.log_error(f"Failed to resolve '{entity_id}' in notification content: {e}")
self.ad_api.log_error(f"Failed to resolve '{entity_id}' in notification content: {e}")
return match.group(0)
if state is None:
self.log_error(f"Unable to resolve '{entity_id}' in notification content")
self.ad_api.log_error(f"Unable to resolve '{entity_id}' in notification content")
return match.group(0)
return str(state)
@@ -96,9 +97,9 @@ class Notification:
self.ad_api.run_in(self.on_timeout_expired,timeout,tag = data['tag'],recipient = recipient)
def send_notification(service):
self.ad_api.log(f"Sending notification '{message}' to {service}{f' with data {data}' if data else ''}")
self.ad_api.log_info(f"Sending notification '{message}' to {service}{f' with data {data}' if data else ''}")
try: self.ad_api.call_service(service,title = title,message = message, data = data)
except ad_exception.ServiceException: self.log_error(f"Invalid service: {service}")
except ad_exception.ServiceException: self.ad_api.log_error(f"Invalid service: {service}")
recipient_yaml = self.config["recipients"][recipient]
if isinstance(recipient_yaml['services'], str):
@@ -107,17 +108,13 @@ class Notification:
for service in recipient_yaml['services']:
send_notification(service)
def log_error(self,message,stop_app = False):
self.ad_api.log(message,level = "ERROR", log = "main_log")
self.ad_api.log(message,level = "ERROR", log = "error_log")
if stop_app:
self.ad_api.stop_app(self.ad_api.name)
class NotificationsManager(hass.Hass):
class NotificationsManager(hass.Hass, LoggerInterface):
def initialize(self):
# Preserve the default app logger used by the previous direct log calls.
self.initialize_logger_interface(self.get_ad_api(), default_log=None)
self.notifications = dict()
if "notifications" in self.args:
for notification in self.args["notifications"]:
self.notifications[notification] = Notification(self,self.args["notifications"][notification],notification)
self.notifications[notification] = Notification(self,self.args["notifications"][notification],notification)
+1 -1
View File
@@ -4,5 +4,5 @@ light_ambiance:
entity: light.desk_ambiance
smart_conditions:
trigger_conditions: sensor.desk_last_motion < 3 and binary_sensor.is_dark_outside
trigger_conditions: binary_sensor.desk_occupancy and binary_sensor.is_dark_outside
blocking_conditions: binary_sensor.is_desk_sleeping
+1 -1
View File
@@ -15,7 +15,7 @@ p1p_light:
smart_conditions:
unavaibility_result: Disabled
trigger_conditions: binary_sensor.garage_occupancy or binary_sensor.garage_p1p_timelapse or camera.garage_p1p_camera == 'streaming'
blocking_conditions: not binary_sensor.p1p_is_printing and not fan.garage_p1p_ventilateur_de_refroidissement and not fan.garage_p1p_ventilateur_de_la_chambre
blocking_conditions: not binary_sensor.p1p_is_printing and not sensor.garage_p1p_vitesse_du_ventilateur_de_refroidissement and not sensor.garage_p1p_vitesse_du_ventilateur_de_la_chambre
p1p_switch:
module: smartswitch
+8 -1
View File
@@ -49,6 +49,13 @@ light_bookshelves:
25: sensor.living_room_ambiance_light == 1
100: True
light_christmas_tree:
module: smartswitch
class: SmartSwitch
entity: switch.chrismas_tree
smart_conditions: sensor.living_room_ambiance_light >= 1
light_tree:
module: smartlight
class: SmartLight
@@ -102,7 +109,7 @@ mezzanine_curtains:
close_conditions:
trigger_conditions: binary_sensor.projector_on or not binary_sensor.projector_off_for_a_while
disable_conditions: self == 'closed' and (binary_sensor.is_dark_outside or binary_sensor.is_anybody_sleeping)
disable_conditions: binary_sensor.is_dark_outside or binary_sensor.is_anybody_sleeping
living_room_sleep_switch:
module: smartswitch
+63 -6
View File
@@ -7,6 +7,7 @@ import time
import json
import socket
import threading
from collections import deque
import re
from urllib.parse import urlparse
@@ -14,6 +15,7 @@ class Vertex2(SmartObject):
def on_initialize_smart_object(self):
# HASS can call initialize() again without calling terminate() first.
self._stop_interrupt_listener()
self._cancel_input_5v_refresh()
self._interrupt_stop = threading.Event()
self._status_requested = threading.Event()
self._last_logged_status = None
@@ -217,9 +219,28 @@ class Vertex2(SmartObject):
if sensor.get_state() != 'unavailable':
sensor.set_state(state='unavailable')
def _schedule_input_5v_refresh(self):
# Device monitoring must run even when no HA input sensors are exposed.
self.run_in(self._refresh_input_5v_sensors, 0.1)
def _cancel_input_5v_refresh(self):
handle = getattr(self, '_input_5v_refresh_handle', None)
if handle is not None:
self.cancel_timer(handle, silent=True)
self._input_5v_refresh_handle = None
self._input_5v_refresh_generation = getattr(self, '_input_5v_refresh_generation', 0) + 1
self._input_5v_failure_since = None
self._input_5v_failures = 0
def _schedule_input_5v_refresh(self, delay=1):
# Coalesce bursts without postponing a pending retry or starving reads.
if self._input_5v_refresh_handle is not None:
return
self._input_5v_refresh_generation += 1
self._input_5v_refresh_handle = self.run_in(
self._on_input_5v_refresh, delay, generation=self._input_5v_refresh_generation)
def _on_input_5v_refresh(self, kwargs):
if kwargs['generation'] != self._input_5v_refresh_generation:
return
self._input_5v_refresh_handle = None
self._refresh_input_5v_sensors()
def _refresh_input_5v_sensors(self, kwargs=None):
if not self._vertex2_ready():
@@ -233,11 +254,27 @@ class Vertex2(SmartObject):
raise ValueError('expected an information object')
except (RequestException, ValueError) as error:
self._invalidate_input_5v_sensors()
self.log_warning(f'Vertex2 input +5V readback failed: {error}')
if not self._vertex2_ready():
self._cancel_input_5v_refresh()
return
self._input_5v_failures += 1
if self._input_5v_failure_since is None:
self._input_5v_failure_since = time.monotonic()
log = self.log_warning if self._input_5v_failures == 2 else self.log_info
log(f'Vertex2 input +5V readback failed '
f'(attempt={self._input_5v_failures}, {type(error).__name__}): {error}')
if self._input_5v_failures == 1:
self._schedule_input_5v_refresh(delay=5)
return
if not self._vertex2_ready():
self._invalidate_input_5v_sensors()
return
if self._input_5v_failure_since is not None:
self.log_info(
f'Vertex2 input +5V readback restored after '
f'{time.monotonic() - self._input_5v_failure_since:.1f}s '
f'({self._input_5v_failures} failed attempts)')
self._cancel_input_5v_refresh()
states = {}
for index in range(4):
# Firmware uses 2 for present and 1 for absent, not boolean 1/0.
@@ -442,11 +479,26 @@ class Vertex2(SmartObject):
status_started = None
reconfigure = True
pending_interrupt_at = None
recent_lines = deque(maxlen=12)
received_bytes = 0
interrupt_count = 0
def status_timeout_error():
return OSError(
'Vertex2 TCP status response timed out '
f'(elapsed={time.monotonic() - status_started:.2f}s, limit=5s, '
f'received={sorted(status)}, missing={sorted(expected - status.keys())}, '
f'bytes={received_bytes}, interrupts={interrupt_count}, '
f'reconfigure={reconfigure}, '
f'recent_lines={list(recent_lines)!r}, partial_line={buffer[-256:]!r})')
while not interrupt_stop.is_set() and self._vertex2_powered():
if status_started is None and self._status_requested.is_set():
self._status_requested.clear()
status = {}
recent_lines.clear()
received_bytes = 0
interrupt_count = 0
status_started = time.monotonic()
request_interrupt_at = pending_interrupt_at
pending_interrupt_at = None
@@ -457,7 +509,7 @@ class Vertex2(SmartObject):
data = connection.recv(1024)
except socket.timeout:
if status_started is not None and time.monotonic() - status_started > 5:
raise OSError('Vertex2 TCP status response timed out')
raise status_timeout_error()
continue
if interrupt_stop.is_set() or not self._vertex2_powered():
@@ -465,6 +517,7 @@ class Vertex2(SmartObject):
if not data:
raise OSError('Vertex2 closed the IP INTERRUPT connection')
received_bytes += len(data)
buffer += data.decode('ascii', errors='ignore')
if len(buffer) > 4096:
raise OSError('Vertex2 TCP response exceeded buffer limit')
@@ -472,7 +525,9 @@ class Vertex2(SmartObject):
while '\n' in buffer:
line, buffer = buffer.split('\n', 1)
line = line.strip()
recent_lines.append(line[:256])
if line == 'int':
interrupt_count += 1
pending_interrupt_at = time.monotonic()
self._status_requested.set()
elif status_started is not None:
@@ -497,7 +552,7 @@ class Vertex2(SmartObject):
status_started = None
if status_started is not None and time.monotonic() - status_started > 5:
raise OSError('Vertex2 TCP status response timed out')
raise status_timeout_error()
except (OSError, ValueError) as error:
if interrupt_stop.is_set():
@@ -557,6 +612,7 @@ class Vertex2(SmartObject):
self._command_processing_handle = None
def terminate(self):
self._cancel_input_5v_refresh()
if getattr(self, '_input_select_config', None):
self._publish_input_select('/availability', 'offline')
self._stop_interrupt_listener()
@@ -721,6 +777,7 @@ class Vertex2(SmartObject):
)
def _suspend_vertex2(self):
self._cancel_input_5v_refresh()
self._invalidate_input_5v_sensors()
self._reset_no_signal_spam()
self._invalidate_plus5_sensors()