Smarthome Functionen
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

videv.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. Virtual Device(s)
  6. Targets:
  7. * MQTT-Interface to control joined devices as one virtual device
  8. * Primary signal routing
  9. * No functionality should be implemented here
  10. """
  11. from base import mqtt_base
  12. from function.rooms import room, room_collection
  13. import json
  14. import time
  15. try:
  16. from config import APP_NAME as ROOT_LOGGER_NAME
  17. except ImportError:
  18. ROOT_LOGGER_NAME = 'root'
  19. class base(mqtt_base):
  20. KEY_INFO = '__info__'
  21. #
  22. SET_TOPIC = "set"
  23. def __init__(self, mqtt_client, topic, default_values=None):
  24. super().__init__(mqtt_client, topic, default_values=default_values)
  25. self.__display_dict__ = {}
  26. self.__control_dict__ = {}
  27. self.__capabilities__ = None
  28. def add_display(self, my_key, ext_device, ext_key, on_change_only=True):
  29. """
  30. listen to data changes of ext_device and update videv information
  31. """
  32. if my_key not in self.keys():
  33. self[my_key] = None
  34. if ext_device.__class__.__name__ == "group":
  35. # store information to identify callback from ext_device
  36. self.__display_dict__[(id(ext_device[0]), ext_key)] = my_key
  37. # register a callback to listen for data from external device
  38. ext_device[0].add_callback(ext_key, None, self.__rx_ext_device_data__, on_change_only)
  39. else:
  40. # store information to identify callback from ext_device
  41. self.__display_dict__[(id(ext_device), ext_key)] = my_key
  42. # register a callback to listen for data from external device
  43. ext_device.add_callback(ext_key, None, self.__rx_ext_device_data__, on_change_only)
  44. # send default data to videv interface
  45. def __rx_ext_device_data__(self, ext_device, ext_key, data):
  46. my_key = self.__display_dict__[(id(ext_device), ext_key)]
  47. self.set(my_key, data)
  48. self.__tx__(my_key, data)
  49. def __tx__(self, key, data):
  50. if type(data) not in (str, ):
  51. data = json.dumps(data)
  52. self.mqtt_client.send('/'.join([self.topic, key]), data)
  53. self.__tx_capabilities__()
  54. def __tx_capabilities__(self):
  55. self.mqtt_client.send(self.topic + '/' + self.KEY_INFO, json.dumps(self.capabilities))
  56. def add_control(self, my_key, ext_device, ext_key, on_change_only=True):
  57. """
  58. listen to videv information and pass data to ext_device
  59. """
  60. if my_key not in self.keys():
  61. self[my_key] = None
  62. # store information to identify callback from videv
  63. self.__control_dict__[my_key] = (ext_device, ext_key, on_change_only)
  64. # add callback for videv changes
  65. self.mqtt_client.add_callback('/'.join([self.topic, my_key, self.SET_TOPIC]), self.__rx_videv_data__)
  66. def __rx_videv_data__(self, client, userdata, message):
  67. my_key = message.topic.split('/')[-2]
  68. try:
  69. data = json.loads(message.payload)
  70. except json.decoder.JSONDecodeError:
  71. data = message.payload
  72. ext_device, ext_key, on_change_only = self.__control_dict__[my_key]
  73. if my_key in self.keys():
  74. if data != self[my_key] or not on_change_only:
  75. ext_device.send_command(ext_key, data)
  76. else:
  77. self.logger.info("Ignoring rx message with topic %s", message.topic)
  78. def add_routing(self, my_key, ext_device, ext_key, on_change_only_disp=True, on_change_only_videv=True):
  79. """
  80. listen to data changes of ext_device and update videv information
  81. and
  82. listen to videv information and pass data to ext_device
  83. """
  84. # add display
  85. self.add_display(my_key, ext_device, ext_key, on_change_only_disp)
  86. self.add_control(my_key, ext_device, ext_key, on_change_only_videv)
  87. @property
  88. def capabilities(self):
  89. if self.__capabilities__ is None:
  90. self.__capabilities__ = {}
  91. self.__capabilities__['__type__'] = self.__class__.__name__
  92. for key in self.__control_dict__:
  93. if not key in self.__capabilities__:
  94. self.__capabilities__[key] = {}
  95. self.__capabilities__[key]['control'] = True
  96. for key in self.__display_dict__.values():
  97. if not key in self.__capabilities__:
  98. self.__capabilities__[key] = {}
  99. self.__capabilities__[key]['display'] = True
  100. return self.__capabilities__
  101. class videv_switching(base):
  102. KEY_STATE = 'state'
  103. def __init__(self, mqtt_client, topic, sw_device, sw_key):
  104. super().__init__(mqtt_client, topic)
  105. self.add_routing(self.KEY_STATE, sw_device, sw_key)
  106. #
  107. self.__tx_capabilities__()
  108. class videv_switching_timer(base):
  109. KEY_STATE = 'state'
  110. KEY_TIMER = 'timer'
  111. def __init__(self, mqtt_client, topic, sw_device, sw_key, tm_device, tm_key):
  112. super().__init__(mqtt_client, topic)
  113. self.add_routing(self.KEY_STATE, sw_device, sw_key)
  114. self.add_display(self.KEY_TIMER, tm_device, tm_key)
  115. #
  116. self.__tx_capabilities__()
  117. class videv_switching_motion(base):
  118. KEY_STATE = 'state'
  119. #
  120. KEY_TIMER = 'timer'
  121. KEY_MOTION_SENSOR = 'motion_%d'
  122. def __init__(self, mqtt_client, topic, sw_device, sw_key, motion_function):
  123. self.motion_sensors = motion_function.motion_sensors
  124. #
  125. super().__init__(mqtt_client, topic)
  126. self.add_routing(self.KEY_STATE, sw_device, sw_key)
  127. self.add_display(self.KEY_TIMER, motion_function, motion_function.KEY_TIMER)
  128. # motion sensor state
  129. for index, motion_sensor in enumerate(self.motion_sensors):
  130. self.add_display(self.KEY_MOTION_SENSOR % index, motion_sensor, motion_sensor.KEY_OCCUPANCY)
  131. #
  132. self.__tx_capabilities__()
  133. class videv_switch_brightness(base):
  134. KEY_STATE = 'state'
  135. KEY_BRIGHTNESS = 'brightness'
  136. def __init__(self, mqtt_client, topic, sw_device, sw_key, br_device, br_key):
  137. super().__init__(mqtt_client, topic)
  138. self.add_routing(self.KEY_STATE, sw_device, sw_key)
  139. self.add_routing(self.KEY_BRIGHTNESS, br_device, br_key)
  140. #
  141. self.__tx_capabilities__()
  142. class videv_switch_brightness_color_temp(base):
  143. KEY_STATE = 'state'
  144. KEY_BRIGHTNESS = 'brightness'
  145. KEY_COLOR_TEMP = 'color_temp'
  146. def __init__(self, mqtt_client, topic, sw_device, sw_key, br_device, br_key, ct_device, ct_key):
  147. super().__init__(mqtt_client, topic)
  148. self.add_routing(self.KEY_STATE, sw_device, sw_key)
  149. self.add_routing(self.KEY_BRIGHTNESS, br_device, br_key)
  150. self.add_routing(self.KEY_COLOR_TEMP, ct_device, ct_key)
  151. #
  152. self.__tx_capabilities__()
  153. class videv_heating(base):
  154. KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint'
  155. KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint'
  156. KEY_AWAY_MODE = 'away_mode'
  157. KEY_SUMMER_MODE = 'summer_mode'
  158. KEY_START_BOOST = 'start_boost'
  159. KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature'
  160. KEY_BOOST_TIMER = 'boost_timer'
  161. #
  162. KEY_TEMPERATURE = 'temperature'
  163. def __init__(self, mqtt_client, topic, heating_function):
  164. super().__init__(mqtt_client, topic)
  165. #
  166. self.add_routing(self.KEY_USER_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_USER_TEMPERATURE_SETPOINT)
  167. self.add_routing(self.KEY_AWAY_MODE, heating_function, heating_function.KEY_AWAY_MODE)
  168. self.add_routing(self.KEY_SUMMER_MODE, heating_function, heating_function.KEY_SUMMER_MODE)
  169. #
  170. self.add_control(self.KEY_START_BOOST, heating_function, heating_function.KEY_START_BOOST, False)
  171. self.add_control(self.KEY_SET_DEFAULT_TEMPERATURE, heating_function, heating_function.KEY_SET_DEFAULT_TEMPERATURE, False)
  172. #
  173. self.add_display(self.KEY_VALVE_TEMPERATURE_SETPOINT, heating_function, heating_function.KEY_TEMPERATURE_SETPOINT)
  174. self.add_display(self.KEY_BOOST_TIMER, heating_function, heating_function.KEY_BOOST_TIMER)
  175. self.add_display(self.KEY_TEMPERATURE, heating_function, heating_function.KEY_TEMPERATURE_CURRENT, False)
  176. #
  177. self.__tx_capabilities__()
  178. class videv_multistate(base):
  179. KEY_STATE = 'state_%d'
  180. def __init__(self, mqtt_client, topic, key_for_device, device, num_states, default_values=None):
  181. super().__init__(mqtt_client, topic)
  182. self.num_states = num_states
  183. # send default values
  184. for i in range(0, num_states):
  185. self.__tx__(self.KEY_STATE % i, False)
  186. #
  187. device.add_callback(key_for_device, None, self.__index_rx__, True)
  188. #
  189. self.__tx_capabilities__()
  190. def __index_rx__(self, device, key, data):
  191. for i in range(0, self.num_states):
  192. self.__tx__(self.KEY_STATE % i, i == data)
  193. #
  194. self.__tx_capabilities__()
  195. class videv_audio_player(base):
  196. KEY_ACTIVE_PLAYER = 'player_%d'
  197. KEY_TITLE = 'title'
  198. NO_TITLE = '---'
  199. def __init__(self, mqtt_client, topic, *args):
  200. super().__init__(mqtt_client, topic)
  201. for i, device in enumerate(args):
  202. self.add_display(self.KEY_ACTIVE_PLAYER % i, device, device.KEY_STATE)
  203. #
  204. for audio_device in args:
  205. audio_device.add_callback(audio_device.KEY_TITLE, None, self.__title_rx__, True)
  206. #
  207. self.__tx_capabilities__()
  208. def __title_rx__(self, device, key, data):
  209. self.__tx__(self.KEY_TITLE, data or self.NO_TITLE)
  210. @property
  211. def capabilities(self):
  212. super().capabilities
  213. self.__capabilities__[self.KEY_TITLE] = {'display': True}
  214. return self.__capabilities__
  215. class videv_warnings(base):
  216. MAX_WARNINGS = 10
  217. KEY_WARNING = 'html_short'
  218. def __init__(self, mqtt_client, topic, default_values=None):
  219. super().__init__(mqtt_client, topic, default_values)
  220. self.__warnings__ = []
  221. def warningcollector(self, client, key, data):
  222. self.__warnings__.append(data)
  223. self.__warnings__ = self.__warnings__[-self.MAX_WARNINGS:]
  224. txt = "<br><br>".join([time.asctime(w[w.KEY_TM]) + "<br>" + w[w.KEY_TEXT] + "<br>" + w[w.KEY_ID] for w in self.__warnings__])
  225. self.__tx__(self.KEY_WARNING, txt)
  226. class all_off(base):
  227. ALLOWED_CLASSES = (room, room_collection, )
  228. def __init__(self, mqtt_client, topic, room_collection):
  229. super().__init__(mqtt_client, topic)
  230. self.__room_collection__ = room_collection
  231. # init __inst_dict__
  232. self.__inst_dict__ = {}
  233. self.__add_instances__("all", self.__room_collection__)
  234. # register mqtt callbacks for all my keys
  235. for key in self.__inst_dict__:
  236. mqtt_client.add_callback(topic + "/" + key, self.all_off)
  237. #
  238. self.__tx_capabilities__()
  239. def __check_inst_capabilities__(self, name, inst):
  240. # fits to specified classes
  241. if isinstance(inst, self.ALLOWED_CLASSES):
  242. try:
  243. # all_off method is callable
  244. return callable(inst.all_off)
  245. except AttributeError:
  246. # all_off method does not exist
  247. return False
  248. return False
  249. def __add_instances__(self, name, inst, level=0):
  250. if self.__check_inst_capabilities__(name, inst):
  251. # add given instance to my __inst_dict__
  252. self.__inst_dict__[name] = inst
  253. # iterate over all attribute names of instance
  254. for sub_name in dir(inst):
  255. # attribute name is not private
  256. if not sub_name.startswith("__"):
  257. sub = getattr(inst, sub_name)
  258. # recurse with this object
  259. if level == 0:
  260. self.__add_instances__(sub_name, sub, level=level+1)
  261. else:
  262. self.__add_instances__(name + "/" + sub_name, sub, level=level+1)
  263. def all_off(self, client, userdata, message):
  264. key = message.topic[len(self.topic) + 1:]
  265. self.__inst_dict__[key].all_off()
  266. self.__tx_capabilities__()
  267. @property
  268. def capabilities(self):
  269. if self.__capabilities__ is None:
  270. self.__capabilities__ = {}
  271. self.__capabilities__['__type__'] = self.__class__.__name__
  272. for key in self.__inst_dict__:
  273. self.__capabilities__[key] = {}
  274. self.__capabilities__[key]['control'] = True
  275. return self.__capabilities__