From a070dc679f1f34069751bb8c07e750af1068e9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Fri, 28 Dec 2018 21:47:23 +0100 Subject: [PATCH] appd - Completed redesign of entity manager --- appdaemon/apps/base.py | 168 ----------------------- appdaemon/apps/helloworld.yaml | 4 - appdaemon/apps/helpers/base.py | 6 + appdaemon/apps/{ => helpers}/entities.py | 51 ++++++- appdaemon/apps/helpers/helpers.yaml | 4 + appdaemon/apps/helpers/timers.py | 42 ++++++ appdaemon/apps/timeofday.py | 5 +- 7 files changed, 103 insertions(+), 177 deletions(-) delete mode 100644 appdaemon/apps/base.py create mode 100644 appdaemon/apps/helpers/base.py rename appdaemon/apps/{ => helpers}/entities.py (71%) create mode 100644 appdaemon/apps/helpers/helpers.yaml create mode 100644 appdaemon/apps/helpers/timers.py diff --git a/appdaemon/apps/base.py b/appdaemon/apps/base.py deleted file mode 100644 index a2f2fa5..0000000 --- a/appdaemon/apps/base.py +++ /dev/null @@ -1,168 +0,0 @@ -import appdaemon.plugins.hass.hassapi as hass - -class Base(hass.Hass): - def initialize(self): - if(getattr(super(), 'initialize', False)): - super().initialize() - -class Timers(Base): - def initialize(self): - if(getattr(super(), 'initialize', False)): - super().initialize() - self._timers = {} - - functions = [ - 'run_in', - 'run_once', - 'run_at', - 'run_daily', - 'run_hourly', - 'run_minutely', - 'run_every', - 'run_at_sunrise', - 'run_at_sunset', - ] - for f in functions: - self._override(f) - - setattr(self, '_cancel_timer', super().cancel_timer) - - def cancel_timer(self, name, *args, **kwargs): - if type(name) is str: - if name in self._timers: - return super().cancel_timer(self._timers[name]) - else: - return super().cancel_timer(*args, **kwargs) - - def _override(self, f): - setattr(self, f'_{f}', getattr(self, f)) - def fn(name, *args, **kwargs): - if type(name) is str: - if name in self._timers: - super().cancel_timer(self._timers[name]) - self._timers[name] = getattr(self, f'_{f}')(*args, **kwargs) - return self._timers[name] - else: - return getattr(self, f'_{f}')(name, *args, **kwargs) - setattr(self, f, fn) - -class Entities(Base): - def initialize(self): - if(getattr(super(), 'initialize', False)): - super().initialize() - - self.e = {} - - def register_entity(self, name, entity, managed=False, default=None, attributes=None): - domain, _ = entity.split('.') - controller = { - 'light': Entities.LightEntity, - 'input_datetime': Entities.InputDateTimeEntity, - 'input_number': Entities.InputNumberEntity, - }.get(domain, Entities.Entity) - self.e[name] = controller(self, entity, managed, default, attributes) - - class Entity: - def __init__(self, hass, entity, managed = False, default = None, attributes = None): - self._entity = entity - self._hass = hass - self._hass.listen_state(self._listener, entity=entity, attributes='all') - self._listeners = [] - if managed: - if default: - self.state = default - self.update(attributes) - - def listen(self, callback, kwarg=None): - """ Listen to changes to entity state """ - self._listeners.append({ - 'callback': callback, - 'kwarg': kwarg, - }) - return self._listeners[-1] - def unlisten(self, handle): - """ Remove state change listener """ - if handle in self._listeners: - self._listeners.remove(handle) - def _listener(self, entity, attribute, old, new, kwargs): - for l in self._listeners: - l['callback'](l['kwarg']) - - def __getattr__(self, key): - if key == 'state': - if self.get_state: - return self.get_state() - return self._hass.get_state(self._entity) - return self._hass.get_state(self._entity, - attribute=key) - def __setattr__(self, key, value): - if key.startswith('_'): - self.__dict__[key] = value - return - if key == 'state': - if self.set_state: - self.set_state(value) - return self._hass.set_state(self._entity, state=value) - attr = self._hass.get_state(self._entity, - attribute='all') - attr = attr.get('attributes', {}) if attr else {} - attr[key] = value - self._hass.set_state(self._entity, attributes=attr) - def __delattr__(self, key): - if key.startswith('_'): - del self.__dict__[key] - return - attr = self._hass.get_state(self._entity, - attribute='all').get('attributes', {}) - attr[key] = '' - self._hass.set_state(self._entity, attributes=attr) - - def update(self, new): - attr = self._hass.get_state(self._entity, attribute='all').get('attributes', {}) - attr.update(new) - self._hass.set_state(self._entity, attributes=attr) - - class LightEntity(Entities.Entity): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def set_state(self, state): - if state == 'on': - self._hass.call_service('light/turn_on', entity_id = - self._entity) - elif state == 'off': - self._hass.call_service('light/turn_off', entity_id = - self._entity) - else: - return - - class InputNumberEntity(Entities.Entity): - def __init__(self, hass, entity, managed = False, default = None, attributes = None): - super().__init__(hass, entity, managed, default, attributes) - if managed: - hass.listen_event(self.service_callback, event = 'call_service') - - def service_callback(self, event, data, kwargs): - if data['service_data'].get('entity_id', '') == self._entity and data['service'] == 'set_value': - self._hass.log("Value changed!") - - - class InputDateTimeEntity(Entities.Entity): - def __init__(self, hass, entity, managed = False, default = None, attributes = None): - super().__init__(hass, entity, managed, default, attributes) - if managed: - hass.listen_event(self.service_callback, event = 'call_service') - - def service_callback(self, event, data, kwargs): - if data['service_data'].get('entity_id', '') == self._entity and data['service'] == 'set_datetime': - self._hass.log("Datetime changed!") - - def set_state(self, state): - time = state.split(':') - time += ['00'] * (3 - len(time)) - self._hass.set_state(self._entity, state=':'.join(time)) - self.update({ - 'hour': int(time[0], base=10), - 'minute': int(time[1], base=10), - 'second': int(time[2], base=10), - }) diff --git a/appdaemon/apps/helloworld.yaml b/appdaemon/apps/helloworld.yaml index b965f31..507d1ea 100644 --- a/appdaemon/apps/helloworld.yaml +++ b/appdaemon/apps/helloworld.yaml @@ -1,7 +1,3 @@ -global_modules: - - base - - entities - hello_world: global_dependencies: - base diff --git a/appdaemon/apps/helpers/base.py b/appdaemon/apps/helpers/base.py new file mode 100644 index 0000000..2bd2a2e --- /dev/null +++ b/appdaemon/apps/helpers/base.py @@ -0,0 +1,6 @@ +import appdaemon.plugins.hass.hassapi as hass + +class Base(hass.Hass): + def initialize(self): + if(getattr(super(), 'initialize', False)): + super().initialize() diff --git a/appdaemon/apps/entities.py b/appdaemon/apps/helpers/entities.py similarity index 71% rename from appdaemon/apps/entities.py rename to appdaemon/apps/helpers/entities.py index 4185276..8028b52 100644 --- a/appdaemon/apps/entities.py +++ b/appdaemon/apps/helpers/entities.py @@ -1,10 +1,10 @@ import base class Entities(base.Base): + def initialize(self): if(getattr(super(), 'initialize', False)): super().initialize() - self.e = {} def register_entity(self, name, entity, managed=False, default=None, attributes=None): @@ -12,10 +12,15 @@ class Entities(base.Base): controller = { 'light': LightEntity, 'switch': SwitchEntity, + 'input_number': InputNumberEntity, + 'input_datetime': InputDateTimeEntity, + 'input_select': InputSelectEntity, }.get(domain, Entity) self.e[name] = controller(self, entity, managed, default, attributes) + class Entity: + def __init__(self, hass, entity, managed=False, state=None, attributes=None): self._hass = hass self._entity = entity @@ -33,15 +38,18 @@ class Entity: else: self.pull() + # State change listeners def listen(self, callback, kwarg=None): self._listeners.append({ 'callback': callback, 'kwarg': kwarg, }) return self._listeners[-1] + def unlisten(self, handle): if handle in self._listeners: self._listeners.remove(handle) + def _listener(self, entity, attribute, old, new, kwargs): self._state = new['state'] self._attributes = new['attributes'] @@ -49,13 +57,13 @@ class Entity: if old != new: self._laststate = new self._callback(old, new) + def _callback(self, old, new): for l in self._listeners: l['callback'](old, new, l['kwarg']) pass - - + # Updating state def pull(self): d = self._hass.get_state(self._entity, attribute='all') self._state = d['state'] @@ -74,12 +82,15 @@ class Entity: def set_state(self, old, new): pass + # If the entity is controller by appd, changes made in the GUI will be communicated via service calls def _service_listener(self, event, data, kwarg): if data['service_data'].get('entity_id', '') == self._entity: self.service_callback(data) + def service_callback(self, data): pass + # @property def state(self): return self._state @@ -91,17 +102,51 @@ class Entity: def attr(self): return self._attributes + class LightEntity(Entity): + def set_state(self, old, new): if new == "on": self._hass.call_service("light/turn_on", entity_id = self._entity) elif new == "off": self._hass.call_service("light/turn_off", entity_id = self._entity) + class SwitchEntity(Entity): + def service_callback(self, data): if data['service'] == 'turn_on': self._state = "on" if data['service'] == 'turn_off': self._state = "off" self.push() + + +class InputNumberEntity(Entity): + + def service_callback(self, data): + if data['domain'] == 'input_number' and data['service'] == 'set_value': + self._state = data['service_data']['value'] + self.push() + +class InputDateTimeEntity(Entity): + + def set_state(self, old, new): + time = new.split(':') + time += ["00"] * (3 - len(time)) + self._state = ':'.join(time) + self.attr['hour'] = int(time[0], base=10) + self.attr['minute'] = int(time[1], base=10) + self.attr['seconf'] = int(time[2], base=10) + + def service_callback(self, data): + if data['service'] == 'set_datetime': + self._state = data['service_data'].get('time', "00:00:00") + self.push() + +class InputSelectEntity(Entity): + + def service_callback(self, data): + if data['service'] == 'select_option': + self._state = data['service_data'].get('option', None) + self.push() diff --git a/appdaemon/apps/helpers/helpers.yaml b/appdaemon/apps/helpers/helpers.yaml new file mode 100644 index 0000000..6fe5458 --- /dev/null +++ b/appdaemon/apps/helpers/helpers.yaml @@ -0,0 +1,4 @@ +global_modules: + - base + - entities + - timers diff --git a/appdaemon/apps/helpers/timers.py b/appdaemon/apps/helpers/timers.py new file mode 100644 index 0000000..b8dc97b --- /dev/null +++ b/appdaemon/apps/helpers/timers.py @@ -0,0 +1,42 @@ +import base + +class Timers(base.Base): + def initialize(self): + if(getattr(super(), 'initialize', False)): + super().initialize() + self._timers = {} + + functions = [ + 'run_in', + 'run_once', + 'run_at', + 'run_daily', + 'run_hourly', + 'run_minutely', + 'run_every', + 'run_at_sunrise', + 'run_at_sunset', + ] + for f in functions: + self._override(f) + + setattr(self, '_cancel_timer', super().cancel_timer) + + def cancel_timer(self, name, *args, **kwargs): + if type(name) is str: + if name in self._timers: + return super().cancel_timer(self._timers[name]) + else: + return super().cancel_timer(*args, **kwargs) + + def _override(self, f): + setattr(self, f'_{f}', getattr(self, f)) + def fn(name, *args, **kwargs): + if type(name) is str: + if name in self._timers: + super().cancel_timer(self._timers[name]) + self._timers[name] = getattr(self, f'_{f}')(*args, **kwargs) + return self._timers[name] + else: + return getattr(self, f'_{f}')(name, *args, **kwargs) + setattr(self, f, fn) diff --git a/appdaemon/apps/timeofday.py b/appdaemon/apps/timeofday.py index b5362a1..77e170b 100644 --- a/appdaemon/apps/timeofday.py +++ b/appdaemon/apps/timeofday.py @@ -1,5 +1,6 @@ import base -class TimeOfDay(base.Entities): +import entities +class TimeOfDay(entities.Entities): def initialize(self): super().initialize() @@ -18,5 +19,5 @@ class TimeOfDay(base.Entities): self.e['sunrise'].listen(self.input_listener, {'changed': "sunrise"}) - def input_listener(self, kwargs): + def input_listener(self, old, new, kwargs): self.log(kwargs)