Преглед на файлове

Initial user interface implemented

master
Dirk Alders преди 1 година
родител
ревизия
a6df2c99a4
променени са 8 файла, в които са добавени 238 реда и са изтрити 83 реда
  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 Целия файл

@@ -7,7 +7,7 @@ from devices.tradfri import sw as tradfri_sw
7 7
 from devices.tradfri import sw_br as tradfri_sw_br
8 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 11
 silvercrest_motion_sensor = None
12 12
 audio_status = None
13 13
 remote = None
@@ -17,7 +17,26 @@ class group(object):
17 17
     def __init__(self, *args):
18 18
         self.device_group = args
19 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 25
     def power_on_action(self, *args, **kwargs):
22 26
         for gm in self.device_group:
23 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 Целия файл

@@ -46,13 +46,14 @@ import time
46 46
 }
47 47
 """
48 48
 
49
+
49 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 57
     PROPERTIES = [
57 58
         "away_mode",
58 59
         "battery",
@@ -65,6 +66,7 @@ class vlv(base):
65 66
         "valve_detection",
66 67
         "window_detection"
67 68
     ]
69
+
68 70
     def __init__(self, mqtt_client, topic, **kwargs):
69 71
         super().__init__(mqtt_client, topic, **kwargs)
70 72
         self["away_mode"] = "OFF"
@@ -82,6 +84,11 @@ class vlv(base):
82 84
         #
83 85
         self.tq = task.threaded_queue()
84 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 93
     def set_state(self, value):
87 94
         self.__set__("state", "on" if value else "off")
@@ -104,3 +111,12 @@ class vlv(base):
104 111
                 data[key] = self[key]
105 112
         self.logger.info("Sending status: %s", repr(data))
106 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 Целия файл

@@ -17,14 +17,26 @@ class powerplug(base):
17 17
         super().__init__(mqtt_client, topic)
18 18
         #
19 19
         for i in range(0, 4):
20
-            self[self.PROPERTIES[i]] = False
20
+            self[self.PROPERTIES[i]] = 'false'
21 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 31
     def __rx_set__(self, client, userdata, message):
24 32
         data = message.payload.decode('utf-8')
25 33
         key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
26 34
         self.logger.info("Received set data for %s: %s", key, repr(data))
27 35
         self.__set__(key, data)
36
+
37
+    def __set__(self, key, data):
38
+        base.__set__(self, key, data)
39
+        #
28 40
         self.send_device_status(key)
29 41
         if key.startswith("output/"):
30 42
             if data == "true":
@@ -34,3 +46,19 @@ class powerplug(base):
34 46
         data = self[key]
35 47
         self.logger.info("Sending status for %s: %s", key, repr(data))
36 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 Целия файл

@@ -43,25 +43,35 @@ class shelly_sw1(base):
43 43
         topic (str): the base topic for this device
44 44
         kwargs (**dict): cd_r0=list of devices connected to relay/0
45 45
     """
46
+    KEY_OUTPUT_0 = "relay/0"
47
+    #
46 48
     PROPERTIES = [
47
-        "relay/0",
49
+        KEY_OUTPUT_0,
48 50
     ]
49 51
 
50 52
     def __init__(self, mqtt_client, topic):
51 53
         super().__init__(mqtt_client, topic)
52
-        self["state"] = "off"
54
+        self[self.KEY_OUTPUT_0] = "off"
53 55
         #
54 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 65
     def __rx_set__(self, client, userdata, message):
59 66
         data = message.payload.decode('utf-8')
60 67
         key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
61 68
         self.logger.info("Received set data for %s: %s", key, repr(data))
62 69
         self.__set__(key, data)
70
+
71
+    def __set__(self, key, data):
72
+        base.__set__(self, key, data)
63 73
         self.send_device_status(key)
64
-        if key == "relay/0":
74
+        if key == self.KEY_OUTPUT_0:
65 75
             if data.lower() == "on":
66 76
                 self.power_on(key)
67 77
                 if self.__auto_off__ is not None:
@@ -79,6 +89,9 @@ class shelly_sw1(base):
79 89
         self.__auto_off__ = task.delayed(sec, self.__auto_off_func__)
80 90
 
81 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 Целия файл

@@ -50,19 +50,25 @@ class sw(base):
50 50
         mqtt_client (mqtt.mqtt_client): A MQTT Client instance
51 51
         topic (str): the base topic for this device
52 52
     """
53
+    KEY_OUTPUT_0 = "state"
53 54
     PROPERTIES = [
54
-        "state",
55
+        KEY_OUTPUT_0,
55 56
     ]
56 57
 
57 58
     def __init__(self, mqtt_client, topic):
58 59
         super().__init__(mqtt_client, topic)
59
-        self["state"] = "off"
60
+        self[self.KEY_OUTPUT_0] = "off"
60 61
         #
61 62
         self.mqtt_client.add_callback(self.topic + '/set', self.__rx_set__)
62 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 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 72
         self.send_device_status()
67 73
 
68 74
     def __rx_set__(self, client, userdata, message):
@@ -71,14 +77,14 @@ class sw(base):
71 77
         for key in data:
72 78
             self.__set__(key, data[key])
73 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 83
     def __rx_get__(self, client, userdata, message):
78 84
         self.send_device_status()
79 85
 
80 86
     def power_on_action(self):
81
-        self["state"] = "on"
87
+        self[self.KEY_OUTPUT_0] = "on"
82 88
         self.send_device_status()
83 89
 
84 90
     def send_device_status(self):
@@ -86,6 +92,10 @@ class sw(base):
86 92
         self.logger.info("Sending status: %s", repr(data))
87 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 100
 class sw_br(sw):
91 101
     """A tradfri device with switching and brightness functionality

+ 62
- 0
home.py Целия файл

@@ -0,0 +1,62 @@
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 Целия файл

@@ -1,14 +1,15 @@
1 1
 import config
2 2
 import devdi
3
-import devdi.props as props
3
+import home
4 4
 import logging
5 5
 import mqtt
6 6
 import os
7 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 12
 # TODO: Implementation of missing devices in devices/__init__.py
11
-# TODO: Implementation of interface for external device stimulation
12 13
 
13 14
 logger = logging.getLogger(config.APP_NAME)
14 15
 
@@ -37,64 +38,17 @@ if __name__ == "__main__":
37 38
     #
38 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 Целия файл

@@ -0,0 +1,53 @@
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…
Отказ
Запис