Browse Source

heating functionality addad (sleep_madi)

tags/v1.0.0
Dirk Alders 2 years ago
parent
commit
1c245a4118
8 changed files with 175 additions and 10 deletions
  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 View File

4
 [submodule "report"]
4
 [submodule "report"]
5
 	path = report
5
 	path = report
6
 	url = https://git.mount-mockery.de/pylib/report.git
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 View File

60
     TX_TYPE = -1
60
     TX_TYPE = -1
61
     TX_FILTER_DATA_KEYS = []
61
     TX_FILTER_DATA_KEYS = []
62
     #
62
     #
63
-    RX_LOG_INFO_ALWAYS_KEYS = []
64
     RX_KEYS = []
63
     RX_KEYS = []
65
     RX_IGNORE_TOPICS = []
64
     RX_IGNORE_TOPICS = []
66
     RX_IGNORE_KEYS = []
65
     RX_IGNORE_KEYS = []
96
             self[key] = data
95
             self[key] = data
97
             # Filter, if needed
96
             # Filter, if needed
98
             self.unpack_filter(key)
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
             self.callback_caller(key, self[key])
102
             self.callback_caller(key, self[key])
102
         elif key not in self.RX_IGNORE_KEYS:
103
         elif key not in self.RX_IGNORE_KEYS:
103
             logger.warning('Got a message from \"%s\" with unparsed content "%s"', self.topic, key)
104
             logger.warning('Got a message from \"%s\" with unparsed content "%s"', self.topic, key)
143
                 logger.error(
144
                 logger.error(
144
                     "Unknown tx type. Set TX_TYPE of class to a known value")
145
                     "Unknown tx type. Set TX_TYPE of class to a known value")
145
             else:
146
             else:
147
+                logger.info("Sending data for (%s) %s - %s", self.topic, key, str(data))
146
                 if self.TX_TYPE == self.TX_DICT:
148
                 if self.TX_TYPE == self.TX_DICT:
147
                     self.mqtt_client.send('/'.join([self.topic, self.TX_TOPIC]), json.dumps({key: data}))
149
                     self.mqtt_client.send('/'.join([self.topic, self.TX_TOPIC]), json.dumps({key: data}))
148
                 else:
150
                 else:
452
     KEY_BATTERY = "battery"
454
     KEY_BATTERY = "battery"
453
     KEY_ACTION = "action"
455
     KEY_ACTION = "action"
454
     #
456
     #
455
-    RX_LOG_INFO_ALWAYS_KEYS = [KEY_ACTION]
456
     RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION]
457
     RX_KEYS = [KEY_LINKQUALITY, KEY_BATTERY, KEY_ACTION]
457
     RX_IGNORE_TOPICS = []
458
     RX_IGNORE_TOPICS = []
458
     RX_IGNORE_KEYS = ['update']
459
     RX_IGNORE_KEYS = ['update']
485
     KEY_STATE = "state"
486
     KEY_STATE = "state"
486
     KEY_BRIGHTNESS = "brightness"
487
     KEY_BRIGHTNESS = "brightness"
487
     KEY_COLOR_TEMP = "color_temp"
488
     KEY_COLOR_TEMP = "color_temp"
489
+    KEY_HEATING_BOOST = "heating_boost"
490
+    KEY_HEATING_SETPOINT = "heating_setpoint"
488
     #
491
     #
489
     TX_TOPIC = 'set'
492
     TX_TOPIC = 'set'
490
     TX_TYPE = base.TX_VALUE
493
     TX_TYPE = base.TX_VALUE
491
     TX_FILTER_DATA_KEYS = []
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
     RX_IGNORE_TOPICS = [KEY_FEEDBACK + '/' + TX_TOPIC, KEY_ENABLE + '/' + TX_TOPIC]
497
     RX_IGNORE_TOPICS = [KEY_FEEDBACK + '/' + TX_TOPIC, KEY_ENABLE + '/' + TX_TOPIC]
496
     RX_FILTER_DATA_KEYS = []
498
     RX_FILTER_DATA_KEYS = []
497
 
499
 
516
         """rv: [0, ..., 100]"""
518
         """rv: [0, ..., 100]"""
517
         return self.get(self.KEY_COLOR_TEMP)
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
     # TX
532
     # TX
521
     #
533
     #
525
     def enable(self, data):
537
     def enable(self, data):
526
         """data: [True, False]"""
538
         """data: [True, False]"""
527
         self.pack(self.KEY_ENABLE, data)
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 View File

6
 from function.first_floor_west import first_floor_west_julian, first_floor_west_living
6
 from function.first_floor_west import first_floor_west_julian, first_floor_west_living
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
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
 import inspect
8
 import inspect
9
+from function import modules
9
 
10
 
10
-# TODO: implement heating function sleep_madi
11
 # TODO: implement circulation pump
11
 # TODO: implement circulation pump
12
 # TODO: implement switch off functionality (except of switch off button transportation)
12
 # TODO: implement switch off functionality (except of switch off button transportation)
13
 # TODO: implement garland (incl. day events like sunset, sunrise, ...)
13
 # TODO: implement garland (incl. day events like sunset, sunrise, ...)
40
         # additional functionality
40
         # additional functionality
41
         #
41
         #
42
         self.init_input_device_sleep_madi_functionality()
42
         self.init_input_device_sleep_madi_functionality()
43
+        self.init_heating_functionality()
43
 
44
 
44
     def init_input_device_sleep_madi_functionality(self):
45
     def init_input_device_sleep_madi_functionality(self):
45
         #
46
         #
57
         self.ffe_button_tradfri_sleep.add_callback(devices.tradfri_button.KEY_ACTION, None,
58
         self.ffe_button_tradfri_sleep.add_callback(devices.tradfri_button.KEY_ACTION, None,
58
                                                    self.ffe_sleep_madi.fade_light)
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
     def devicelist(self):
65
     def devicelist(self):
61
         if self.__devices__ is None:
66
         if self.__devices__ is None:
62
             self.__devices__ = []
67
             self.__devices__ = []

+ 86
- 0
function/modules.py View File

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 View File

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

+ 1
- 1
mqtt

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

+ 2
- 1
smart_brain.py View File

10
 
10
 
11
 if __name__ == "__main__":
11
 if __name__ == "__main__":
12
     if config.DEBUG:
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
     else:
15
     else:
15
         report.stdoutLoggingConfigure(((config.APP_NAME, logging.INFO),
16
         report.stdoutLoggingConfigure(((config.APP_NAME, logging.INFO),
16
                                       (config.APP_NAME+'.devices', logging.WARNING)), report.SHORT_FMT)
17
                                       (config.APP_NAME+'.devices', logging.WARNING)), report.SHORT_FMT)

+ 1
- 0
task

1
+Subproject commit af35e83d1f07fd4cb9070bdb77cf1f3bdda3a463

Loading…
Cancel
Save