Compare commits
6 Commits
c9d65b29fd
...
7cb4e71959
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cb4e71959 | |||
| 244c481c10 | |||
| 75098e76e4 | |||
| f163b9eede | |||
| 60224f0459 | |||
| ece357e799 |
@@ -105,9 +105,9 @@ class AlarmClock(SmartObject):
|
|||||||
for component in calendar_ubi.walk():
|
for component in calendar_ubi.walk():
|
||||||
if component.name == "VEVENT":
|
if component.name == "VEVENT":
|
||||||
event_summary = component.get('summary')
|
event_summary = component.get('summary')
|
||||||
if event_summary == "Away": event_type = EventType.Vacation
|
if event_summary == "Away" or event_summary == "Absent(e)": event_type = EventType.Vacation
|
||||||
elif event_summary == "Busy": event_type = EventType.Busy
|
elif event_summary == "Busy" or event_summary == "Occupé(e)": event_type = EventType.Busy
|
||||||
elif event_summary == "Tentative": event_type = EventType.Tentative
|
elif event_summary == "Tentative" or event_summary == "Provisoire": event_type = EventType.Tentative
|
||||||
elif event_summary == "Working elsewhere" or event_summary == "Free": event_type = EventType.Flex
|
elif event_summary == "Working elsewhere" or event_summary == "Free": event_type = EventType.Flex
|
||||||
else: event_type = None
|
else: event_type = None
|
||||||
|
|
||||||
|
|||||||
@@ -5,54 +5,37 @@ debugger:
|
|||||||
port: 5678
|
port: 5678
|
||||||
wait_for_client: false # set to true when you need to debug initialize()
|
wait_for_client: false # set to true when you need to debug initialize()
|
||||||
|
|
||||||
motion_tracker:
|
|
||||||
module: motiontracker
|
|
||||||
class: MotionTracker
|
|
||||||
|
|
||||||
priority: 5 # this need to be initialized before app using motion tracker
|
|
||||||
|
|
||||||
max_time: 30
|
|
||||||
|
|
||||||
areas:
|
|
||||||
corridor:
|
|
||||||
motion_sensors: binary_sensor.corridor_motion
|
|
||||||
hallway:
|
|
||||||
motion_sensors: binary_sensor.hallway_motion
|
|
||||||
restroom:
|
|
||||||
motion_sensors: binary_sensor.restroom_motion
|
|
||||||
garage:
|
|
||||||
motion_sensors: binary_sensor.garage_motion
|
|
||||||
kitchen:
|
|
||||||
motion_sensors: binary_sensor.kitchen_motion
|
|
||||||
mezzanine:
|
|
||||||
motion_sensors: binary_sensor.mezzanine_motion
|
|
||||||
|
|
||||||
|
|
||||||
informations_collector:
|
informations_collector:
|
||||||
module: informationscollector
|
module: informationscollector
|
||||||
class: InformationsCollector
|
class: InformationsCollector
|
||||||
priority: 1
|
priority: 1
|
||||||
|
|
||||||
#mute: True
|
mute: True
|
||||||
|
|
||||||
|
appdaemon_version_sensor: sensor.ad_appdaemon_version
|
||||||
|
|
||||||
error_collector:
|
error_collector:
|
||||||
reset_error_count_button: input_button.reset_error_count
|
reset_error_count_button: input_button.reset_error_count
|
||||||
output_sensor: sensor.ad_errors
|
output_sensor: sensor.ad_errors
|
||||||
|
|
||||||
# min_max_temp_sensors:
|
min_max_temp_sensors:
|
||||||
# temperature_sensors:
|
temperature_sensors:
|
||||||
# - sensor.temperature_exterieur_nord
|
- sensor.ext_front_temperature
|
||||||
# - sensor.temperature_exterieur_sud
|
- sensor.ext_back_temperature
|
||||||
# output_sensors:
|
output_sensors:
|
||||||
# max_temp: sensor.max_temp_outside
|
max_temp: sensor.max_temp_outside
|
||||||
# max_temp_yesterday: sensor.max_temp_yesterday_outside
|
max_temp_yesterday: sensor.max_temp_yesterday_outside
|
||||||
# min_temp: sensor.min_temp_outside
|
min_temp: sensor.min_temp_outside
|
||||||
# min_temp_yesterday: sensor.min_temp_yesterday_outside
|
min_temp_yesterday: sensor.min_temp_yesterday_outside
|
||||||
|
|
||||||
group_collectors:
|
group_collectors:
|
||||||
windows:
|
windows:
|
||||||
group: binary_sensor.group_windows
|
group: binary_sensor.group_windows
|
||||||
output_sensor: sensor.ad_number_of_windows_open
|
output_sensor: sensor.ad_number_of_windows_open
|
||||||
|
|
||||||
|
doors:
|
||||||
|
group: binary_sensor.group_doors
|
||||||
|
output_sensor: sensor.ad_number_of_doors_open
|
||||||
|
|
||||||
music_players:
|
music_players:
|
||||||
group: media_player.group_music_player
|
group: media_player.group_music_player
|
||||||
|
|||||||
36
apps/global/presence.yaml
Normal file
36
apps/global/presence.yaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
motion_tracker:
|
||||||
|
module: motiontracker
|
||||||
|
class: MotionTracker
|
||||||
|
|
||||||
|
priority: 5 # this need to be initialized before app using motion tracker
|
||||||
|
|
||||||
|
max_time: 30
|
||||||
|
|
||||||
|
areas:
|
||||||
|
corridor:
|
||||||
|
motion_sensors: binary_sensor.corridor_motion
|
||||||
|
hallway:
|
||||||
|
motion_sensors: binary_sensor.hallway_motion
|
||||||
|
restroom:
|
||||||
|
motion_sensors: binary_sensor.restroom_motion
|
||||||
|
garage:
|
||||||
|
motion_sensors: binary_sensor.garage_motion
|
||||||
|
kitchen:
|
||||||
|
motion_sensors: binary_sensor.kitchen_motion
|
||||||
|
mezzanine:
|
||||||
|
motion_sensors: binary_sensor.mezzanine_motion
|
||||||
|
living_room:
|
||||||
|
motion_sensors: binary_sensor.living_room_presence_presence
|
||||||
|
|
||||||
|
occupancy_sensors:
|
||||||
|
module: virtualsensors
|
||||||
|
class: VirtualSensorsApp
|
||||||
|
priority: 8 # default priority app is 50, since most of them require sensors created by virtual sensors, it's important that virtual_sensors start first
|
||||||
|
|
||||||
|
sensors:
|
||||||
|
binary_sensor.occupancy_living_room:
|
||||||
|
trigger_conditions: sensor.living_room_last_motion <= 5
|
||||||
|
binary_sensor.occupancy_kitchen:
|
||||||
|
trigger_conditions: sensor.kitchen_last_motion <= 5
|
||||||
|
binary_sensor.occupancy_restroom:
|
||||||
|
trigger_conditions: sensor.restroom_last_motion <= 3
|
||||||
@@ -1,16 +1,41 @@
|
|||||||
import ad_toolbox.smartcondition as SmartCondition
|
import ad_toolbox.smartcondition as SmartCondition
|
||||||
from ad_toolbox.smartobject import SmartObject
|
from ad_toolbox.smartobject import SmartObject
|
||||||
|
from importlib import metadata
|
||||||
|
import json
|
||||||
import pickle
|
import pickle
|
||||||
import os
|
import os
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
class InformationsCollector(SmartObject):
|
class InformationsCollector(SmartObject):
|
||||||
|
|
||||||
|
APPDAEMON_PYPI_URL = "https://pypi.org/pypi/appdaemon/json"
|
||||||
|
|
||||||
def on_initialize_smart_object(self):
|
def on_initialize_smart_object(self):
|
||||||
super().on_initialize_smart_object()
|
super().on_initialize_smart_object()
|
||||||
|
|
||||||
if self.dataset is None:
|
if self.dataset is None:
|
||||||
self.dataset = dict()
|
self.dataset = dict()
|
||||||
|
|
||||||
|
sensor_name = self.args.get('appdaemon_version_sensor')
|
||||||
|
legacy_version_config = self.args.get('appdaemon_version_collector')
|
||||||
|
if sensor_name is None and isinstance(legacy_version_config, dict):
|
||||||
|
sensor_name = legacy_version_config.get('output_sensor')
|
||||||
|
|
||||||
|
if sensor_name is not None:
|
||||||
|
self.appdaemon_version_sensor = self.create_entity(
|
||||||
|
sensor_name,
|
||||||
|
state='unknown',
|
||||||
|
attributes={
|
||||||
|
'latest_official_version': self.dataset.get('appdaemon_latest_official_version'),
|
||||||
|
'update_available': None,
|
||||||
|
},
|
||||||
|
icon='mdi:package-up'
|
||||||
|
)
|
||||||
|
self.update_appdaemon_version_sensor()
|
||||||
|
self.run_daily(self.update_appdaemon_version_sensor, "06:00:00")
|
||||||
|
elif legacy_version_config is not None:
|
||||||
|
self.log_error("Invalid appdaemon version sensor configuration")
|
||||||
|
|
||||||
if 'error_collector' in self.args:
|
if 'error_collector' in self.args:
|
||||||
if 'output_sensor' in self.args['error_collector']:
|
if 'output_sensor' in self.args['error_collector']:
|
||||||
sensor_name = self.args['error_collector']['output_sensor']
|
sensor_name = self.args['error_collector']['output_sensor']
|
||||||
@@ -31,10 +56,10 @@ class InformationsCollector(SmartObject):
|
|||||||
self.listen_state(self.on_temperature_change, sensor)
|
self.listen_state(self.on_temperature_change, sensor)
|
||||||
|
|
||||||
output_sensors = self.args['min_max_temp_sensors']['output_sensors']
|
output_sensors = self.args['min_max_temp_sensors']['output_sensors']
|
||||||
self.max_temp_sensor = self.get_entity(output_sensors['max_temp'])
|
self.max_temp_sensor = self.create_entity(output_sensors['max_temp'])
|
||||||
self.max_temp_yesterday_sensor = self.get_entity(output_sensors['max_temp_yesterday'])
|
self.max_temp_yesterday_sensor = self.create_entity(output_sensors['max_temp_yesterday'])
|
||||||
self.min_temp_sensor = self.get_entity(output_sensors['min_temp'])
|
self.min_temp_sensor = self.create_entity(output_sensors['min_temp'])
|
||||||
self.min_temp_yesterday_sensor = self.get_entity(output_sensors['min_temp_yesterday'])
|
self.min_temp_yesterday_sensor = self.create_entity(output_sensors['min_temp_yesterday'])
|
||||||
|
|
||||||
current_min_temp, current_max_temp = self.compute_min_max_temp()
|
current_min_temp, current_max_temp = self.compute_min_max_temp()
|
||||||
if not self.max_temp_sensor.exists() or self.max_temp_sensor.get_state() == 'unavailable':
|
if not self.max_temp_sensor.exists() or self.max_temp_sensor.get_state() == 'unavailable':
|
||||||
@@ -95,6 +120,74 @@ class InformationsCollector(SmartObject):
|
|||||||
self.dataset['min_temp'] = temperature
|
self.dataset['min_temp'] = temperature
|
||||||
self.min_temp_sensor.set_state(state = temperature, attributes = { 'unit_of_measurement': '°C', 'device_class': 'temperature'})
|
self.min_temp_sensor.set_state(state = temperature, attributes = { 'unit_of_measurement': '°C', 'device_class': 'temperature'})
|
||||||
|
|
||||||
|
def update_appdaemon_version_sensor(self, kwargs=None):
|
||||||
|
installed_version = self._get_installed_appdaemon_version()
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
latest_official_version = self.dataset.get('appdaemon_latest_official_version')
|
||||||
|
|
||||||
|
update_available = self._is_appdaemon_update_available(installed_version, latest_official_version)
|
||||||
|
|
||||||
|
self.appdaemon_version_sensor.set_state(
|
||||||
|
state=installed_version or 'unknown',
|
||||||
|
attributes={
|
||||||
|
'latest_official_version': latest_official_version,
|
||||||
|
'update_available': update_available,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_installed_appdaemon_version(self):
|
||||||
|
try:
|
||||||
|
return metadata.version('appdaemon')
|
||||||
|
except metadata.PackageNotFoundError:
|
||||||
|
self.log_warning("AppDaemon package metadata is unavailable")
|
||||||
|
except Exception as err:
|
||||||
|
self.log_warning(f"Unable to read installed AppDaemon version: {err}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
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')
|
||||||
|
except Exception as err:
|
||||||
|
self.log_warning(f"Unable to fetch latest official AppDaemon version: {err}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _is_appdaemon_update_available(self, installed_version, latest_official_version):
|
||||||
|
if installed_version is None or latest_official_version is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
installed_parts = self._parse_version_parts(installed_version)
|
||||||
|
latest_parts = self._parse_version_parts(latest_official_version)
|
||||||
|
|
||||||
|
if installed_parts is None or latest_parts is None:
|
||||||
|
return latest_official_version != installed_version
|
||||||
|
|
||||||
|
return latest_parts > installed_parts
|
||||||
|
|
||||||
|
def _parse_version_parts(self, version):
|
||||||
|
if not isinstance(version, str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
parts = []
|
||||||
|
for token in version.split('.'):
|
||||||
|
numeric_prefix = []
|
||||||
|
for char in token:
|
||||||
|
if not char.isdigit():
|
||||||
|
break
|
||||||
|
numeric_prefix.append(char)
|
||||||
|
|
||||||
|
if not numeric_prefix:
|
||||||
|
return None
|
||||||
|
|
||||||
|
parts.append(int(''.join(numeric_prefix)))
|
||||||
|
|
||||||
|
return tuple(parts)
|
||||||
|
|
||||||
def terminate(self):
|
def terminate(self):
|
||||||
self.log_info("Writing dataset to " + self.get_dataset_name())
|
self.log_info("Writing dataset to " + self.get_dataset_name())
|
||||||
f = open(self.get_dataset_name(), 'wb')
|
f = open(self.get_dataset_name(), 'wb')
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ light_kitchen_sink:
|
|||||||
|
|
||||||
smart_conditions:
|
smart_conditions:
|
||||||
- light.kitchen_sink_switch
|
- light.kitchen_sink_switch
|
||||||
- sensor.kitchen_last_motion < 3 and (sensor.kitchen_motion_light_level < 10 or self)
|
- binary_sensor.occupancy_kitchen and (sensor.kitchen_motion_light_level < 10 or self)
|
||||||
|
|
||||||
light_brightness_pct:
|
light_brightness_pct:
|
||||||
100: light.kitchen_sink_switch
|
100: light.kitchen_sink_switch
|
||||||
|
|||||||
Reference in New Issue
Block a user