diff --git a/devices/__init__.py b/devices/__init__.py index b4ea53b..c68448f 100644 --- a/devices/__init__.py +++ b/devices/__init__.py @@ -26,10 +26,6 @@ devices (DEVICES) """ -# TODO: usage of base.mqtt as parent for class base - -__DEPENDENCIES__ = [] - import json import logging import task diff --git a/function/modules.py b/function/modules.py index c734ac1..1d11797 100644 --- a/function/modules.py +++ b/function/modules.py @@ -272,7 +272,7 @@ class motion_sensor_light(common_base): # self.sw_device = sw_device self.sw_method = sw_method - self.args = args + self.motion_sensors = args self.timer_reload_value = timer_value # sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, True, self.reload_timer, True) @@ -292,7 +292,7 @@ class motion_sensor_light(common_base): self.set(self.KEY_TIMER, 0) def set_motion_detected(self, device, key, data): - for sensor_index, arg_device in enumerate(self.args): + for sensor_index, arg_device in enumerate(self.motion_sensors): if arg_device.topic == device.topic: break self.set(self.KEY_MOTION_SENSOR % sensor_index, data) @@ -302,7 +302,7 @@ class motion_sensor_light(common_base): self.sw_method(True) def motion_detected(self): - for i in range(0, len(self.args)): + for i in range(0, len(self.motion_sensors)): if self[self.KEY_MOTION_SENSOR % i]: return True return False diff --git a/function/videv.py b/function/videv.py index 05cb17e..f593888 100644 --- a/function/videv.py +++ b/function/videv.py @@ -10,17 +10,10 @@ Targets: * No functionality should be implemented here """ -# TODO: Extend virtual devices -# * Digital-Audio-Sources (Spotify, MPD, Currently playing) oder direkt von my_apps?! -# * - from base import mqtt_base import devices import json -BASETOPIC = "videv" - - try: from config import APP_NAME as ROOT_LOGGER_NAME except ImportError: @@ -28,174 +21,160 @@ except ImportError: class base(mqtt_base): - EXEC_RX_FUNC_ALWAYS = [] + KEY_INFO = '__info__' - def __init__(self, mqtt_client, topic, *args, default_values=None): + def __init__(self, mqtt_client, topic, default_values=None): super().__init__(mqtt_client, topic, default_values=default_values) - self.__device_list__ = {} - for videv_key, device in [reduced[:2] for reduced in args]: - self.__device_list__[videv_key] = device - # send initial state - for key in self.keys(): - self.__tx__(key, self[key]) - # add receive topics - mqtt_client.add_callback(self.topic + "/#", self.__rx__) + self.__display_dict__ = {} + self.__control_dict__ = {} + self.__capabilities__ = None + + def add_display(self, my_key, ext_device, ext_key, on_change_only=True): + """ + listen to data changes of ext_device and update videv information + """ + if ext_device.__class__.__name__ == "group": + # store information to identify callback from ext_device + self.__display_dict__[(id(ext_device[0]), ext_key)] = my_key + # register a callback to listen for data from external device + ext_device[0].add_callback(ext_key, None, self.__rx_ext_device_data__, on_change_only) + else: + # store information to identify callback from ext_device + self.__display_dict__[(id(ext_device), ext_key)] = my_key + # register a callback to listen for data from external device + ext_device.add_callback(ext_key, None, self.__rx_ext_device_data__, on_change_only) + # send default data to videv interface + + def __rx_ext_device_data__(self, ext_device, ext_key, data): + self.__tx__(self.__display_dict__[(id(ext_device), ext_key)], data) def __tx__(self, key, data): if type(data) not in (str, ): data = json.dumps(data) - if key in self.keys(): - self.mqtt_client.send(self.topic + '/' + key, data) - else: - self.logger.warning("Ignoring send request for key %s (not available for this class)", key) + self.mqtt_client.send(self.topic + '/' + key, data) + self.__tx_capabilities__() - def __rx__(self, client, userdata, message): - key = message.topic.split('/')[-1] - if key in self.keys(): + def __tx_capabilities__(self): + self.mqtt_client.send(self.topic + '/' + self.KEY_INFO, json.dumps(self.capabilities)) + + def add_control(self, my_key, ext_device, ext_key, on_change_only=True): + """ + listen to videv information and pass data to ext_device + """ + self[my_key] = None + # store information to identify callback from videv + self.__control_dict__[my_key] = (ext_device, ext_key, on_change_only) + # add callback for videv changes + self.mqtt_client.add_callback(self.topic + '/' + my_key, self.__rx_videv_data__) + + def __rx_videv_data__(self, client, userdata, message): + my_key = message.topic.split('/')[-1] + ext_device, ext_key, on_change_only = self.__control_dict__[my_key] + if my_key in self.keys(): try: data = json.loads(message.payload) except json.decoder.JSONDecodeError: data = message.payload - if data != self[key] or key in self.EXEC_RX_FUNC_ALWAYS: - self.__rx_functionality__(key, data) - self.set(key, data) + if data != self[my_key] or not on_change_only: + ext_device.set(ext_key, data) + self.set(my_key, data) else: self.logger.info("Ignoring rx message with topic %s", message.topic) - def __rx_functionality__(self, key, data): - raise NotImplemented("Method __rx_functionality__ needs to be implemented in child class") + def add_routing(self, my_key, ext_device, ext_key, on_change_only_disp=True, on_change_only_videv=True): + """ + listen to data changes of ext_device and update videv information + and + listen to videv information and pass data to ext_device + """ + # add display + self.add_display(my_key, ext_device, ext_key, on_change_only_disp) + self.add_control(my_key, ext_device, ext_key, on_change_only_videv) - def __device_data__(self, device, key, data): - raise NotImplemented("Method __device_data__ needs to be implemented in child class") + @property + def capabilities(self): + if self.__capabilities__ is None: + self.__capabilities__ = {} + self.__capabilities__['__type__'] = self.__class__.__name__ + for key in self.__control_dict__: + if not key in self.__capabilities__: + self.__capabilities__[key] = {} + self.__capabilities__[key]['control'] = True + for key in self.__display_dict__.values(): + if not key in self.__capabilities__: + self.__capabilities__[key] = {} + self.__capabilities__[key]['display'] = True + return self.__capabilities__ -class base_routing(base): - def __init__(self, mqtt_client, topic, *args, default_values=None): - super().__init__(mqtt_client, topic, *args, default_values=default_values) - # - self.__device_key__ = {} - index = 0 - for videv_key, device, device_key in args: - if self.__device_list__[videv_key] != device: - raise ReferenceError("Parent class generated a deviating device list") - self.__device_key__[videv_key] = device_key - index += 1 - # add callbacks - for key in self.__device_list__: - if self.__device_list__[key].__class__.__name__ == "group": - self.__device_list__[key][0].add_callback(self.__device_key__[key], None, self.__device_data__, True) - else: - self.__device_list__[key].add_callback(self.__device_key__[key], None, self.__device_data__, True) - - def __rx_functionality__(self, key, data): - try: - self.__device_list__[key].set(self.__device_key__[key], data) - except KeyError: - self.logger.warning("RX passthrough not possible for key %s", key) - - def __device_data__(self, device, key, data): - l1 = [] - for k, v in self.__device_list__.items(): - if v.__class__.__name__ == "group": - if id(device) in [id(d) for d in v]: - l1.append(k) - else: - if id(v) == id(device): - l1.append(k) - l2 = [k for k, v in self.__device_key__.items() if v == key] - try: - videv_key = [k for k in l1 if k in l2][0] - except IndexError: - self.logger.warning("videv_key not available for %s::%s", device.__class__.__name__, device.topic) - else: - self.set(videv_key, data) - self.__tx__(videv_key, data) - - -class videv_switching(base_routing): +class videv_switching(base): KEY_STATE = 'state' - # - DEFAULT_VALUES = { - KEY_STATE: False, - } def __init__(self, mqtt_client, topic, sw_device, sw_key): - super().__init__(mqtt_client, topic, (self.KEY_STATE, sw_device, sw_key)) + super().__init__(mqtt_client, topic) + self.add_routing(self.KEY_STATE, sw_device, sw_key) + # + self.__tx_capabilities__() -class videv_switching_timer(base_routing): +class videv_switching_timer(base): KEY_STATE = 'state' KEY_TIMER = 'timer' - # - DEFAULT_VALUES = { - KEY_STATE: False, - KEY_TIMER: 0 - } def __init__(self, mqtt_client, topic, sw_device, sw_key, tm_device, tm_key): - super().__init__(mqtt_client, topic, (self.KEY_STATE, sw_device, sw_key), (self.KEY_TIMER, tm_device, tm_key)) + super().__init__(mqtt_client, topic) + self.add_routing(self.KEY_STATE, sw_device, sw_key) + self.add_display(self.KEY_TIMER, tm_device, tm_key) + # + self.__tx_capabilities__() -class videv_switching_motion(base_routing): +class videv_switching_motion(base): KEY_STATE = 'state' + # KEY_TIMER = 'timer' KEY_MOTION_SENSOR = 'motion_%d' - # - DEFAULT_VALUES = { - KEY_STATE: False, - KEY_TIMER: 0 - } def __init__(self, mqtt_client, topic, sw_device, sw_key, motion_function): - dv = {self.KEY_STATE: False, self.KEY_TIMER: 0} - for i in range(0, len(motion_function.args)): - dv[motion_function.KEY_MOTION_SENSOR % i] = False - super().__init__( - mqtt_client, topic, - (self.KEY_STATE, sw_device, sw_key), - (self.KEY_TIMER, motion_function, motion_function.KEY_TIMER), - *[[self.KEY_MOTION_SENSOR % i, motion_function, motion_function.KEY_MOTION_SENSOR % i] for i in range(0, len(motion_function.args))], - default_values=dv - ) + self.motion_sensors = motion_function.motion_sensors + # + super().__init__(mqtt_client, topic) + self.add_routing(self.KEY_STATE, sw_device, sw_key) + self.add_display(self.KEY_TIMER, motion_function, motion_function.KEY_TIMER) + # motion sensor state + for index, motion_sensor in enumerate(self.motion_sensors): + self.add_display(self.KEY_MOTION_SENSOR % index, motion_sensor, motion_sensor.KEY_OCCUPANCY) + # + self.__tx_capabilities__() -class videv_switch_brightness(base_routing): +class videv_switch_brightness(base): KEY_STATE = 'state' KEY_BRIGHTNESS = 'brightness' - # - DEFAULT_VALUES = { - KEY_STATE: False, - KEY_BRIGHTNESS: 0 - } def __init__(self, mqtt_client, topic, sw_device, sw_key, br_device, br_key): + super().__init__(mqtt_client, topic) + self.add_routing(self.KEY_STATE, sw_device, sw_key) + self.add_routing(self.KEY_BRIGHTNESS, br_device, br_key) # - super().__init__(mqtt_client, topic, (self.KEY_STATE, sw_device, sw_key), (self.KEY_BRIGHTNESS, br_device, br_key)) + self.__tx_capabilities__() -class videv_switch_brightness_color_temp(base_routing): +class videv_switch_brightness_color_temp(base): KEY_STATE = 'state' KEY_BRIGHTNESS = 'brightness' KEY_COLOR_TEMP = 'color_temp' - # - DEFAULT_VALUES = { - KEY_STATE: False, - KEY_BRIGHTNESS: 0, - KEY_COLOR_TEMP: 0, - } def __init__(self, mqtt_client, topic, sw_device, sw_key, br_device, br_key, ct_device, ct_key): + super().__init__(mqtt_client, topic) + self.add_routing(self.KEY_STATE, sw_device, sw_key) + self.add_routing(self.KEY_BRIGHTNESS, br_device, br_key) + self.add_routing(self.KEY_COLOR_TEMP, ct_device, ct_key) # - super().__init__( - mqtt_client, topic, - (self.KEY_STATE, sw_device, sw_key), - (self.KEY_BRIGHTNESS, br_device, br_key), - (self.KEY_COLOR_TEMP, ct_device, ct_key) - ) + self.__tx_capabilities__() -class videv_heating(base_routing): - KEY_TEMPERATURE = 'temperature' +class videv_heating(base): KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint' KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint' KEY_AWAY_MODE = 'away_mode' @@ -204,69 +183,66 @@ class videv_heating(base_routing): KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature' KEY_BOOST_TIMER = 'boost_timer' # - EXEC_RX_FUNC_ALWAYS = [KEY_START_BOOST, KEY_SET_DEFAULT_TEMPERATURE, KEY_USER_TEMPERATURE_SETPOINT] + KEY_TEMPERATURE = 'temperature' def __init__(self, mqtt_client, topic, heating_function): + super().__init__(mqtt_client, topic) # - super().__init__( - mqtt_client, topic, - (self.KEY_TEMPERATURE, heating_function, heating_function.KEY_TEMPERATURE_CURRENT), - (self.KEY_USER_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_USER_TEMPERATURE_SETPOINT), - (self.KEY_VALVE_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_TEMPERATURE_SETPOINT), - (self.KEY_AWAY_MODE, heating_function, heating_function.KEY_AWAY_MODE), - (self.KEY_SUMMER_MODE, heating_function, heating_function.KEY_SUMMER_MODE), - (self.KEY_START_BOOST, heating_function, heating_function.KEY_START_BOOST), - (self.KEY_SET_DEFAULT_TEMPERATURE, heating_function, heating_function.KEY_SET_DEFAULT_TEMPERATURE), - (self.KEY_BOOST_TIMER, heating_function, heating_function.KEY_BOOST_TIMER), - default_values={ - self.KEY_TEMPERATURE: heating_function[heating_function.KEY_TEMPERATURE_CURRENT], - self.KEY_VALVE_TEMPERATURE_SETPOINT: heating_function[heating_function.KEY_TEMPERATURE_SETPOINT], - self.KEY_USER_TEMPERATURE_SETPOINT: heating_function[heating_function.KEY_USER_TEMPERATURE_SETPOINT], - self.KEY_AWAY_MODE: heating_function[heating_function.KEY_AWAY_MODE], - self.KEY_SUMMER_MODE: heating_function[heating_function.KEY_SUMMER_MODE], - self.KEY_BOOST_TIMER: heating_function[heating_function.KEY_BOOST_TIMER], - self.KEY_START_BOOST: True, - self.KEY_SET_DEFAULT_TEMPERATURE: True, - } - ) + self.add_routing(self.KEY_USER_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_USER_TEMPERATURE_SETPOINT) + self.add_routing(self.KEY_AWAY_MODE, heating_function, heating_function.KEY_AWAY_MODE) + self.add_routing(self.KEY_SUMMER_MODE, heating_function, heating_function.KEY_SUMMER_MODE) + # + self.add_control(self.KEY_START_BOOST, heating_function, heating_function.KEY_START_BOOST, False) + self.add_control(self.KEY_SET_DEFAULT_TEMPERATURE, heating_function, heating_function.KEY_SET_DEFAULT_TEMPERATURE, False) + # + self.add_display(self.KEY_VALVE_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_TEMPERATURE_SETPOINT) + self.add_display(self.KEY_BOOST_TIMER, heating_function, heating_function.KEY_BOOST_TIMER) + self.add_display(self.KEY_TEMPERATURE, heating_function, heating_function.KEY_TEMPERATURE_CURRENT) + # + self.__tx_capabilities__() class videv_multistate(base): + KEY_STATE = 'state_%d' + def __init__(self, mqtt_client, topic, key_for_device, device, num_states, default_values=None): - dv = dict.fromkeys(["state_%d" % i for i in range(0, num_states)]) - for key in dv: - dv[key] = False - super().__init__(mqtt_client, topic, (key_for_device, device), default_values=dv) + super().__init__(mqtt_client, topic) + self.num_states = num_states + # send default values + for i in range(0, num_states): + self.__tx__(self.KEY_STATE % i, False) # device.add_callback(key_for_device, None, self.__index_rx__, True) + # + self.__tx_capabilities__() def __index_rx__(self, device, key, data): - for index, key in enumerate(self): - self.set(key, index == data) - self.__tx__(key, self[key]) - - def __rx_functionality__(self, key, data): - pass # read only device + for i in range(0, self.num_states): + self.__tx__(self.KEY_STATE % i, i == data) + # + self.__tx_capabilities__() -class videv_audio_player(base_routing): +class videv_audio_player(base): KEY_ACTIVE_PLAYER = 'player_%d' KEY_TITLE = 'title' NO_TITLE = '---' def __init__(self, mqtt_client, topic, *args): - dv = dict.fromkeys([self.KEY_ACTIVE_PLAYER % i for i in range(0, len(args))]) - for key in dv: - dv[key] = False - dv[self.KEY_TITLE] = self.NO_TITLE - super().__init__( - mqtt_client, topic, - *[[self.KEY_ACTIVE_PLAYER % i, device, devices.audio_status.KEY_STATE] for i, device in enumerate(args)], - default_values=dv - ) + super().__init__(mqtt_client, topic) + for i, device in enumerate(args): + self.add_display(self.KEY_ACTIVE_PLAYER % i, device, device.KEY_STATE) + # for audio_device in args: audio_device.add_callback(audio_device.KEY_TITLE, None, self.__title_rx__, True) + # + self.__tx_capabilities__() def __title_rx__(self, device, key, data): - self.set(self.KEY_TITLE, data or self.NO_TITLE) - self.__tx__(self.KEY_TITLE, self[self.KEY_TITLE]) + self.__tx__(self.KEY_TITLE, data or self.NO_TITLE) + + @property + def capabilities(self): + super().capabilities + self.__capabilities__[self.KEY_TITLE] = {'display': True} + return self.__capabilities__ diff --git a/smart_brain.py b/smart_brain.py index 13fc00c..99bd463 100644 --- a/smart_brain.py +++ b/smart_brain.py @@ -7,6 +7,14 @@ import time logger = logging.getLogger(config.APP_NAME) +# TODO: Change Nodered topics to videv +# TODO: Extend virtual devices +# * All Off +# * ... +# TODO: Remove gui from rooms and devices +# TODO: Rework devices to base.mqtt (pack -> set, ...) +# TODO: Implement handling of warnings (videv element to show in webapp?) + if __name__ == "__main__": if config.DEBUG: @@ -19,8 +27,5 @@ if __name__ == "__main__": func = function.all_functions(mc) - # for device in func.devicelist(): - # device.add_warning_callback(None) - while (True): time.sleep(1)