Explorar el Código

heating functionality addad (sleep_madi)

tags/v1.0.0
Dirk Alders hace 2 años
padre
commit
1c245a4118
Se han modificado 8 ficheros con 175 adiciones y 10 borrados
  1. 3
    0
      .gitmodules
  2. 76
    6
      devices/__init__.py
  3. 6
    1
      function/__init__.py
  4. 86
    0
      function/modules.py
  5. 0
    1
      function/rooms.py
  6. 1
    1
      mqtt
  7. 2
    1
      smart_brain.py
  8. 1
    0
      task

+ 3
- 0
.gitmodules Ver fichero

@@ -4,3 +4,6 @@
4 4
 [submodule "report"]
5 5
 	path = report
6 6
 	url = https://git.mount-mockery.de/pylib/report.git
7
+[submodule "task"]
8
+	path = task
9
+	url = https://git.mount-mockery.de/pylib/task.git

+ 76
- 6
devices/__init__.py Ver fichero

@@ -60,7 +60,6 @@ class base(dict):
60 60
     TX_TYPE = -1
61 61
     TX_FILTER_DATA_KEYS = []
62 62
     #
63
-    RX_LOG_INFO_ALWAYS_KEYS = []
64 63
     RX_KEYS = []
65 64
     RX_IGNORE_TOPICS = []
66 65
     RX_IGNORE_KEYS = []
@@ -96,8 +95,10 @@ class base(dict):
96 95
             self[key] = data
97 96
             # Filter, if needed
98 97
             self.unpack_filter(key)
99
-            logger.log(logging.INFO if key in self.RX_LOG_INFO_ALWAYS_KEYS or prev_value != self.get(key) else logging.DEBUG,
100
-                       "Received data for (%s) %s - %s", self.topic, key, str(self.get(key)))
98
+            if prev_value != self.get(key):
99
+                logger.info("Received new data for (%s) %s - %s", self.topic, key, str(self.get(key)))
100
+            else:
101
+                logger.debug("Received data for (%s) %s - %s", self.topic, key, str(self.get(key)))
101 102
             self.callback_caller(key, self[key])
102 103
         elif key not in self.RX_IGNORE_KEYS:
103 104
             logger.warning('Got a message from \"%s\" with unparsed content "%s"', self.topic, key)
@@ -143,6 +144,7 @@ class base(dict):
143 144
                 logger.error(
144 145
                     "Unknown tx type. Set TX_TYPE of class to a known value")
145 146
             else:
147
+                logger.info("Sending data for (%s) %s - %s", self.topic, key, str(data))
146 148
                 if self.TX_TYPE == self.TX_DICT:
147 149
                     self.mqtt_client.send('/'.join([self.topic, self.TX_TOPIC]), json.dumps({key: data}))
148 150
                 else:
@@ -452,7 +454,6 @@ class tradfri_button(base):
452 454
     KEY_BATTERY = "battery"
453 455
     KEY_ACTION = "action"
454 456
     #
455
-    RX_LOG_INFO_ALWAYS_KEYS = [KEY_ACTION]
456 457
     RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION]
457 458
     RX_IGNORE_TOPICS = []
458 459
     RX_IGNORE_KEYS = ['update']
@@ -485,13 +486,14 @@ class nodered_gui(base):
485 486
     KEY_STATE = "state"
486 487
     KEY_BRIGHTNESS = "brightness"
487 488
     KEY_COLOR_TEMP = "color_temp"
489
+    KEY_HEATING_BOOST = "heating_boost"
490
+    KEY_HEATING_SETPOINT = "heating_setpoint"
488 491
     #
489 492
     TX_TOPIC = 'set'
490 493
     TX_TYPE = base.TX_VALUE
491 494
     TX_FILTER_DATA_KEYS = []
492 495
     #
493
-    RX_LOG_INFO_ALWAYS_KEYS = []
494
-    RX_KEYS = [KEY_STATE, KEY_BRIGHTNESS, KEY_COLOR_TEMP]
496
+    RX_KEYS = [KEY_STATE, KEY_BRIGHTNESS, KEY_COLOR_TEMP, KEY_HEATING_BOOST, KEY_HEATING_SETPOINT]
495 497
     RX_IGNORE_TOPICS = [KEY_FEEDBACK + '/' + TX_TOPIC, KEY_ENABLE + '/' + TX_TOPIC]
496 498
     RX_FILTER_DATA_KEYS = []
497 499
 
@@ -516,6 +518,16 @@ class nodered_gui(base):
516 518
         """rv: [0, ..., 100]"""
517 519
         return self.get(self.KEY_COLOR_TEMP)
518 520
 
521
+    @property
522
+    def heating_boost(self):
523
+        """rv: [True, False]"""
524
+        return self.get(self.KEY_HEATING_BOOST)
525
+
526
+    @property
527
+    def heating_(self):
528
+        """rv: [5, ..., 30]"""
529
+        return self.get(self.KEY_HEATING_SETPOINT)
530
+
519 531
     #
520 532
     # TX
521 533
     #
@@ -525,3 +537,61 @@ class nodered_gui(base):
525 537
     def enable(self, data):
526 538
         """data: [True, False]"""
527 539
         self.pack(self.KEY_ENABLE, data)
540
+
541
+
542
+class brennenstuhl_heatingvalve(base):
543
+    KEY_LINKQUALITY = "linkquality"
544
+    KEY_BATTERY = "battery"
545
+    KEY_HEATING_SETPOINT = "current_heating_setpoint"
546
+    KEY_TEMPERATURE = "local_temperature"
547
+    #
548
+    KEY_AWAY_MODE = "away_mode"
549
+    KEY_CHILD_LOCK = "child_lock"
550
+    KEY_PRESET = "preset"
551
+    KEY_SYSTEM_MODE = "system_mode"
552
+    KEY_VALVE_DETECTION = "valve_detection"
553
+    KEY_WINDOW_DETECTION = "window_detection"
554
+    #
555
+    TX_TOPIC = 'set'
556
+    TX_VALUE = 0
557
+    TX_DICT = 1
558
+    TX_TYPE = base.TX_DICT
559
+    TX_FILTER_DATA_KEYS = []
560
+    #
561
+    RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_HEATING_SETPOINT, KEY_TEMPERATURE]
562
+    RX_IGNORE_TOPICS = [TX_TOPIC]
563
+    RX_IGNORE_KEYS = [KEY_AWAY_MODE, KEY_CHILD_LOCK, KEY_PRESET,
564
+                      KEY_SYSTEM_MODE, KEY_VALVE_DETECTION, KEY_WINDOW_DETECTION]
565
+    RX_FILTER_DATA_KEYS = []
566
+
567
+    def __init__(self, mqtt_client, topic):
568
+        super().__init__(mqtt_client, topic)
569
+        self.mqtt_client.send(self.topic + '/' + self.TX_TOPIC, json.dumps(
570
+            {self.KEY_WINDOW_DETECTION: "ON", self.KEY_CHILD_LOCK: "UNLOCK", self.KEY_VALVE_DETECTION: "ON", self.KEY_SYSTEM_MODE: "heat"}))
571
+
572
+    def warning_call_condition(self):
573
+        return self.get(self.KEY_BATTERY) <= BATTERY_WARN_LEVEL
574
+
575
+    def warning_text(self):
576
+        return "Low battery level detected for %s. Battery level was %.0f%%." % (self.topic, self.get(self.KEY_BATTERY))
577
+
578
+    #
579
+    # RX
580
+    #
581
+    @property
582
+    def linkqulity(self):
583
+        return self.get(self.KEY_LINKQUALITY)
584
+
585
+    @property
586
+    def heating_setpoint(self):
587
+        return self.get(self.KEY_HEATING_SETPOINT)
588
+
589
+    @property
590
+    def temperature(self):
591
+        return self.get(self.KEY_TEMPERATURE)
592
+
593
+    #
594
+    # TX
595
+    #
596
+    def set_heating_setpoint(self, setpoint):
597
+        self.pack(self.KEY_HEATING_SETPOINT, setpoint)

+ 6
- 1
function/__init__.py Ver fichero

@@ -6,8 +6,8 @@ from function.ground_floor_west import ground_floor_west_floor, ground_floor_wes
6 6
 from function.first_floor_west import first_floor_west_julian, first_floor_west_living
7 7
 from function.first_floor_east import first_floor_east_floor, first_floor_east_kitchen, first_floor_east_dining, first_floor_east_sleep_madi, first_floor_east_living
8 8
 import inspect
9
+from function import modules
9 10
 
10
-# TODO: implement heating function sleep_madi
11 11
 # TODO: implement circulation pump
12 12
 # TODO: implement switch off functionality (except of switch off button transportation)
13 13
 # TODO: implement garland (incl. day events like sunset, sunrise, ...)
@@ -40,6 +40,7 @@ class all_functions(object):
40 40
         # additional functionality
41 41
         #
42 42
         self.init_input_device_sleep_madi_functionality()
43
+        self.init_heating_functionality()
43 44
 
44 45
     def init_input_device_sleep_madi_functionality(self):
45 46
         #
@@ -57,6 +58,10 @@ class all_functions(object):
57 58
         self.ffe_button_tradfri_sleep.add_callback(devices.tradfri_button.KEY_ACTION, None,
58 59
                                                    self.ffe_sleep_madi.fade_light)
59 60
 
61
+    def init_heating_functionality(self):
62
+        self.ffe_heating_sleep_madi = modules.heating_function_brennenstuhl(
63
+            self.mqtt_client, "zigbee_og_e/radiator/sleep_madi", 20, "gui/ffe_bo_sleep_madi", "gui/ffe_ts_sleep_madi", "gui/ffe_bl_sleep_madi")
64
+
60 65
     def devicelist(self):
61 66
         if self.__devices__ is None:
62 67
             self.__devices__ = []

+ 86
- 0
function/modules.py Ver fichero

@@ -0,0 +1,86 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+#
4
+
5
+import devices
6
+import logging
7
+import task
8
+
9
+try:
10
+    from config import APP_NAME as ROOT_LOGGER_NAME
11
+except ImportError:
12
+    ROOT_LOGGER_NAME = 'root'
13
+logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
14
+
15
+
16
+class heating_function_brennenstuhl(object):
17
+    RETURN_TO_DEFAULT_TIME = 45 * 60
18
+    BOOST_TEMP_OFFSET = 5
19
+
20
+    def __init__(self, mqtt_client, topic_valve, default_temperature, topic_boost, topic_setpoint, topic_led):
21
+        self.ct = task.periodic(1, self.cyclic_task)
22
+        #
23
+        self.topic = topic_valve
24
+        self.default_temperature = default_temperature
25
+        #
26
+        self.heating_valve = devices.brennenstuhl_heatingvalve(mqtt_client, topic_valve)
27
+        self.heating_valve.set_heating_setpoint(self.default_temperature)
28
+        self.heating_valve.add_callback(
29
+            devices.brennenstuhl_heatingvalve.KEY_HEATING_SETPOINT, None, self.heating_setpoint_actions)
30
+
31
+        self.gui_value_temp_setp = devices.nodered_gui(mqtt_client, topic_setpoint)
32
+        self.gui_value_temp_setp.add_callback(
33
+            devices.nodered_gui.KEY_HEATING_SETPOINT, None, self.heating_setpoint_actions)
34
+
35
+        self.gui_button_boost = devices.nodered_gui(mqtt_client, topic_boost)
36
+        self.gui_button_boost.add_callback(None, None, self.boost_actions)
37
+
38
+        self.gui_led_boost = devices.nodered_gui(mqtt_client, topic_led)
39
+
40
+        #
41
+        self.return_to_default_timer = None
42
+        self.return_to_default_setpoint = None
43
+        self.gui_led_boost.set_feedback(False)
44
+        #
45
+        self.ct.run()
46
+
47
+    def heating_setpoint_actions(self, device, key, data):
48
+        if device.topic == self.heating_valve.topic:
49
+            # valve setpoint action
50
+            self.gui_value_temp_setp.set_feedback(data)
51
+            if data > self.default_temperature:
52
+                if data != self.return_to_default_setpoint:
53
+                    logger.info('Got heating setpoint (%.1f°C) \"%s\" with deviation to the default value (%.1f°C). Starting timer for returning to default.',
54
+                                data, self.topic, self.default_temperature)
55
+                    self.return_to_default_timer = self.RETURN_TO_DEFAULT_TIME
56
+                    self.return_to_default_setpoint = data
57
+                    self.gui_led_boost.set_feedback(True)
58
+            else:
59
+                if self.return_to_default_timer is not None:
60
+                    logger.info('Deleting timer \"%s\" for returning to default.', self.topic)
61
+                self.return_to_default_timer = None
62
+                self.return_to_default_setpoint = None
63
+                self.gui_led_boost.set_feedback(False)
64
+        elif device.topic == self.gui_value_temp_setp.topic:
65
+            # user setpoint action
66
+            logger.info('Setpoint change \"%s\" to %.1f°C', self.topic, data)
67
+            self.default_temperature = data
68
+            self.heating_valve.set_heating_setpoint(self.default_temperature)
69
+            self.return_to_default_timer = None
70
+            self.return_to_default_setpoint = None
71
+            self.gui_led_boost.set_feedback(False)
72
+
73
+    def boost_actions(self, davice, key, data):
74
+        logger.info('Starting boost mode \"%s\" with setpoint %.1f°C.',
75
+                    self.topic, self.default_temperature + self.BOOST_TEMP_OFFSET)
76
+        self.heating_valve.set_heating_setpoint(self.default_temperature + self.BOOST_TEMP_OFFSET)
77
+
78
+    def cyclic_task(self, rt):
79
+        if self.return_to_default_timer is not None:
80
+            self.return_to_default_timer -= self.ct.cycle_time
81
+            if self.return_to_default_timer <= 0:
82
+                logger.info('Return to default timer expired \"%s\".', self.topic)
83
+                self.heating_valve.set_heating_setpoint(self.default_temperature)
84
+                self.return_to_default_timer = None
85
+                self.return_to_default_setpoint = None
86
+                self.gui_led_boost.set_feedback(False)

+ 0
- 1
function/rooms.py Ver fichero

@@ -2,7 +2,6 @@
2 2
 # -*- coding: utf-8 -*-
3 3
 #
4 4
 
5
-import config
6 5
 import devices
7 6
 import logging
8 7
 

+ 1
- 1
mqtt

@@ -1 +1 @@
1
-Subproject commit 1921bc619a9c4af682a7707d6fe58069478c59cd
1
+Subproject commit 79ac04ffdb61ea61334b2bb90bee565472957657

+ 2
- 1
smart_brain.py Ver fichero

@@ -10,7 +10,8 @@ logger = logging.getLogger(config.APP_NAME)
10 10
 
11 11
 if __name__ == "__main__":
12 12
     if config.DEBUG:
13
-        report.stdoutLoggingConfigure(([config.APP_NAME, logging.DEBUG], ), report.LONG_FMT)
13
+        report.appLoggingConfigure(None, None, ((config.APP_NAME, logging.DEBUG), ),
14
+                                   fmt=report.SHORT_FMT, host='localhost', port=19996)
14 15
     else:
15 16
         report.stdoutLoggingConfigure(((config.APP_NAME, logging.INFO),
16 17
                                       (config.APP_NAME+'.devices', logging.WARNING)), report.SHORT_FMT)

+ 1
- 0
task

@@ -0,0 +1 @@
1
+Subproject commit af35e83d1f07fd4cb9070bdb77cf1f3bdda3a463

Loading…
Cancelar
Guardar