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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. Functional Modules
  6. Targets:
  7. * Device like structure to be compatible with videv
  8. - KEY_* as part of the class for all parameters which needs to be accessed from videv
  9. - Method *.set(key, data) to pass data from videv to Module
  10. - Method .add_calback(key, data, callback, on_change_only=False) to register videv actualisation on changes
  11. """
  12. from base import common_base
  13. import config
  14. import devices
  15. from function.db import get_radiator_data, set_radiator_data
  16. from function.helpers import day_state
  17. import logging
  18. import task
  19. try:
  20. from config import APP_NAME as ROOT_LOGGER_NAME
  21. except ImportError:
  22. ROOT_LOGGER_NAME = 'root'
  23. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  24. class switched_light(object):
  25. def __init__(self, sw_device, sw_key, li_device):
  26. sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, True, li_device.request_data, True)
  27. class brightness_choose_n_action(common_base):
  28. KEY_ACTIVE_DEVICE = 'active_device'
  29. #
  30. DEFAULT_VALUES = {KEY_ACTIVE_DEVICE: None}
  31. def __init__(self, button_tradfri):
  32. super().__init__()
  33. # brightness change
  34. button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_DOWN_LONG, self.brightness_action)
  35. button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_UP_LONG, self.brightness_action)
  36. button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_DOWN_RELEASE, self.brightness_action)
  37. button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_UP_RELEASE, self.brightness_action)
  38. # device change
  39. button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_UP, self.choose_next_device)
  40. button_tradfri.add_callback(button_tradfri.KEY_ACTION, button_tradfri.ACTION_BRIGHTNESS_DOWN, self.choose_prev_device)
  41. #
  42. self.brightness_device_list = []
  43. self.callback_device_list = []
  44. self.device_states = []
  45. def add(self, brightness_device, callback_device, callback_key):
  46. """
  47. brightness_device: A device for brightness function needs to have the following methods:
  48. * .default_inc()
  49. * .default_dec()
  50. * .default_stop()
  51. callback_device: A device for installing callback which are executed, when the device is switched on or off. It needs the following method:
  52. * .add_callback(key, data or None, callback, on_changes_only)
  53. """
  54. self.brightness_device_list.append(brightness_device)
  55. self.callback_device_list.append((callback_device, callback_key))
  56. self.device_states.append(False)
  57. callback_device.add_callback(callback_key, True, self.device_state_action, True)
  58. callback_device.add_callback(callback_key, False, self.device_state_action, True)
  59. def device_state_action(self, device, key, data):
  60. self.device_states[self.callback_device_list.index((device, key))] = data
  61. if data is True:
  62. self.set(self.KEY_ACTIVE_DEVICE, self.callback_device_list.index((device, key)))
  63. else:
  64. if self[self.KEY_ACTIVE_DEVICE] is not None:
  65. if self.callback_device_list[self[self.KEY_ACTIVE_DEVICE]][0] == device:
  66. self.choose_next_device()
  67. def choose_prev_device(self, device=None, key=None, data=None):
  68. if self[self.KEY_ACTIVE_DEVICE] is not None:
  69. start_value = self[self.KEY_ACTIVE_DEVICE]
  70. for i in range(0, len(self.brightness_device_list)):
  71. target_state = (start_value - i - 1) % (len(self.brightness_device_list))
  72. if self.device_states[target_state]:
  73. self.set(self.KEY_ACTIVE_DEVICE, target_state)
  74. return
  75. self.set(self.KEY_ACTIVE_DEVICE, None)
  76. def choose_next_device(self, device=None, key=None, data=None):
  77. if self[self.KEY_ACTIVE_DEVICE] is not None:
  78. start_value = self[self.KEY_ACTIVE_DEVICE]
  79. for i in range(0, len(self.brightness_device_list)):
  80. target_state = (start_value + i + 1) % (len(self.brightness_device_list))
  81. if self.device_states[target_state]:
  82. self.set(self.KEY_ACTIVE_DEVICE, target_state)
  83. return
  84. self.set(self.KEY_ACTIVE_DEVICE, None)
  85. def brightness_action(self, device, key, data):
  86. if self[self.KEY_ACTIVE_DEVICE] is not None:
  87. target = self.brightness_device_list[self[self.KEY_ACTIVE_DEVICE]]
  88. if data == devices.tradfri_button.ACTION_BRIGHTNESS_UP_LONG:
  89. logger.info("Increasing \"%s\" - %s", type(self).__name__, target.topic)
  90. target.default_inc()
  91. elif data == devices.tradfri_button.ACTION_BRIGHTNESS_DOWN_LONG:
  92. logger.info("Decreasing \"%s\" - %s", type(self).__name__, target.topic)
  93. target.default_dec()
  94. elif data in [devices.tradfri_button.ACTION_BRIGHTNESS_UP_RELEASE, devices.tradfri_button.ACTION_BRIGHTNESS_DOWN_RELEASE]:
  95. target.default_stop()
  96. class timer_on_activation(common_base):
  97. KEY_TIMER = 'timer'
  98. #
  99. DEFAULT_VALUES = {
  100. KEY_TIMER: 0
  101. }
  102. def __init__(self, sw_device, sw_key, timer_reload_value):
  103. super().__init__()
  104. #
  105. self.timer_reload_value = timer_reload_value
  106. #
  107. sw_device.add_callback(sw_key, None, self.circ_pump_actions, True)
  108. #
  109. self.ct = task.periodic(6, self.cyclic_task)
  110. self.ct.run()
  111. def circ_pump_actions(self, device, key, data):
  112. if data is True:
  113. self.set(self.KEY_TIMER, self.timer_reload_value)
  114. else:
  115. self.set(self.KEY_TIMER, 0)
  116. def cyclic_task(self, rt):
  117. timer_value = self[self.KEY_TIMER] - self.ct.cycle_time
  118. if timer_value <= 0:
  119. self.set(self.KEY_TIMER, 0)
  120. else:
  121. self.set(self.KEY_TIMER, timer_value)
  122. class heating_function(common_base):
  123. KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint'
  124. KEY_TEMPERATURE_SETPOINT = 'temperature_setpoint'
  125. KEY_TEMPERATURE_CURRENT = 'temperature_current'
  126. KEY_AWAY_MODE = 'away_mode'
  127. KEY_SUMMER_MODE = 'summer_mode'
  128. KEY_START_BOOST = 'start_boost'
  129. KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature'
  130. KEY_BOOST_TIMER = 'boost_timer'
  131. #
  132. BOOST_TEMPERATURE = 30
  133. AWAY_REDUCTION = 5
  134. SUMMER_TEMPERATURE = 5
  135. def __init__(self, heating_valve):
  136. self.heating_valve = heating_valve
  137. self.default_temperature = config.DEFAULT_TEMPERATURE[heating_valve.topic]
  138. db_data = get_radiator_data(heating_valve.topic)
  139. super().__init__({
  140. self.KEY_USER_TEMPERATURE_SETPOINT: db_data[2] or self.default_temperature,
  141. self.KEY_TEMPERATURE_SETPOINT: db_data[3] or self.default_temperature,
  142. self.KEY_TEMPERATURE_CURRENT: None,
  143. self.KEY_AWAY_MODE: db_data[0] or False,
  144. self.KEY_SUMMER_MODE: db_data[1] or False,
  145. self.KEY_START_BOOST: True,
  146. self.KEY_SET_DEFAULT_TEMPERATURE: False,
  147. self.KEY_BOOST_TIMER: 0
  148. })
  149. #
  150. self.heating_valve.set_heating_setpoint(self[self.KEY_TEMPERATURE_SETPOINT])
  151. #
  152. self.heating_valve.add_callback(self.heating_valve.KEY_HEATING_SETPOINT, None, self.get_radiator_setpoint)
  153. self.heating_valve.add_callback(self.heating_valve.KEY_TEMPERATURE, None, self.get_radiator_temperature)
  154. #
  155. self.add_callback(self.KEY_USER_TEMPERATURE_SETPOINT, None, self.user_temperature_setpoint, False)
  156. self.add_callback(self.KEY_TEMPERATURE_SETPOINT, None, self.set_heating_setpoint, True)
  157. self.add_callback(self.KEY_AWAY_MODE, None, self.away_mode, True)
  158. self.add_callback(self.KEY_SUMMER_MODE, None, self.summer_mode, True)
  159. self.add_callback(self.KEY_SET_DEFAULT_TEMPERATURE, None, self.setpoint_to_default)
  160. self.add_callback(self.KEY_START_BOOST, True, self.boost, False)
  161. self.add_callback(self.KEY_BOOST_TIMER, 0, self.timer_expired, True)
  162. # cyclic task initialisation
  163. self.ct = task.periodic(1, self.cyclic_task)
  164. self.ct.run()
  165. def timer_expired(self, device, data, key):
  166. self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT])
  167. self.heating_valve.logger.info('Timer expired. returning to regular temperature setpoint %.1f°C.',
  168. self[self.KEY_TEMPERATURE_SETPOINT])
  169. def cyclic_task(self, rt):
  170. timer_value = self[self.KEY_BOOST_TIMER] - self.ct.cycle_time
  171. if self[self.KEY_BOOST_TIMER] <= 0:
  172. self.set(self.KEY_BOOST_TIMER, 0)
  173. else:
  174. self.set(self.KEY_BOOST_TIMER, timer_value)
  175. def cancel_boost(self):
  176. self.set(self.KEY_BOOST_TIMER, 0, block_callback=[self.timer_expired])
  177. def send_command(self, key, data, block_callback=[]):
  178. rv = super().set(key, data, block_callback)
  179. set_radiator_data(self.heating_valve.topic, self[self.KEY_AWAY_MODE], self[self.KEY_SUMMER_MODE],
  180. self[self.KEY_USER_TEMPERATURE_SETPOINT], self[self.KEY_TEMPERATURE_SETPOINT])
  181. return rv
  182. def away_mode(self, device, key, value):
  183. if value is True:
  184. self.cancel_boost()
  185. self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode])
  186. self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT] - self.AWAY_REDUCTION)
  187. else:
  188. self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT])
  189. def summer_mode(self, device, key, value):
  190. if value is True:
  191. self.cancel_boost()
  192. self.set(self.KEY_AWAY_MODE, False, [self.away_mode])
  193. self.set(self.KEY_TEMPERATURE_SETPOINT, self.SUMMER_TEMPERATURE)
  194. else:
  195. self.set(self.KEY_TEMPERATURE_SETPOINT, self[self.KEY_USER_TEMPERATURE_SETPOINT])
  196. def boost(self, device, key, data):
  197. if self[self.KEY_BOOST_TIMER] == 0:
  198. self.heating_valve.logger.info('Starting boost mode with setpoint %.1f°C.', self.BOOST_TEMPERATURE)
  199. self.set(self.KEY_BOOST_TIMER, 15*60)
  200. self.set(self.KEY_TEMPERATURE_SETPOINT, self.BOOST_TEMPERATURE)
  201. else:
  202. self.set(self.KEY_BOOST_TIMER, min(self[self.KEY_BOOST_TIMER] + 15 * 60, 60 * 60))
  203. self.set(self.KEY_AWAY_MODE, False, [self.away_mode])
  204. self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode])
  205. def setpoint_to_default(self, device, key, data):
  206. self.cancel_boost()
  207. self.set(self.KEY_AWAY_MODE, False, [self.away_mode])
  208. self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode])
  209. self.set(self.KEY_USER_TEMPERATURE_SETPOINT, self.default_temperature, [self.user_temperature_setpoint])
  210. self.set(self.KEY_TEMPERATURE_SETPOINT, self.default_temperature)
  211. def user_temperature_setpoint(self, device, key, data):
  212. self.cancel_boost()
  213. self.set(self.KEY_AWAY_MODE, False, [self.away_mode])
  214. self.set(self.KEY_SUMMER_MODE, False, [self.summer_mode])
  215. self.set(self.KEY_TEMPERATURE_SETPOINT, data)
  216. def set_heating_setpoint(self, device, key, data):
  217. self.heating_valve.set_heating_setpoint(data)
  218. def get_radiator_setpoint(self, device, key, data):
  219. if self[self.KEY_BOOST_TIMER] == 0 and not self[self.KEY_AWAY_MODE] and not self[self.KEY_SUMMER_MODE]:
  220. self.set(self.KEY_USER_TEMPERATURE_SETPOINT, data, block_callback=[self.set_heating_setpoint])
  221. def get_radiator_temperature(self, device, key, data):
  222. self.set(self.KEY_TEMPERATURE_CURRENT, data)
  223. class motion_sensor_light(common_base):
  224. KEY_TIMER = 'timer'
  225. KEY_MOTION_SENSOR = 'motion_%d'
  226. KEY_MOTION_SENSOR_0 = 'motion_%d' % 0
  227. KEY_MOTION_SENSOR_1 = 'motion_%d' % 1
  228. KEY_MOTION_SENSOR_2 = 'motion_%d' % 2
  229. KEY_MOTION_SENSOR_3 = 'motion_%d' % 3
  230. KEY_MOTION_SENSOR_4 = 'motion_%d' % 4
  231. def __init__(self, sw_device, sw_method, *args, timer_value=30):
  232. """
  233. sw_device is the device switching the light, args are 0-n motion sensors
  234. """
  235. dv = dict.fromkeys([self.KEY_MOTION_SENSOR % i for i in range(0, len(args))])
  236. for key in dv:
  237. dv[key] = False
  238. dv[self.KEY_TIMER] = 0
  239. super().__init__(default_values=dv)
  240. #
  241. self.sw_device = sw_device
  242. self.sw_method = sw_method
  243. self.motion_sensors = args
  244. self.timer_reload_value = timer_value
  245. #
  246. sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, True, self.reload_timer, True)
  247. sw_device.add_callback(devices.shelly.KEY_OUTPUT_0, False, self.reset_timer, True)
  248. for motion_sensor in args:
  249. motion_sensor.add_callback(motion_sensor.KEY_OCCUPANCY, None, self.set_motion_detected, True)
  250. #
  251. self.add_callback(self.KEY_TIMER, 0, self.timer_expired, True)
  252. #
  253. cyclic_task = task.periodic(1, self.cyclic_task)
  254. cyclic_task.run()
  255. def reload_timer(self, device, key, data):
  256. self.set(self.KEY_TIMER, self.timer_reload_value)
  257. def reset_timer(self, device=None, key=None, data=None):
  258. self.set(self.KEY_TIMER, 0)
  259. def set_motion_detected(self, device, key, data):
  260. for sensor_index, arg_device in enumerate(self.motion_sensors):
  261. if arg_device.topic == device.topic:
  262. break
  263. self.set(self.KEY_MOTION_SENSOR % sensor_index, data)
  264. # auto light on with state sunset -> time_offset_sunrise=60 (longer sunset) and time_offset_sunset=-60 (longer sunset)
  265. if day_state(None, None, 60, -60).get_state() == day_state.KEY_SUNSET:
  266. if data is True:
  267. logger.info("%s: Motion detected - Switching on main light %s", device.topic, self.sw_device.topic)
  268. self.sw_method(True)
  269. def motion_detected(self):
  270. for i in range(0, len(self.motion_sensors)):
  271. if self[self.KEY_MOTION_SENSOR % i]:
  272. return True
  273. return False
  274. def timer_expired(self, device, key, data):
  275. logger.info("No motion and time ran out - Switching off main light %s", self.sw_device.topic)
  276. self.sw_method(False)
  277. def cyclic_task(self, cyclic_task):
  278. min_value = 10 if self.motion_detected() else 0
  279. if self[self.KEY_TIMER] != 0:
  280. self.set(self.KEY_TIMER, max(min_value, self[self.KEY_TIMER] - cyclic_task.cycle_time))