123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317 |
- #!/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.set(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 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__
|