#!/usr/bin/env python # -*- coding: utf-8 -*- # """ Virtual Device(s) Targets: * MQTT-Interface to control joined devices as one virtual device * Primary signal routing * No functionality should be implemented here """ from base import mqtt_base from function.rooms import room, room_collection import json import time try: from config import APP_NAME as ROOT_LOGGER_NAME except ImportError: ROOT_LOGGER_NAME = 'root' class base(mqtt_base): KEY_INFO = '__info__' def __init__(self, mqtt_client, topic, default_values=None): super().__init__(mqtt_client, topic, default_values=default_values) self.__display_dict__ = {} self.__control_dict__ = {} self.__capabilities__ = None self.__active_tx__ = {} 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): my_key = self.__display_dict__[(id(ext_device), ext_key)] self[my_key] = data self.__tx__(my_key, data) def __tx__(self, key, data): if key in self.__control_dict__: self.__active_tx__[key] = (time.time(), data) if type(data) not in (str, ): data = json.dumps(data) self.mqtt_client.send(self.topic + '/' + key, data) self.__tx_capabilities__() 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] try: data = json.loads(message.payload) except json.decoder.JSONDecodeError: data = message.payload if my_key in self.__active_tx__: tm, tx_data = self.__active_tx__.pop(my_key) do_ex = data != tx_data and time.time() - tm < 2 else: do_ex = True if do_ex: ext_device, ext_key, on_change_only = self.__control_dict__[my_key] if my_key in self.keys(): if data != self[my_key] or not on_change_only: ext_device.send_command(ext_key, data) self.set(my_key, data) else: self.logger.info("Ignoring rx message with topic %s", message.topic) 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) @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 videv_switching(base): KEY_STATE = 'state' def __init__(self, mqtt_client, topic, 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): KEY_STATE = 'state' KEY_TIMER = 'timer' def __init__(self, mqtt_client, topic, sw_device, sw_key, 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): KEY_STATE = 'state' # KEY_TIMER = 'timer' KEY_MOTION_SENSOR = 'motion_%d' def __init__(self, mqtt_client, topic, sw_device, sw_key, motion_function): 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): KEY_STATE = 'state' KEY_BRIGHTNESS = 'brightness' 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) # self.__tx_capabilities__() class videv_switch_brightness_color_temp(base): KEY_STATE = 'state' KEY_BRIGHTNESS = 'brightness' KEY_COLOR_TEMP = 'color_temp' 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) # self.__tx_capabilities__() class videv_heating(base): KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint' KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint' KEY_AWAY_MODE = 'away_mode' KEY_SUMMER_MODE = 'summer_mode' KEY_START_BOOST = 'start_boost' KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature' KEY_BOOST_TIMER = 'boost_timer' # KEY_TEMPERATURE = 'temperature' def __init__(self, mqtt_client, topic, heating_function): super().__init__(mqtt_client, topic) # 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, False) # 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): 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 i in range(0, self.num_states): self.__tx__(self.KEY_STATE % i, i == data) # self.__tx_capabilities__() class videv_audio_player(base): KEY_ACTIVE_PLAYER = 'player_%d' KEY_TITLE = 'title' NO_TITLE = '---' def __init__(self, mqtt_client, topic, *args): 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.__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__ class videv_warnings(base): MAX_WARNINGS = 10 KEY_WARNING = 'html_short' def __init__(self, mqtt_client, topic, default_values=None): super().__init__(mqtt_client, topic, default_values) self.__warnings__ = [] def warningcollector(self, client, key, data): self.__warnings__.append(data) self.__warnings__ = self.__warnings__[-self.MAX_WARNINGS:] txt = "

".join([time.asctime(w[w.KEY_TM]) + "
" + w[w.KEY_TEXT] + "
" + w[w.KEY_ID] for w in self.__warnings__]) self.__tx__(self.KEY_WARNING, txt) class all_off(base): ALLOWED_CLASSES = (room, room_collection, ) def __init__(self, mqtt_client, topic, room_collection): super().__init__(mqtt_client, topic) self.__room_collection__ = room_collection # init __inst_dict__ self.__inst_dict__ = {} self.__add_instances__("all", self.__room_collection__) # register mqtt callbacks for all my keys for key in self.__inst_dict__: mqtt_client.add_callback(topic + "/" + key, self.all_off) # self.__tx_capabilities__() def __check_inst_capabilities__(self, name, inst): # fits to specified classes if isinstance(inst, self.ALLOWED_CLASSES): try: # all_off method is callable return callable(inst.all_off) except AttributeError: # all_off method does not exist return False return False def __add_instances__(self, name, inst, level=0): if self.__check_inst_capabilities__(name, inst): # add given instance to my __inst_dict__ self.__inst_dict__[name] = inst # iterate over all attribute names of instance for sub_name in dir(inst): # attribute name is not private if not sub_name.startswith("__"): sub = getattr(inst, sub_name) # recurse with this object if level == 0: self.__add_instances__(sub_name, sub, level=level+1) else: self.__add_instances__(name + "/" + sub_name, sub, level=level+1) def all_off(self, client, userdata, message): key = message.topic[len(self.topic) + 1:] self.__inst_dict__[key].all_off() self.__tx_capabilities__() @property def capabilities(self): if self.__capabilities__ is None: self.__capabilities__ = {} self.__capabilities__['__type__'] = self.__class__.__name__ for key in self.__inst_dict__: self.__capabilities__[key] = {} self.__capabilities__[key]['control'] = True return self.__capabilities__