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.

__init__.py 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. devices (DEVICES)
  6. =================
  7. **Author:**
  8. * Dirk Alders <sudo-dirk@mount-mockery.de>
  9. **Description:**
  10. This Module supports smarthome devices
  11. **Submodules:**
  12. * :mod:`shelly`
  13. * :mod:`silvercrest_powerplug`
  14. **Unittest:**
  15. See also the :download:`unittest <devices/_testresults_/unittest.pdf>` documentation.
  16. **Module Documentation:**
  17. """
  18. # TODO: Usage of mqtt_base for all devices
  19. __DEPENDENCIES__ = []
  20. import json
  21. import logging
  22. try:
  23. from config import APP_NAME as ROOT_LOGGER_NAME
  24. except ImportError:
  25. ROOT_LOGGER_NAME = 'root'
  26. BATTERY_WARN_LEVEL = 5
  27. def is_json(data):
  28. try:
  29. json.loads(data)
  30. except json.decoder.JSONDecodeError:
  31. return False
  32. else:
  33. return True
  34. class base(dict):
  35. TX_TOPIC = "set"
  36. TX_VALUE = 0
  37. TX_DICT = 1
  38. TX_TYPE = -1
  39. TX_FILTER_DATA_KEYS = []
  40. #
  41. RX_KEYS = []
  42. RX_IGNORE_TOPICS = []
  43. RX_IGNORE_KEYS = []
  44. RX_FILTER_DATA_KEYS = []
  45. def __init__(self, mqtt_client, topic):
  46. # data storage
  47. self.mqtt_client = mqtt_client
  48. self.topic = topic
  49. self.logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  50. for entry in self.topic.split('/'):
  51. self.logger = self.logger.getChild(entry)
  52. # initialisations
  53. dict.__init__(self)
  54. mqtt_client.add_callback(
  55. topic=self.topic, callback=self.receive_callback)
  56. mqtt_client.add_callback(
  57. topic=self.topic+"/#", callback=self.receive_callback)
  58. #
  59. self.callback_list = []
  60. self.warning_callback = None
  61. #
  62. self.__previous__ = {}
  63. def receive_callback(self, client, userdata, message):
  64. self.unpack(message)
  65. def unpack_filter(self, key):
  66. if key in self.RX_FILTER_DATA_KEYS:
  67. if self.get(key) == 1 or self.get(key) == 'on' or self.get(key) == 'ON':
  68. self[key] = True
  69. elif self.get(key) == 0 or self.get(key) == 'off' or self.get(key) == 'OFF':
  70. self[key] = False
  71. def unpack_single_value(self, key, data):
  72. prev_value = self.get(key)
  73. if key in self.RX_KEYS:
  74. self[key] = data
  75. self.__previous__[key] = prev_value
  76. # Filter, if needed
  77. self.unpack_filter(key)
  78. self.logger.debug("Received data %s - %s", key, str(self.get(key)))
  79. self.callback_caller(key, self[key], self.get(key) != self.__previous__.get(key))
  80. elif key not in self.RX_IGNORE_KEYS:
  81. self.logger.warning('Got a message with unparsed content: "%s - %s"', key, str(data))
  82. else:
  83. self.logger.debug("Ignoring key %s", key)
  84. def unpack(self, message):
  85. content_key = message.topic[len(self.topic) + 1:]
  86. if content_key not in self.RX_IGNORE_TOPICS and (not message.topic.endswith(self.TX_TOPIC) or len(self.TX_TOPIC) == 0):
  87. self.logger.debug("Unpacking content_key \"%s\" from message.", content_key)
  88. if is_json(message.payload):
  89. data = json.loads(message.payload)
  90. if type(data) is dict:
  91. for key in data:
  92. self.unpack_single_value(key, data[key])
  93. else:
  94. self.unpack_single_value(content_key, data)
  95. # String
  96. else:
  97. self.unpack_single_value(
  98. content_key, message.payload.decode('utf-8'))
  99. self.warning_caller()
  100. else:
  101. self.logger.debug("Ignoring topic %s", content_key)
  102. def pack_filter(self, key, data):
  103. if key in self.TX_FILTER_DATA_KEYS:
  104. if data is True:
  105. return "on"
  106. elif data is False:
  107. return "off"
  108. else:
  109. return data
  110. return data
  111. def set(self, key, data):
  112. self.pack(key, data)
  113. def pack(self, key, data):
  114. data = self.pack_filter(key, data)
  115. if self.TX_TOPIC is not None:
  116. if self.TX_TYPE < 0:
  117. self.logger.error("Unknown tx type. Set TX_TYPE of class to a known value")
  118. else:
  119. self.logger.debug("Sending data for %s - %s", key, str(data))
  120. if self.TX_TYPE == self.TX_DICT:
  121. self.mqtt_client.send('/'.join([self.topic, self.TX_TOPIC]), json.dumps({key: data}))
  122. else:
  123. if type(data) not in [str, bytes]:
  124. data = json.dumps(data)
  125. self.mqtt_client.send('/'.join([self.topic, key, self.TX_TOPIC] if len(self.TX_TOPIC) > 0 else [self.topic, key]), data)
  126. else:
  127. self.logger.error("Unknown tx toptic. Set TX_TOPIC of class to a known value")
  128. def add_callback(self, key, data, callback, on_change_only=False):
  129. """
  130. key: key or None for all keys
  131. data: data or None for all data
  132. """
  133. cb_tup = (key, data, callback, on_change_only)
  134. if cb_tup not in self.callback_list:
  135. self.callback_list.append(cb_tup)
  136. def add_warning_callback(self, callback):
  137. self.warning_callback = callback
  138. def warning_call_condition(self):
  139. return False
  140. def callback_caller(self, key, data, value_changed):
  141. for cb_key, cb_data, callback, on_change_only in self.callback_list:
  142. if (cb_key == key or cb_key is None) and (cb_data == data or cb_data is None) and callback is not None:
  143. if not on_change_only or value_changed:
  144. callback(self, key, data)
  145. def warning_caller(self):
  146. if self.warning_call_condition():
  147. warn_txt = self.warning_text()
  148. self.logger.warning(warn_txt)
  149. if self.warning_callback is not None:
  150. self.warning_callback(self, warn_txt)
  151. def warning_text(self):
  152. return "default warning text - replace parent warning_text function"
  153. def previous_value(self, key):
  154. return self.__previous__.get(key)
  155. class shelly(base):
  156. KEY_OUTPUT_0 = "relay/0"
  157. KEY_OUTPUT_1 = "relay/1"
  158. KEY_INPUT_0 = "input/0"
  159. KEY_INPUT_1 = "input/1"
  160. KEY_LONGPUSH_0 = "longpush/0"
  161. KEY_LONGPUSH_1 = "longpush/1"
  162. KEY_TEMPERATURE = "temperature"
  163. KEY_OVERTEMPERATURE = "overtemperature"
  164. KEY_ID = "id"
  165. KEY_MODEL = "model"
  166. KEY_MAC = "mac"
  167. KEY_IP = "ip"
  168. KEY_NEW_FIRMWARE = "new_fw"
  169. KEY_FIRMWARE_VERSION = "fw_ver"
  170. #
  171. TX_TOPIC = "command"
  172. TX_TYPE = base.TX_VALUE
  173. TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1]
  174. #
  175. RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OVERTEMPERATURE, KEY_TEMPERATURE,
  176. KEY_ID, KEY_MODEL, KEY_MAC, KEY_IP, KEY_NEW_FIRMWARE, KEY_FIRMWARE_VERSION]
  177. RX_IGNORE_TOPICS = [KEY_OUTPUT_0 + '/' + "energy", KEY_OUTPUT_1 + '/' + "energy", 'input_event/0', 'input_event/1']
  178. RX_IGNORE_KEYS = ['temperature_f']
  179. RX_FILTER_DATA_KEYS = [KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OVERTEMPERATURE]
  180. def __init__(self, mqtt_client, topic):
  181. super().__init__(mqtt_client, topic)
  182. #
  183. # WARNING CALL
  184. #
  185. def warning_call_condition(self):
  186. return self.get(self.KEY_OVERTEMPERATURE)
  187. def warning_text(self):
  188. if self.overtemperature:
  189. if self.temperature is not None:
  190. return "Overtemperature detected for %s. Temperature was %.1f°C." % (self.topic, self.temperature)
  191. else:
  192. return "Overtemperature detected for %s." % self.topic
  193. #
  194. # RX
  195. #
  196. @property
  197. def output_0(self):
  198. """rv: [True, False]"""
  199. return self.get(self.KEY_OUTPUT_0)
  200. @property
  201. def output_1(self):
  202. """rv: [True, False]"""
  203. return self.get(self.KEY_OUTPUT_1)
  204. @property
  205. def input_0(self):
  206. """rv: [True, False]"""
  207. return self.get(self.KEY_INPUT_0)
  208. @property
  209. def input_1(self):
  210. """rv: [True, False]"""
  211. return self.get(self.KEY_INPUT_1)
  212. @property
  213. def longpush_0(self):
  214. """rv: [True, False]"""
  215. return self.get(self.KEY_LONGPUSH_0)
  216. @property
  217. def longpush_1(self):
  218. """rv: [True, False]"""
  219. return self.get(self.KEY_LONGPUSH_1)
  220. @property
  221. def temperature(self):
  222. """rv: numeric value"""
  223. return self.get(self.KEY_TEMPERATURE)
  224. #
  225. # TX
  226. #
  227. def set_output_0(self, state):
  228. """state: [True, False, 'toggle']"""
  229. self.pack(self.KEY_OUTPUT_0, state)
  230. def set_output_0_mcb(self, device, key, data):
  231. self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
  232. self.set_output_0(data)
  233. def toggle_output_0_mcb(self, device, key, data):
  234. self.logger.info("Toggeling output 0")
  235. self.set_output_0('toggle')
  236. def set_output_1(self, state):
  237. """state: [True, False, 'toggle']"""
  238. self.pack(self.KEY_OUTPUT_1, state)
  239. def set_output_1_mcb(self, device, key, data):
  240. self.logger.log(logging.INFO if data != self.output_1 else logging.DEBUG, "Changing output 1 to %s", str(data))
  241. self.set_output_1(data)
  242. def toggle_output_1_mcb(self, device, key, data):
  243. self.logger.info("Toggeling output 1")
  244. self.set_output_1('toggle')
  245. class silvercrest_powerplug(base):
  246. KEY_LINKQUALITY = "linkquality"
  247. KEY_OUTPUT_0 = "state"
  248. #
  249. TX_TYPE = base.TX_DICT
  250. TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0]
  251. #
  252. RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0]
  253. RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0]
  254. def __init__(self, mqtt_client, topic):
  255. super().__init__(mqtt_client, topic)
  256. #
  257. # RX
  258. #
  259. @property
  260. def output_0(self):
  261. """rv: [True, False]"""
  262. return self.get(self.KEY_OUTPUT_0)
  263. @property
  264. def linkquality(self):
  265. """rv: numeric value"""
  266. return self.get(self.KEY_LINKQUALITY)
  267. #
  268. # TX
  269. #
  270. def set_output_0(self, state):
  271. """state: [True, False, 'toggle']"""
  272. self.pack(self.KEY_OUTPUT_0, state)
  273. def set_output_0_mcb(self, device, key, data):
  274. self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
  275. self.set_output_0(data)
  276. def toggle_output_0_mcb(self, device, key, data):
  277. self.logger.info("Toggeling output 0")
  278. self.set_output_0('toggle')
  279. class silvercrest_motion_sensor(base):
  280. KEY_BATTERY = "battery"
  281. KEY_BATTERY_LOW = "battery_low"
  282. KEY_LINKQUALITY = "linkquality"
  283. KEY_OCCUPANCY = "occupancy"
  284. KEY_UNMOUNTED = "tamper"
  285. KEY_VOLTAGE = "voltage"
  286. #
  287. RX_KEYS = [KEY_BATTERY, KEY_BATTERY_LOW, KEY_LINKQUALITY, KEY_OCCUPANCY, KEY_UNMOUNTED, KEY_VOLTAGE]
  288. def __init__(self, mqtt_client, topic):
  289. super().__init__(mqtt_client, topic)
  290. def warning_call_condition(self):
  291. return self.get(self.KEY_BATTERY_LOW)
  292. def warning_text(self, data):
  293. return "Battery low: level=%d" % self.get(self.KEY_BATTERY)
  294. #
  295. # RX
  296. #
  297. @property
  298. def linkquality(self):
  299. """rv: numeric value"""
  300. return self.get(self.KEY_LINKQUALITY)
  301. class my_powerplug(base):
  302. KEY_OUTPUT_0 = "output/1"
  303. KEY_OUTPUT_1 = "output/2"
  304. KEY_OUTPUT_2 = "output/3"
  305. KEY_OUTPUT_3 = "output/4"
  306. KEY_OUTPUT_ALL = "output/all"
  307. KEY_OUTPUT_LIST = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3]
  308. #
  309. TX_TYPE = base.TX_VALUE
  310. #
  311. RX_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_OUTPUT_2, KEY_OUTPUT_3]
  312. def __init__(self, mqtt_client, topic):
  313. super().__init__(mqtt_client, topic)
  314. #
  315. # RX
  316. #
  317. @property
  318. def output_0(self):
  319. """rv: [True, False]"""
  320. return self.get(self.KEY_OUTPUT_0)
  321. @property
  322. def output_1(self):
  323. """rv: [True, False]"""
  324. return self.get(self.KEY_OUTPUT_1)
  325. @property
  326. def output_2(self):
  327. """rv: [True, False]"""
  328. return self.get(self.KEY_OUTPUT_2)
  329. @property
  330. def output_3(self):
  331. """rv: [True, False]"""
  332. return self.get(self.KEY_OUTPUT_3)
  333. #
  334. # TX
  335. #
  336. def set_output(self, key, state):
  337. if key in self.KEY_OUTPUT_LIST:
  338. self.pack(key, state)
  339. else:
  340. logging.error("Unknown key to set the output!")
  341. def set_output_0(self, state):
  342. """state: [True, False, 'toggle']"""
  343. self.pack(self.KEY_OUTPUT_0, state)
  344. def set_output_0_mcb(self, device, key, data):
  345. self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
  346. self.set_output_0(data)
  347. def toggle_output_0_mcb(self, device, key, data):
  348. self.logger.info("Toggeling output 0")
  349. self.set_output_0('toggle')
  350. def set_output_1(self, state):
  351. """state: [True, False, 'toggle']"""
  352. self.pack(self.KEY_OUTPUT_1, state)
  353. def set_output_1_mcb(self, device, key, data):
  354. self.logger.log(logging.INFO if data != self.output_1 else logging.DEBUG, "Changing output 1 to %s", str(data))
  355. self.set_output_1(data)
  356. def toggle_output_1_mcb(self, device, key, data):
  357. self.logger.info("Toggeling output 1")
  358. self.set_output_1('toggle')
  359. def set_output_2(self, state):
  360. """state: [True, False, 'toggle']"""
  361. self.pack(self.KEY_OUTPUT_2, state)
  362. def set_output_2_mcb(self, device, key, data):
  363. self.logger.log(logging.INFO if data != self.output_2 else logging.DEBUG, "Changing output 2 to %s", str(data))
  364. self.set_output_2(data)
  365. def toggle_output_2_mcb(self, device, key, data):
  366. self.logger.info("Toggeling output 2")
  367. self.set_output_2('toggle')
  368. def set_output_3(self, state):
  369. """state: [True, False, 'toggle']"""
  370. self.pack(self.KEY_OUTPUT_3, state)
  371. def set_output_3_mcb(self, device, key, data):
  372. self.logger.log(logging.INFO if data != self.output_3 else logging.DEBUG, "Changing output 3 to %s", str(data))
  373. self.set_output_3(data)
  374. def toggle_output_3_mcb(self, device, key, data):
  375. self.logger.info("Toggeling output 3")
  376. self.set_output_3('toggle')
  377. def set_output_all(self, state):
  378. """state: [True, False, 'toggle']"""
  379. self.pack(self.KEY_OUTPUT_ALL, state)
  380. def set_output_all_mcb(self, device, key, data):
  381. self.logger.info("Changing all outputs to %s", str(data))
  382. self.set_output_all(data)
  383. def toggle_output_all_mcb(self, device, key, data):
  384. self.logger.info("Toggeling all outputs")
  385. self.set_output_0('toggle')
  386. class tradfri_light(base):
  387. KEY_LINKQUALITY = "linkquality"
  388. KEY_OUTPUT_0 = "state"
  389. KEY_BRIGHTNESS = "brightness"
  390. KEY_COLOR_TEMP = "color_temp"
  391. KEY_BRIGHTNESS_FADE = "brightness_move"
  392. #
  393. TX_TYPE = base.TX_DICT
  394. TX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP, KEY_BRIGHTNESS_FADE]
  395. #
  396. RX_KEYS = [KEY_LINKQUALITY, KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
  397. RX_IGNORE_KEYS = ['update', 'color_mode', 'color_temp_startup']
  398. RX_FILTER_DATA_KEYS = [KEY_OUTPUT_0, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
  399. def __init__(self, mqtt_client, topic):
  400. super().__init__(mqtt_client, topic)
  401. def unpack_filter(self, key):
  402. if key == self.KEY_BRIGHTNESS:
  403. self[key] = (self[key] - 1) * 100 / 254
  404. elif key == self.KEY_COLOR_TEMP:
  405. self[key] = (self[key] - 250) * 10 / 204
  406. else:
  407. super().unpack_filter(key)
  408. def pack_filter(self, key, data):
  409. if key == self.KEY_BRIGHTNESS:
  410. return data * 254 / 100 + 1
  411. elif key == self.KEY_COLOR_TEMP:
  412. return data * 204 / 10 + 250
  413. else:
  414. return super().pack_filter(key, data)
  415. def request_data(self):
  416. self.mqtt_client.send(self.topic + "/get", '{%s: ""}' % self.KEY_OUTPUT_0)
  417. #
  418. # RX
  419. #
  420. @property
  421. def output_0(self):
  422. """rv: [True, False]"""
  423. return self.get(self.KEY_OUTPUT_0, False)
  424. @property
  425. def linkquality(self):
  426. """rv: numeric value"""
  427. return self.get(self.KEY_LINKQUALITY, 0)
  428. @property
  429. def brightness(self):
  430. """rv: numeric value [0%, ..., 100%]"""
  431. return self.get(self.KEY_BRIGHTNESS, 0)
  432. @property
  433. def color_temp(self):
  434. """rv: numeric value [0, ..., 10]"""
  435. return self.get(self.KEY_COLOR_TEMP, 0)
  436. #
  437. # TX
  438. #
  439. def set_output_0(self, state):
  440. """state: [True, False, 'toggle']"""
  441. self.pack(self.KEY_OUTPUT_0, state)
  442. def set_output_0_mcb(self, device, key, data):
  443. self.logger.log(logging.INFO if data != self.output_0 else logging.DEBUG, "Changing output 0 to %s", str(data))
  444. self.set_output_0(data)
  445. def toggle_output_0_mcb(self, device, key, data):
  446. self.logger.info("Toggeling output 0")
  447. self.set_output_0('toggle')
  448. def set_brightness(self, brightness):
  449. """brightness: [0, ..., 100]"""
  450. self.pack(self.KEY_BRIGHTNESS, brightness)
  451. def set_brightness_mcb(self, device, key, data):
  452. self.logger.log(logging.INFO if data != self.brightness else logging.DEBUG, "Changing brightness to %s", str(data))
  453. self.set_brightness(data)
  454. def default_inc(self, speed=40):
  455. self.pack(self.KEY_BRIGHTNESS_FADE, speed)
  456. def default_dec(self, speed=-40):
  457. self.default_inc(speed)
  458. def default_stop(self):
  459. self.default_inc(0)
  460. def set_color_temp(self, color_temp):
  461. """color_temp: [0, ..., 10]"""
  462. self.pack(self.KEY_COLOR_TEMP, color_temp)
  463. def set_color_temp_mcb(self, device, key, data):
  464. self.logger.log(logging.INFO if data != self.color_temp else logging.DEBUG, "Changing color temperature to %s", str(data))
  465. self.set_color_temp(data)
  466. class tradfri_button(base):
  467. ACTION_TOGGLE = "toggle"
  468. ACTION_BRIGHTNESS_UP = "brightness_up_click"
  469. ACTION_BRIGHTNESS_DOWN = "brightness_down_click"
  470. ACTION_RIGHT = "arrow_right_click"
  471. ACTION_LEFT = "arrow_left_click"
  472. ACTION_BRIGHTNESS_UP_LONG = "brightness_up_hold"
  473. ACTION_BRIGHTNESS_UP_RELEASE = "brightness_up_release"
  474. ACTION_BRIGHTNESS_DOWN_LONG = "brightness_down_hold"
  475. ACTION_BRIGHTNESS_DOWN_RELEASE = "brightness_down_release"
  476. ACTION_RIGHT_LONG = "arrow_right_hold"
  477. ACTION_RIGHT_RELEASE = "arrow_right_release"
  478. ACTION_LEFT_LONG = "arrow_left_hold"
  479. ACTION_LEFT_RELEASE = "arrow_left_release"
  480. #
  481. KEY_LINKQUALITY = "linkquality"
  482. KEY_BATTERY = "battery"
  483. KEY_ACTION = "action"
  484. KEY_ACTION_DURATION = "action_duration"
  485. #
  486. RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION]
  487. RX_IGNORE_KEYS = ['update', KEY_ACTION_DURATION]
  488. def __init__(self, mqtt_client, topic):
  489. super().__init__(mqtt_client, topic)
  490. #
  491. # RX
  492. #
  493. @property
  494. def action(self):
  495. """rv: action_txt"""
  496. return self.get(self.KEY_ACTION)
  497. #
  498. # WARNING CALL
  499. #
  500. def warning_call_condition(self):
  501. return self.get(self.KEY_BATTERY) is not None and self.get(self.KEY_BATTERY) <= BATTERY_WARN_LEVEL
  502. def warning_text(self):
  503. return "Low battery level detected for %s. Battery level was %.0f%%." % (self.topic, self.get(self.KEY_BATTERY))
  504. class nodered_gui_leds(base):
  505. KEY_LED_0 = "led0"
  506. KEY_LED_1 = "led1"
  507. KEY_LED_2 = "led2"
  508. KEY_LED_3 = "led3"
  509. KEY_LED_4 = "led4"
  510. KEY_LED_5 = "led5"
  511. KEY_LED_6 = "led6"
  512. KEY_LED_7 = "led7"
  513. KEY_LED_8 = "led8"
  514. KEY_LED_9 = "led9"
  515. KEY_LED_LIST = [KEY_LED_0, KEY_LED_1, KEY_LED_2, KEY_LED_3, KEY_LED_4, KEY_LED_5, KEY_LED_6, KEY_LED_7, KEY_LED_8, KEY_LED_9]
  516. #
  517. TX_TYPE = base.TX_VALUE
  518. def set_led(self, key, data):
  519. """data: [True, False]"""
  520. self.logger.debug("Sending %s with content %s", key, str(data))
  521. self.pack(key, data)
  522. class nodered_gui_timer(base):
  523. KEY_TIMER = "timer"
  524. #
  525. TX_TYPE = base.TX_VALUE
  526. def set_timer(self, data):
  527. """data: numeric"""
  528. self.pack(self.KEY_TIMER, data)
  529. def set_timer_mcb(self, device, key, data):
  530. self.logger.debug("Sending %s with content %s", key, str(data))
  531. self.set_timer(data)
  532. class nodered_gui_button(base):
  533. KEY_STATE = "state"
  534. #
  535. RX_KEYS = [KEY_STATE]
  536. #
  537. # RX
  538. #
  539. @property
  540. def state(self):
  541. """rv: [True, False]"""
  542. return self.get(self.KEY_STATE)
  543. class nodered_gui_switch(nodered_gui_button):
  544. TX_TYPE = base.TX_VALUE
  545. #
  546. # TX
  547. #
  548. def set_state(self, data):
  549. """data: [True, False]"""
  550. self.pack(self.KEY_STATE, data)
  551. def set_state_mcb(self, device, key, data):
  552. self.logger.debug("Sending %s with content %s", key, str(data))
  553. self.set_state(data)
  554. class nodered_gui_light(nodered_gui_switch, nodered_gui_leds, nodered_gui_timer):
  555. KEY_ENABLE = "enable"
  556. KEY_BRIGHTNESS = "brightness"
  557. KEY_COLOR_TEMP = "color_temp"
  558. #
  559. TX_TYPE = base.TX_VALUE
  560. #
  561. RX_KEYS = nodered_gui_switch.RX_KEYS + [KEY_ENABLE, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
  562. #
  563. # RX
  564. #
  565. @property
  566. def enable(self):
  567. """rv: [True, False]"""
  568. return self.get(self.KEY_ENABLE)
  569. @property
  570. def brightness(self):
  571. """rv: [True, False]"""
  572. return self.get(self.KEY_BRIGHTNESS)
  573. @property
  574. def color_temp(self):
  575. """rv: [True, False]"""
  576. return self.get(self.KEY_COLOR_TEMP)
  577. #
  578. # TX
  579. #
  580. def set_enable(self, data):
  581. """data: [True, False]"""
  582. self.pack(self.KEY_ENABLE, data)
  583. def set_enable_mcb(self, device, key, data):
  584. self.logger.debug("Sending %s with content %s", key, str(data))
  585. self.set_enable(data)
  586. def set_brightness(self, data):
  587. """data: [0%, ..., 100%]"""
  588. self.pack(self.KEY_BRIGHTNESS, data)
  589. def set_brightness_mcb(self, device, key, data):
  590. self.logger.debug("Sending %s with content %s", key, str(data))
  591. self.set_brightness(data)
  592. def set_color_temp(self, data):
  593. """data: [0, ..., 10]"""
  594. self.pack(self.KEY_COLOR_TEMP, data)
  595. def set_color_temp_mcb(self, device, key, data):
  596. self.logger.debug("Sending %s with content %s", key, str(data))
  597. self.set_color_temp(data)
  598. class nodered_gui_radiator(nodered_gui_timer):
  599. KEY_TEMPERATURE = "temperature"
  600. KEY_SETPOINT_TEMP = "setpoint_temp"
  601. KEY_SETPOINT_TO_DEFAULT = "setpoint_to_default"
  602. KEY_BOOST = 'boost'
  603. KEY_AWAY = "away"
  604. KEY_SUMMER = "summer"
  605. KEY_ENABLE = "enable"
  606. #
  607. RX_KEYS = [KEY_TEMPERATURE, KEY_SETPOINT_TEMP, KEY_SETPOINT_TO_DEFAULT, KEY_BOOST, KEY_AWAY, KEY_SUMMER]
  608. #
  609. # TX
  610. #
  611. def set_temperature(self, data):
  612. """data: [True, False]"""
  613. self.pack(self.KEY_TEMPERATURE, data)
  614. def set_temperature_mcb(self, device, key, data):
  615. self.logger.debug("Sending %s with content %s", key, str(data))
  616. self.set_temperature(data)
  617. def set_setpoint_temperature(self, data):
  618. """data: [True, False]"""
  619. self.pack(self.KEY_SETPOINT_TEMP, data)
  620. def set_setpoint_temperature_mcb(self, device, key, data):
  621. self.logger.debug("Sending %s with content %s", key, str(data))
  622. self.set_setpoint_temperature(data)
  623. def set_away(self, data):
  624. """data: [True, False]"""
  625. self.pack(self.KEY_AWAY, data)
  626. def set_away_mcb(self, device, key, data):
  627. self.logger.debug("Sending %s with content %s", key, str(data))
  628. self.set_away(data)
  629. def set_summer(self, data):
  630. """data: [True, False]"""
  631. self.pack(self.KEY_SUMMER, data)
  632. def set_summer_mcb(self, device, key, data):
  633. self.logger.debug("Sending %s with content %s", key, str(data))
  634. self.set_summer(data)
  635. def set_enable(self, data):
  636. """data: [True, False]"""
  637. self.pack(self.KEY_ENABLE, data)
  638. def set_enable_mcb(self, device, key, data):
  639. self.logger.debug("Sending %s with content %s", key, str(data))
  640. self.set_enable(data)
  641. class brennenstuhl_heatingvalve(base):
  642. KEY_LINKQUALITY = "linkquality"
  643. KEY_BATTERY = "battery"
  644. KEY_HEATING_SETPOINT = "current_heating_setpoint"
  645. KEY_TEMPERATURE = "local_temperature"
  646. #
  647. KEY_AWAY_MODE = "away_mode"
  648. KEY_CHILD_LOCK = "child_lock"
  649. KEY_PRESET = "preset"
  650. KEY_SYSTEM_MODE = "system_mode"
  651. KEY_VALVE_DETECTION = "valve_detection"
  652. KEY_WINDOW_DETECTION = "window_detection"
  653. #
  654. TX_TYPE = base.TX_DICT
  655. #
  656. RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_HEATING_SETPOINT, KEY_TEMPERATURE]
  657. RX_IGNORE_KEYS = [KEY_AWAY_MODE, KEY_CHILD_LOCK, KEY_PRESET, KEY_SYSTEM_MODE, KEY_VALVE_DETECTION, KEY_WINDOW_DETECTION]
  658. def __init__(self, mqtt_client, topic):
  659. super().__init__(mqtt_client, topic)
  660. self.mqtt_client.send(self.topic + '/' + self.TX_TOPIC, json.dumps({self.KEY_WINDOW_DETECTION: "ON",
  661. self.KEY_CHILD_LOCK: "UNLOCK", self.KEY_VALVE_DETECTION: "ON", self.KEY_SYSTEM_MODE: "heat", self.KEY_PRESET: "manual"}))
  662. def warning_call_condition(self):
  663. return self.get(self.KEY_BATTERY, 100) <= BATTERY_WARN_LEVEL
  664. def warning_text(self):
  665. return "Low battery level detected for %s. Battery level was %.0f%%." % (self.topic, self.get(self.KEY_BATTERY))
  666. #
  667. # RX
  668. #
  669. @property
  670. def linkqulity(self):
  671. return self.get(self.KEY_LINKQUALITY)
  672. @property
  673. def heating_setpoint(self):
  674. return self.get(self.KEY_HEATING_SETPOINT)
  675. @property
  676. def temperature(self):
  677. return self.get(self.KEY_TEMPERATURE)
  678. #
  679. # TX
  680. #
  681. def set_heating_setpoint(self, setpoint):
  682. self.pack(self.KEY_HEATING_SETPOINT, setpoint)
  683. def set_heating_setpoint_mcb(self, device, key, data):
  684. self.logger.info("Changing heating setpoint to %s", str(data))
  685. self.set_heating_setpoint(data)
  686. class remote(base):
  687. KEY_CD = "CD"
  688. KEY_LINE1 = "LINE1"
  689. KEY_LINE3 = "LINE3"
  690. KEY_MUTE = "MUTE"
  691. KEY_POWER = "POWER"
  692. KEY_VOLDOWN = "VOLDOWN"
  693. KEY_VOLUP = "VOLUP"
  694. #
  695. TX_TOPIC = ''
  696. TX_TYPE = base.TX_VALUE
  697. #
  698. RX_IGNORE_TOPICS = [KEY_CD, KEY_LINE1, KEY_LINE3, KEY_MUTE, KEY_POWER, KEY_VOLUP, KEY_VOLDOWN]
  699. def set_cd(self, device=None, key=None, data=None):
  700. self.pack(self.KEY_CD, None)
  701. def set_line1(self, device=None, key=None, data=None):
  702. self.pack(self.KEY_LINE1, None)
  703. def set_line3(self, device=None, key=None, data=None):
  704. self.pack(self.KEY_LINE3, None)
  705. def set_mute(self, device=None, key=None, data=None):
  706. self.pack(self.KEY_MUTE, None)
  707. def set_power(self, device=None, key=None, data=None):
  708. self.pack(self.KEY_POWER, None)
  709. def set_volume_up(self, data=False):
  710. """data: [True, False]"""
  711. self.pack(self.KEY_VOLUP, data)
  712. def set_volume_down(self, data=False):
  713. """data: [True, False]"""
  714. self.pack(self.KEY_VOLDOWN, data)
  715. def default_inc(self, device=None, key=None, data=None):
  716. self.set_volume_up(True)
  717. def default_dec(self, device=None, key=None, data=None):
  718. self.set_volume_down(True)
  719. def default_stop(self, device=None, key=None, data=None):
  720. self.set_volume_up(False)
  721. class status(base):
  722. KEY_STATE = "state"
  723. #
  724. TX_TYPE = base.TX_VALUE
  725. #
  726. RX_KEYS = [KEY_STATE]
  727. def set_state(self, num, data):
  728. """data: [True, False]"""
  729. self.pack(self.KEY_STATE + "/" + str(num), data)
  730. def set_state_mcb(self, device, key, data):
  731. self.logger.info("Changing state to %s", str(data))
  732. self.set_state(data)
  733. class audio_status(status):
  734. KEY_TITLE = "title"
  735. #
  736. RX_KEYS = [status.KEY_STATE, KEY_TITLE]