diff --git a/__init__.py b/__init__.py index 835d4fd..afc685e 100644 --- a/__init__.py +++ b/__init__.py @@ -17,8 +17,6 @@ async def async_setup_entry(hass, entry): """Set up this component using config flow.""" _LOGGER.debug("async setup entry %s", entry.data["entities"]) unsub = entry.add_update_listener(update_listener) - #if PresenceSimulationSwitch.instances == 0: - # async_add_entities([PresenceSimulationSwitch(hass)], True) # Use `hass.async_create_task` to avoid a circular dependency between the platform and the component hass.async_create_task(hass.config_entries.async_forward_entry_setup(entry, SENSOR_PLATFORM)) @@ -34,7 +32,6 @@ async def async_setup(hass, config): async def async_mysetup(hass, entities, deltaStr): """Set up this component (YAML or UI).""" - #hass.states.async_set(DOMAIN+".running", "off") # replaced by the sensor delta = int(deltaStr) _LOGGER.debug("Entities for presence simulation: %s", entities) @@ -42,7 +39,6 @@ async def stop_presence_simulation(err=None): """Stop the presence simulation, raising a potential error""" entity = hass.data[DOMAIN][SENSOR_PLATFORM][SENSOR] entity.turn_off() - #hass.states.async_set(DOMAIN+".running", "off") if err is not None: _LOGGER.debug("Error in presence simulation, exiting") raise e @@ -63,7 +59,6 @@ async def async_expand_entities(entities): except Exception as e: _LOGGER.error("Error when trying to identify entity %s: %s", entity, e) else: - #await stop_presence_simulation(e) group_entities_expanded = await async_expand_entities(group_entities) _LOGGER.debug("State %s", group_entities_expanded) for tmp in group_entities_expanded: @@ -86,7 +81,6 @@ async def handle_presence_simulation(call): return running = True entity.turn_on() - #hass.states.async_set(DOMAIN+".running", "on") _LOGGER.debug("Started presence simulation") current_date = datetime.now(timezone.utc) @@ -98,8 +92,7 @@ async def handle_presence_simulation(call): for entity_id in dic: _LOGGER.debug('Entity %s', entity_id) #launch a thread by entity_id - #put the tasks in a tab to be able to kill them when the service stops - hass.async_create_task(simulate_single_entity(entity_id, dic[entity_id])) #coroutine + hass.async_create_task(simulate_single_entity(entity_id, dic[entity_id])) hass.async_create_task(restart_presence_simulation()) _LOGGER.debug("All async tasks launched") @@ -131,35 +124,29 @@ async def simulate_single_entity(entity_id, hist): for state in hist: #hypothsis: states are ordered chronologically _LOGGER.debug("State %s", state.as_dict()) _LOGGER.debug("Switch of %s foreseen at %s", entity_id, state.last_changed+timedelta(delta)) + entity = hass.data[DOMAIN][SENSOR_PLATFORM][SENSOR] + await entity.async_add_next_event(state.last_changed+timedelta(delta), entity_id, state.state) + while is_running(): minus_delta = datetime.now(timezone.utc) + timedelta(-delta) - #_LOGGER.debug("%s <= %s", state.last_changed, minus_delta) if state.last_changed <= minus_delta: break - #_LOGGER.debug("%s: will retry in 30 sec", entity_id) await asyncio.sleep(30) if not is_running(): return # exit if state is false #call service to turn on/off the light await update_entity(entity_id, state) + await entity.async_remove_event(entity_id) async def update_entity(entity_id, state): domain = entity_id.split('.')[0] service_data = {"entity_id": entity_id} if domain == "light": _LOGGER.debug("Switching light %s to %s", entity_id, state.state) - try: - if state.attributes["brightness"]: + if "brightness" in state.attributes: service_data["brightness"] = state.attributes["brightness"] - except: - #brightness not defined - pass - try: - if state.attributes["rgb_color"]: + if "rgb_color" in state.attributes: service_data["rgb_color"] = state.attributes["rgb_color"] - except: - #rgb_color not defined - pass await hass.services.async_call("light", "turn_"+state.state, service_data, blocking=False) else: _LOGGER.debug("Switching entity %s to %s", entity_id, state.state) @@ -167,7 +154,6 @@ async def update_entity(entity_id, state): def is_running(): """Returns true if the simulation is running""" - #return hass.states.get(DOMAIN+'.running').state == "on" entity = hass.data[DOMAIN][SENSOR_PLATFORM][SENSOR] return entity.is_on @@ -182,7 +168,6 @@ async def update_listener(hass, entry): """Update listener after an update in the UI""" _LOGGER.debug("Updating listener"); # The OptionsFlow saves data to options. - # Move them back to data and clean options (dirty, but not sure how else to do that) if len(entry.options) > 0: entry.data = entry.options entry.options = {} diff --git a/sensor.py b/sensor.py index fee2b2c..3938001 100644 --- a/sensor.py +++ b/sensor.py @@ -1,11 +1,13 @@ from homeassistant.helpers.entity import ToggleEntity +from datetime import datetime, timezone, timedelta +import math import logging from .const import ( DOMAIN, SENSOR_PLATFORM, SENSOR ) - +SCAN_INTERVAL = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) async def async_setup_platform(hass, _, async_add_entities, discovery_info=None): @@ -23,9 +25,13 @@ async def async_setup_entry(hass, config_entry, async_add_devices): class PresenceSimulationSwitch(ToggleEntity): instances = 0 + def __init__(self, hass): self.turn_off() self.hass = hass + self.attr={} + self.attr["friendly_name"] = "Presence Simulation Toggle" + self._next_events = [] PresenceSimulationSwitch.instances += 1 pass @@ -46,18 +52,27 @@ def turn_on(self, **kwargs): def turn_off(self, **kwargs): self._state = "off" + self._next_events = [] async def async_update(self): - pass + if len(self._next_events) > 0: + self.attr["next_event_datetime"], self.attr["next_entity_id"], self.attr["next_entity_state"] = sorted(self._next_events)[0] + else: + for prop in ("next_event_datetime", "next_entity_id", "next_entity_state"): + if prop in self.attr: + del self.attr[prop] def update(self): - pass + if len(self._next_events) > 0: + self.attr["next_event_datetime"], self.attr["next_entity_id"], self.attr["next_entity_state"] = sorted(self._next_events)[0] + else: + for prop in ("next_event_datetime", "next_entity_id", "next_entity_state"): + if prop in self.attr: + del self.attr[prop] @property def device_state_attributes(self): - attr={} - attr["friendly_name"] = "Presence Simulation Toggle" - return attr + return self.attr async def async_added_to_hass(self): """When sensor is added to hassio.""" @@ -67,3 +82,13 @@ async def async_added_to_hass(self): if SENSOR_PLATFORM not in self.hass.data[DOMAIN]: self.hass.data[DOMAIN][SENSOR_PLATFORM] = {} self.hass.data[DOMAIN][SENSOR_PLATFORM][SENSOR] = self + + async def async_add_next_event(self, next_datetime, entity_id, state): + self._next_events.append((next_datetime, entity_id, state)) + + async def async_remove_event(self, entity_id): + i=0 + for e in self._next_events: + if e[1] == entity_id: + del self._next_events[i] + i += 1