Browse Source

Initial user interface implemented

master
Dirk Alders 1 year ago
parent
commit
a6df2c99a4
8 changed files with 238 additions and 83 deletions
  1. 20
    1
      devices/__init__.py
  2. 21
    5
      devices/brennenstuhl.py
  3. 29
    1
      devices/my.py
  4. 20
    7
      devices/shelly.py
  5. 16
    6
      devices/tradfri.py
  6. 62
    0
      home.py
  7. 17
    63
      home_emulation.py
  8. 53
    0
      user_interface.py

+ 20
- 1
devices/__init__.py View File

7
 from devices.tradfri import sw_br as tradfri_sw_br
7
 from devices.tradfri import sw_br as tradfri_sw_br
8
 from devices.tradfri import sw_br_ct as tradfri_sw_br_ct
8
 from devices.tradfri import sw_br_ct as tradfri_sw_br_ct
9
 
9
 
10
-tradfri_button = None   # TODO: required, when a interface for external device stimulation is available
10
+tradfri_button = None
11
 silvercrest_motion_sensor = None
11
 silvercrest_motion_sensor = None
12
 audio_status = None
12
 audio_status = None
13
 remote = None
13
 remote = None
17
     def __init__(self, *args):
17
     def __init__(self, *args):
18
         self.device_group = args
18
         self.device_group = args
19
         self.topic = self.device_group[0].topic
19
         self.topic = self.device_group[0].topic
20
+        #
21
+        self.user_cmds = {}
22
+        for key in self.device_group[0].user_cmds:
23
+            self.user_cmds[key] = getattr(self, self.device_group[0].user_cmds[key].__name__)
20
 
24
 
21
     def power_on_action(self, *args, **kwargs):
25
     def power_on_action(self, *args, **kwargs):
22
         for gm in self.device_group:
26
         for gm in self.device_group:
23
             gm.power_on_action(*args, **kwargs)
27
             gm.power_on_action(*args, **kwargs)
28
+
29
+    def __getattribute__(self, name):
30
+        def group_execution(*args, **kwargs):
31
+            for member in self.device_group[:]:
32
+                m = getattr(member, name)
33
+                m(*args, **kwargs)
34
+        try:
35
+            rv = super().__getattribute__(name)
36
+        except AttributeError:
37
+            if callable(getattr(self.device_group[0], name)):
38
+                return group_execution
39
+            else:
40
+                return getattr(self.device_group[0], name)
41
+        else:
42
+            return rv

+ 21
- 5
devices/brennenstuhl.py View File

46
 }
46
 }
47
 """
47
 """
48
 
48
 
49
+
49
 class vlv(base):
50
 class vlv(base):
50
-#    """A tradfri device with switching functionality
51
+    """A tradfri device with switching functionality
51
 
52
 
52
-#    Args:
53
-#        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
54
-#        topic (str): the base topic for this device
55
-#    """
53
+    Args:
54
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
55
+        topic (str): the base topic for this device
56
+    """
56
     PROPERTIES = [
57
     PROPERTIES = [
57
         "away_mode",
58
         "away_mode",
58
         "battery",
59
         "battery",
65
         "valve_detection",
66
         "valve_detection",
66
         "window_detection"
67
         "window_detection"
67
     ]
68
     ]
69
+
68
     def __init__(self, mqtt_client, topic, **kwargs):
70
     def __init__(self, mqtt_client, topic, **kwargs):
69
         super().__init__(mqtt_client, topic, **kwargs)
71
         super().__init__(mqtt_client, topic, **kwargs)
70
         self["away_mode"] = "OFF"
72
         self["away_mode"] = "OFF"
82
         #
84
         #
83
         self.tq = task.threaded_queue()
85
         self.tq = task.threaded_queue()
84
         self.tq.run()
86
         self.tq.run()
87
+        #
88
+        cmd_base = self.topic.replace('/', '.') + '.'
89
+        self.user_cmds = {
90
+            cmd_base + 'setpoint': self.__ui_setpoint__,
91
+        }
85
 
92
 
86
     def set_state(self, value):
93
     def set_state(self, value):
87
         self.__set__("state", "on" if value else "off")
94
         self.__set__("state", "on" if value else "off")
104
                 data[key] = self[key]
111
                 data[key] = self[key]
105
         self.logger.info("Sending status: %s", repr(data))
112
         self.logger.info("Sending status: %s", repr(data))
106
         self.mqtt_client.send(self.topic, json.dumps(data))
113
         self.mqtt_client.send(self.topic, json.dumps(data))
114
+
115
+    def __ui_setpoint__(self, *args):
116
+        try:
117
+            value = float(args[0].replace(',', '.'))
118
+        except (ValueError, IndexError):
119
+            print("You need to give a numeric value as first argument.")
120
+        else:
121
+            self.__set__("current_heating_setpoint", value)
122
+            self.tq.enqueue(1, self.send_device_status, self)

+ 29
- 1
devices/my.py View File

17
         super().__init__(mqtt_client, topic)
17
         super().__init__(mqtt_client, topic)
18
         #
18
         #
19
         for i in range(0, 4):
19
         for i in range(0, 4):
20
-            self[self.PROPERTIES[i]] = False
20
+            self[self.PROPERTIES[i]] = 'false'
21
             self.mqtt_client.add_callback(self.topic + '/output/%d/set' % (i + 1), self.__rx_set__)
21
             self.mqtt_client.add_callback(self.topic + '/output/%d/set' % (i + 1), self.__rx_set__)
22
+        #
23
+        cmd_base = self.topic.replace('/', '.') + '.'
24
+        self.user_cmds = {
25
+            cmd_base + 'toggle1': self.__ui_toggle_output_0__,
26
+            cmd_base + 'toggle2': self.__ui_toggle_output_1__,
27
+            cmd_base + 'toggle3': self.__ui_toggle_output_2__,
28
+            cmd_base + 'toggle4': self.__ui_toggle_output_3__,
29
+        }
22
 
30
 
23
     def __rx_set__(self, client, userdata, message):
31
     def __rx_set__(self, client, userdata, message):
24
         data = message.payload.decode('utf-8')
32
         data = message.payload.decode('utf-8')
25
         key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
33
         key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
26
         self.logger.info("Received set data for %s: %s", key, repr(data))
34
         self.logger.info("Received set data for %s: %s", key, repr(data))
27
         self.__set__(key, data)
35
         self.__set__(key, data)
36
+
37
+    def __set__(self, key, data):
38
+        base.__set__(self, key, data)
39
+        #
28
         self.send_device_status(key)
40
         self.send_device_status(key)
29
         if key.startswith("output/"):
41
         if key.startswith("output/"):
30
             if data == "true":
42
             if data == "true":
34
         data = self[key]
46
         data = self[key]
35
         self.logger.info("Sending status for %s: %s", key, repr(data))
47
         self.logger.info("Sending status for %s: %s", key, repr(data))
36
         self.mqtt_client.send(self.topic + '/' + key, data)
48
         self.mqtt_client.send(self.topic + '/' + key, data)
49
+
50
+    def __ui_toggle_output__(self, num):
51
+        key = self.PROPERTIES[num]
52
+        self.__set__(key, 'false' if self.get(key).lower() == 'true' else 'true')
53
+
54
+    def __ui_toggle_output_0__(self, *args):
55
+        self.__ui_toggle_output__(0)
56
+
57
+    def __ui_toggle_output_1__(self, *args):
58
+        self.__ui_toggle_output__(1)
59
+
60
+    def __ui_toggle_output_2__(self, *args):
61
+        self.__ui_toggle_output__(2)
62
+
63
+    def __ui_toggle_output_3__(self, *args):
64
+        self.__ui_toggle_output__(3)

+ 20
- 7
devices/shelly.py View File

43
         topic (str): the base topic for this device
43
         topic (str): the base topic for this device
44
         kwargs (**dict): cd_r0=list of devices connected to relay/0
44
         kwargs (**dict): cd_r0=list of devices connected to relay/0
45
     """
45
     """
46
+    KEY_OUTPUT_0 = "relay/0"
47
+    #
46
     PROPERTIES = [
48
     PROPERTIES = [
47
-        "relay/0",
49
+        KEY_OUTPUT_0,
48
     ]
50
     ]
49
 
51
 
50
     def __init__(self, mqtt_client, topic):
52
     def __init__(self, mqtt_client, topic):
51
         super().__init__(mqtt_client, topic)
53
         super().__init__(mqtt_client, topic)
52
-        self["state"] = "off"
54
+        self[self.KEY_OUTPUT_0] = "off"
53
         #
55
         #
54
         self.__auto_off__ = None
56
         self.__auto_off__ = None
55
         #
57
         #
56
-        self.mqtt_client.add_callback(self.topic + '/relay/0/command', self.__rx_set__)
58
+        self.mqtt_client.add_callback(self.topic + '/' + self.KEY_OUTPUT_0 + '/command', self.__rx_set__)
59
+        #
60
+        cmd_base = self.topic.replace('/', '.') + '.'
61
+        self.user_cmds = {
62
+            cmd_base + 'toggle': self.__ui_toggle_output_0__,
63
+        }
57
 
64
 
58
     def __rx_set__(self, client, userdata, message):
65
     def __rx_set__(self, client, userdata, message):
59
         data = message.payload.decode('utf-8')
66
         data = message.payload.decode('utf-8')
60
         key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
67
         key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
61
         self.logger.info("Received set data for %s: %s", key, repr(data))
68
         self.logger.info("Received set data for %s: %s", key, repr(data))
62
         self.__set__(key, data)
69
         self.__set__(key, data)
70
+
71
+    def __set__(self, key, data):
72
+        base.__set__(self, key, data)
63
         self.send_device_status(key)
73
         self.send_device_status(key)
64
-        if key == "relay/0":
74
+        if key == self.KEY_OUTPUT_0:
65
             if data.lower() == "on":
75
             if data.lower() == "on":
66
                 self.power_on(key)
76
                 self.power_on(key)
67
                 if self.__auto_off__ is not None:
77
                 if self.__auto_off__ is not None:
79
         self.__auto_off__ = task.delayed(sec, self.__auto_off_func__)
89
         self.__auto_off__ = task.delayed(sec, self.__auto_off_func__)
80
 
90
 
81
     def __auto_off_func__(self):
91
     def __auto_off_func__(self):
82
-        if self.get(self.PROPERTIES[0]).lower() != 'off':
83
-            self.__set__(self.PROPERTIES[0], "off")
84
-            self.send_device_status(self.PROPERTIES[0])
92
+        if self.get(self.KEY_OUTPUT_0).lower() != 'off':
93
+            self.__set__(self.KEY_OUTPUT_0, "off")
94
+            self.send_device_status(self.KEY_OUTPUT_0)
95
+
96
+    def __ui_toggle_output_0__(self, *args):
97
+        self.__set__(self.KEY_OUTPUT_0, 'off' if self.get(self.KEY_OUTPUT_0).lower() == 'on' else 'on')

+ 16
- 6
devices/tradfri.py View File

50
         mqtt_client (mqtt.mqtt_client): A MQTT Client instance
50
         mqtt_client (mqtt.mqtt_client): A MQTT Client instance
51
         topic (str): the base topic for this device
51
         topic (str): the base topic for this device
52
     """
52
     """
53
+    KEY_OUTPUT_0 = "state"
53
     PROPERTIES = [
54
     PROPERTIES = [
54
-        "state",
55
+        KEY_OUTPUT_0,
55
     ]
56
     ]
56
 
57
 
57
     def __init__(self, mqtt_client, topic):
58
     def __init__(self, mqtt_client, topic):
58
         super().__init__(mqtt_client, topic)
59
         super().__init__(mqtt_client, topic)
59
-        self["state"] = "off"
60
+        self[self.KEY_OUTPUT_0] = "off"
60
         #
61
         #
61
         self.mqtt_client.add_callback(self.topic + '/set', self.__rx_set__)
62
         self.mqtt_client.add_callback(self.topic + '/set', self.__rx_set__)
62
         self.mqtt_client.add_callback(self.topic + '/get', self.__rx_get__)
63
         self.mqtt_client.add_callback(self.topic + '/get', self.__rx_get__)
64
+        #
65
+        cmd_base = self.topic.replace('/', '.') + '.'
66
+        self.user_cmds = {
67
+            cmd_base + 'toggle': self.__ui_toggle_output_0__,
68
+        }
63
 
69
 
64
     def set_state(self, value):
70
     def set_state(self, value):
65
-        self.__set__("state", "on" if value else "off")
71
+        self.__set__(self.KEY_OUTPUT_0, "on" if value else "off")
66
         self.send_device_status()
72
         self.send_device_status()
67
 
73
 
68
     def __rx_set__(self, client, userdata, message):
74
     def __rx_set__(self, client, userdata, message):
71
         for key in data:
77
         for key in data:
72
             self.__set__(key, data[key])
78
             self.__set__(key, data[key])
73
         self.send_device_status()
79
         self.send_device_status()
74
-        if "state" in data and data.get("state", 'OFF').lower() == "on":
75
-            self.power_on("state")
80
+        if self.KEY_OUTPUT_0 in data and data.get(self.KEY_OUTPUT_0, 'OFF').lower() == "on":
81
+            self.power_on(self.KEY_OUTPUT_0)
76
 
82
 
77
     def __rx_get__(self, client, userdata, message):
83
     def __rx_get__(self, client, userdata, message):
78
         self.send_device_status()
84
         self.send_device_status()
79
 
85
 
80
     def power_on_action(self):
86
     def power_on_action(self):
81
-        self["state"] = "on"
87
+        self[self.KEY_OUTPUT_0] = "on"
82
         self.send_device_status()
88
         self.send_device_status()
83
 
89
 
84
     def send_device_status(self):
90
     def send_device_status(self):
86
         self.logger.info("Sending status: %s", repr(data))
92
         self.logger.info("Sending status: %s", repr(data))
87
         self.mqtt_client.send(self.topic, data)
93
         self.mqtt_client.send(self.topic, data)
88
 
94
 
95
+    def __ui_toggle_output_0__(self, *args):
96
+        self.__set__(self.KEY_OUTPUT_0, 'off' if self.get(self.KEY_OUTPUT_0).lower() == 'on' else 'on')
97
+        self.send_device_status()
98
+
89
 
99
 
90
 class sw_br(sw):
100
 class sw_br(sw):
91
     """A tradfri device with switching and brightness functionality
101
     """A tradfri device with switching and brightness functionality

+ 62
- 0
home.py View File

1
+import devdi.props as props
2
+
3
+
4
+def functions(pd):
5
+    #######
6
+    # GFW #
7
+    #######
8
+    loc = props.LOC_GFW
9
+    # DIRK
10
+    roo = props.ROO_DIR
11
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
12
+    tml = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
13
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
14
+
15
+    sml = pd.get(props.STG_MYA, loc, roo, props.FUN_MPP)
16
+    tml = pd.get(props.STG_ZGW, loc, roo, props.FUN_DEL)
17
+    sml.register_power_on_instance(tml, sml.PROPERTIES[1])
18
+
19
+    # FLOOR
20
+    roo = props.ROO_FLO
21
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
22
+    tml = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
23
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
24
+
25
+    #######
26
+    # FFW #
27
+    #######
28
+    loc = props.LOC_FFW
29
+    # JULIAN
30
+    roo = props.ROO_JUL
31
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
32
+    tml = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
33
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
34
+    # LIVINGROOM
35
+    roo = props.ROO_LIV
36
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
37
+    tml = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
38
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
39
+    # SLEEP
40
+    roo = props.ROO_SLP
41
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
42
+    tml = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
43
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
44
+
45
+    #######
46
+    # FFE #
47
+    #######
48
+    loc = props.LOC_FFE
49
+    # KITCHEN
50
+    roo = props.ROO_KIT
51
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_CIR)
52
+    sml.auto_off(600)
53
+    # LIVINGROOM
54
+    roo = props.ROO_LIV
55
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
56
+    tml = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
57
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
58
+    # SLEEP
59
+    roo = props.ROO_SLP
60
+    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
61
+    tml = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
62
+    sml.register_power_on_instance(tml, sml.PROPERTIES[0])

+ 17
- 63
home_emulation.py View File

1
 import config
1
 import config
2
 import devdi
2
 import devdi
3
-import devdi.props as props
3
+import home
4
 import logging
4
 import logging
5
 import mqtt
5
 import mqtt
6
 import os
6
 import os
7
 import report
7
 import report
8
-import time
8
+import user_interface
9
 
9
 
10
+# TODO: Add some more ui commands for devices
11
+# TODO: Add some test for smart_brain
10
 # TODO: Implementation of missing devices in devices/__init__.py
12
 # TODO: Implementation of missing devices in devices/__init__.py
11
-# TODO: Implementation of interface for external device stimulation
12
 
13
 
13
 logger = logging.getLogger(config.APP_NAME)
14
 logger = logging.getLogger(config.APP_NAME)
14
 
15
 
37
     #
38
     #
38
     # Smart Home Functionality
39
     # Smart Home Functionality
39
     #
40
     #
40
-    #######
41
-    # GFW #
42
-    #######
43
-    loc = props.LOC_GFW
44
-    # DIRK
45
-    roo = props.ROO_DIR
46
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
47
-    tml = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
48
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
41
+    home.functions(pd)
49
 
42
 
50
-    sml = pd.get(props.STG_MYA, loc, roo, props.FUN_MPP)
51
-    tml = pd.get(props.STG_ZGW, loc, roo, props.FUN_DEL)
52
-    sml.register_power_on_instance(tml, sml.PROPERTIES[1])
53
-
54
-    # FLOOR
55
-    roo = props.ROO_FLO
56
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
57
-    tml = pd.get(props.STG_ZGW, loc, roo, props.FUN_MAL)
58
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
59
-
60
-    #######
61
-    # FFW #
62
-    #######
63
-    loc = props.LOC_FFW
64
-    # JULIAN
65
-    roo = props.ROO_JUL
66
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
67
-    tml = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
68
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
69
-    # LIVINGROOM
70
-    roo = props.ROO_LIV
71
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
72
-    tml = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
73
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
74
-    # SLEEP
75
-    roo = props.ROO_SLP
76
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
77
-    tml = pd.get(props.STG_ZFW, loc, roo, props.FUN_MAL)
78
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
79
-
80
-    #######
81
-    # FFE #
82
-    #######
83
-    loc = props.LOC_FFE
84
-    # KITCHEN
85
-    roo = props.ROO_KIT
86
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_CIR)
87
-    sml.auto_off(600)
88
-    # LIVINGROOM
89
-    roo = props.ROO_LIV
90
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
91
-    tml = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
92
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
93
-    # SLEEP
94
-    roo = props.ROO_SLP
95
-    sml = pd.get(props.STG_SHE, loc, roo, props.FUN_MAL)
96
-    tml = pd.get(props.STG_ZFE, loc, roo, props.FUN_MAL)
97
-    sml.register_power_on_instance(tml, sml.PROPERTIES[0])
98
-
99
-    while (True):
100
-        time.sleep(1)
43
+    #
44
+    # User Interface
45
+    #
46
+    ui = user_interface.user_interface()
47
+    # Add the device commands
48
+    for d in pd.values():
49
+        try:
50
+            for cmd in d.user_cmds:
51
+                ui.add_command(cmd, d.user_cmds[cmd])
52
+        except AttributeError:
53
+            pass    # device seems to have no user commands
54
+    ui.run()

+ 53
- 0
user_interface.py View File

1
+import readline
2
+import sys
3
+import time
4
+
5
+
6
+class user_interface(dict):
7
+    def __init__(self):
8
+        super().__init__(self)
9
+        self.add_command('quit', None)
10
+
11
+    def run(self):
12
+        readline.parse_and_bind("tab: complete")
13
+        readline.set_completer(self.completer)
14
+        print("\nEnter command: ")
15
+        while (True):
16
+            userfeedback = input('')
17
+            command = userfeedback.split(' ')[0]
18
+            if userfeedback == 'quit':
19
+                break
20
+            else:
21
+                self.call_it(userfeedback)
22
+
23
+    def call_it(self, userfeedback):
24
+        args = userfeedback.split(" ")
25
+        cmd = args.pop(0)
26
+        cb = self.get(cmd)
27
+        if callable(cb):
28
+            cb(*args)
29
+        else:
30
+            print("Unknown command \"%s\"" % name)
31
+
32
+    def add_command(self, name, callback):
33
+        self[name] = callback
34
+
35
+    def reduced_list(self, text):
36
+        """
37
+        Create reduced completation list
38
+        """
39
+        reduced_list = {}
40
+        for cmd in self.keys():
41
+            next_dot = cmd[len(text):].find('.')
42
+            if next_dot >= 0:
43
+                reduced_list[cmd[:len(text) + next_dot + 1]] = None
44
+            else:
45
+                reduced_list[cmd] = None
46
+        return reduced_list.keys()
47
+
48
+    def completer(self, text, state):
49
+        """
50
+        Our custom completer function
51
+        """
52
+        options = [x for x in self.reduced_list(text) if x.startswith(text)]
53
+        return options[state]

Loading…
Cancel
Save