Browse Source

Initial test environment (extracted from smart_brain project)

tags/v1.2.0
Dirk Alders 1 year ago
parent
commit
88cfbb98db
14 changed files with 1606 additions and 0 deletions
  1. 12
    0
      .gitmodules
  2. 16
    0
      .vscode/launch.json
  3. 12
    0
      .vscode/settings.json
  4. 1
    0
      base.py
  5. 1
    0
      config.py
  6. 1
    0
      geo
  7. 1
    0
      mqtt
  8. 1
    0
      report
  9. 1
    0
      requirements.txt
  10. 810
    0
      simulation/devices.py
  11. 268
    0
      simulation/rooms.py
  12. 85
    0
      smart_brain_test.py
  13. 1
    0
      task
  14. 396
    0
      tests/__init__.py

+ 12
- 0
.gitmodules View File

@@ -0,0 +1,12 @@
1
+[submodule "geo"]
2
+	path = geo
3
+	url = https://git.mount-mockery.de/pylib/geo.git
4
+[submodule "report"]
5
+	path = report
6
+	url = https://git.mount-mockery.de/pylib/report.git
7
+[submodule "mqtt"]
8
+	path = mqtt
9
+	url = https://git.mount-mockery.de/pylib/mqtt.git
10
+[submodule "task"]
11
+	path = task
12
+	url = https://git.mount-mockery.de/pylib/task.git

+ 16
- 0
.vscode/launch.json View File

@@ -0,0 +1,16 @@
1
+{
2
+    // Verwendet IntelliSense zum Ermitteln möglicher Attribute.
3
+    // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen.
4
+    // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387
5
+    "version": "0.2.0",
6
+    "configurations": [
7
+        {
8
+            "name": "Python: Main File execution",
9
+            "type": "python",
10
+            "request": "launch",
11
+            "program": "${workspaceFolder}/smart_brain_test.py",
12
+            "console": "integratedTerminal",
13
+            "justMyCode": true
14
+        }
15
+    ]
16
+}

+ 12
- 0
.vscode/settings.json View File

@@ -0,0 +1,12 @@
1
+{
2
+    "python.defaultInterpreterPath": "./venv/bin/python",
3
+    "editor.formatOnSave": true,
4
+    "autopep8.args": [
5
+        "--max-line-length=150"
6
+    ],
7
+    "editor.fontSize": 14,
8
+    "emmet.includeLanguages": {
9
+        "django-html": "html"
10
+    },
11
+    "python.analysis.typeCheckingMode": "off"
12
+}

+ 1
- 0
base.py View File

@@ -0,0 +1 @@
1
+../smart_brain/base.py

+ 1
- 0
config.py View File

@@ -0,0 +1 @@
1
+../smart_brain/config.py

+ 1
- 0
geo

@@ -0,0 +1 @@
1
+Subproject commit 11166bb27ad2335f7812fcb88c788397f5106751

+ 1
- 0
mqtt

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

+ 1
- 0
report

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

+ 1
- 0
requirements.txt View File

@@ -0,0 +1 @@
1
+colored

+ 810
- 0
simulation/devices.py View File

@@ -0,0 +1,810 @@
1
+from base import mqtt_base
2
+import colored
3
+import copy
4
+import json
5
+import task
6
+import time
7
+
8
+COLOR_GUI_ACTIVE = colored.fg("light_blue")
9
+COLOR_GUI_PASSIVE = COLOR_GUI_ACTIVE + colored.attr("dim")
10
+COLOR_SHELLY = colored.fg("light_magenta")
11
+COLOR_POWERPLUG = colored.fg("light_cyan")
12
+COLOR_LIGHT_ACTIVE = colored.fg("yellow")
13
+COLOR_LIGHT_PASSIVE = COLOR_LIGHT_ACTIVE + colored.attr("dim")
14
+COLOR_MOTION_SENSOR = colored.fg("dark_orange_3b")
15
+COLOR_HEATING_VALVE = colored.fg("red")
16
+COLOR_REMOTE = colored.fg("green")
17
+
18
+
19
+class base(mqtt_base):
20
+    AUTOSEND = True
21
+    COMMANDS = []
22
+    BOOL_KEYS = []
23
+
24
+    def __init__(self, mqtt_client, topic, default_values=None):
25
+        super().__init__(mqtt_client, topic, default_values)
26
+
27
+        self.names = {}
28
+        self.commands = self.COMMANDS[:]
29
+        #
30
+        self.mqtt_client.add_callback(self.topic, self.__rx__)
31
+        self.mqtt_client.add_callback(self.topic + '/#', self.__rx__)
32
+
33
+    def add_channel_name(self, key, name):
34
+        self.names[key] = name
35
+
36
+    def capabilities(self):
37
+        return self.commands
38
+
39
+    def __payload_filter__(self, payload):
40
+        try:
41
+            return json.loads(payload)
42
+        except json.decoder.JSONDecodeError:
43
+            return payload.decode("utf-8")
44
+
45
+    def __rx__(self, client, userdata, message):
46
+        print("%s: __rx__ not handled!" % self.__class__.__name__)
47
+
48
+    def __tx__(self, keys_changed):
49
+        print("%s: __tx__ not handled!" % self.__class__.__name__)
50
+
51
+    def __send__(self, client, key, data):
52
+        self.__tx__([key])
53
+
54
+    def __ext_to_int__(self, key, data):
55
+        if key in self.BOOL_KEYS:
56
+            if data == 'toggle':
57
+                return not self.get(key)
58
+            return data == "on"
59
+        return data
60
+
61
+    def __int_to_ext__(self, key, value):
62
+        if key in self.BOOL_KEYS:
63
+            return "on" if value is True else "off"
64
+        return value
65
+
66
+    def __devicename__(self):
67
+        return " - ".join(self.topic.split('/')[1:])
68
+
69
+    def __percent_bar__(self, percent_value):
70
+        rv = ""
71
+        for i in range(0, 10):
72
+            rv += u"\u25ac" if (percent_value - 5) > 10*i else u"\u25ad"
73
+        return rv
74
+
75
+    def __command_int_value__(self, value):
76
+        try:
77
+            return int(value)
78
+        except TypeError:
79
+            print("You need to give a integer parameter not '%s'" % str(value))
80
+
81
+    def __command_float_value__(self, value):
82
+        try:
83
+            return float(value)
84
+        except TypeError:
85
+            print("You need to give a numeric parameter not '%s'" % str(value))
86
+
87
+    def print_formatted_light(self, color, state, description, led=False):
88
+        if led is True:
89
+            if state is True:
90
+                icon = colored.fg('green') + "\u2b24" + color
91
+            else:
92
+                icon = colored.fg('light_gray') + "\u2b24" + color
93
+        else:
94
+            icon = u'\u2b24' if state is True else u'\u25ef'
95
+        print(color + 10 * ' ' + icon + 9 * ' ' + self.__devicename__(), description + colored.attr("reset"))
96
+
97
+    def print_formatted_videv(self, color, state, description):
98
+        icon = u'\u25a0' if state is True else u'\u25a1'
99
+        print(color + 10 * ' ' + icon + 9 * ' ' + self.__devicename__(), description + colored.attr("reset"))
100
+
101
+    def print_formatted_percent(self, color, prefix, perc_value, value_str,  description):
102
+        if len(prefix) > 1 or len(value_str) > 7:
103
+            raise ValueError("Length of prefix (%d) > 1 or length of value_str (%d) > 7" % (len(prefix), len(value_str)))
104
+        print(color + prefix + self.__percent_bar__(perc_value), value_str + (8 - len(value_str))
105
+              * ' ' + self.__devicename__(), description + colored.attr("reset"))
106
+
107
+
108
+class base_videv(base):
109
+    def set(self, key, data, block_callback=[]):
110
+        if key in self.keys():
111
+            return super().set(key, data, block_callback)
112
+        else:
113
+            self.__send__(self, key, data)
114
+
115
+
116
+class shelly(base):
117
+    KEY_OUTPUT_0 = "relay/0"
118
+    KEY_OUTPUT_1 = "relay/1"
119
+    KEY_INPUT_0 = "input/0"
120
+    KEY_INPUT_1 = "input/1"
121
+    KEY_LONGPUSH_0 = "longpush/0"
122
+    KEY_LONGPUSH_1 = "longpush/1"
123
+    #
124
+    BOOL_KEYS = [KEY_OUTPUT_0, KEY_OUTPUT_1, KEY_INPUT_0, KEY_INPUT_1, KEY_LONGPUSH_0, KEY_LONGPUSH_1, ]
125
+    #
126
+    INPUT_FUNC_OUT1_FOLLOW = "out1_follow"
127
+    INPUT_FUNC_OUT1_TRIGGER = "out1_trigger"
128
+    INPUT_FUNC_OUT2_FOLLOW = "out2_follow"
129
+    INPUT_FUNC_OUT2_TRIGGER = "out2_trigger"
130
+    #
131
+    COMMANDS = [
132
+        "get_output_0", "toggle_output_0",
133
+        "get_output_1", "toggle_output_1",
134
+        "get_input_0", "toggle_input_0",
135
+        "get_input_1", "toggle_input_1",
136
+        "trigger_input_0_longpress", "trigger_input_1_longpress",
137
+    ]
138
+
139
+    def __init__(self, mqtt_client, topic, input_0_func=None, input_1_func=None, output_0_auto_off=None):
140
+        super().__init__(mqtt_client, topic, default_values={self.KEY_OUTPUT_0: False, self.KEY_OUTPUT_1: False,
141
+                                                             self.KEY_INPUT_0: False, self.KEY_INPUT_1: False})
142
+        #
143
+        self.__input_0_func = input_0_func
144
+        self.__input_1_func = input_1_func
145
+        self.__output_0_auto_off__ = output_0_auto_off
146
+        # print ouput changes
147
+        self.add_callback(self.KEY_OUTPUT_0, None, self.print_formatted, True)
148
+        self.add_callback(self.KEY_OUTPUT_1, None, self.print_formatted, True)
149
+        # publish state changes
150
+        self.add_callback(self.KEY_OUTPUT_0, None, self.__send__, True)
151
+        self.add_callback(self.KEY_OUTPUT_1, None, self.__send__, True)
152
+        #
153
+        if self.__output_0_auto_off__ is not None:
154
+            self.__delayed_off__ = task.delayed(float(self.__output_0_auto_off__), self.__auto_off__, self.KEY_OUTPUT_0)
155
+        self.add_callback(self.KEY_OUTPUT_0, True, self.__start_auto_off__)
156
+        #
157
+        self.add_callback(self.KEY_INPUT_0, self.__input_function__, None)
158
+        self.add_callback(self.KEY_INPUT_1, self.__input_function__, None)
159
+        #
160
+        self.__tx__((self.KEY_OUTPUT_0, self.KEY_OUTPUT_1))
161
+
162
+    def __rx__(self, client, userdata, message):
163
+        value = self.__payload_filter__(message.payload)
164
+        if message.topic.startswith(self.topic) and message.topic.endswith("/command"):
165
+            key = '/'.join(message.topic.split('/')[-3:-1])
166
+            self.set(key, self.__ext_to_int__(key, value))
167
+
168
+    def __tx__(self, keys_changed):
169
+        for key in keys_changed:
170
+            self.mqtt_client.send(self.topic + '/' + key, self.__int_to_ext__(key, self[key]))
171
+
172
+    def __input_function__(self, device, key, data):
173
+        if key == self.KEY_INPUT_0:
174
+            func = self.__input_0_func
175
+        elif key == self.KEY_INPUT_1:
176
+            func = self.__input_1_func
177
+        else:
178
+            func = None
179
+        if func == self.INPUT_FUNC_OUT1_FOLLOW:
180
+            self.set(self.KEY_OUTPUT_0, data)
181
+        elif func == self.INPUT_FUNC_OUT1_TRIGGER:
182
+            self.set__toggle_data__(self.KEY_OUTPUT_0)
183
+        elif func == self.INPUT_FUNC_OUT2_FOLLOW:
184
+            self.__set_data__(self.KEY_OUTPUT_1, data)
185
+        elif func == self.INPUT_FUNC_OUT2_TRIGGER:
186
+            self.__toggle_data__(self.KEY_OUTPUT_1)
187
+
188
+    def __start_auto_off__(self, device, key, data):
189
+        # stop delayed task if needed
190
+        if self.__output_0_auto_off__ is not None:
191
+            if not self.__delayed_off__._stopped:
192
+                self.__delayed_off__.stop()
193
+        # start delayed task
194
+        if self.__output_0_auto_off__ is not None:
195
+            self.__delayed_off__.run()
196
+
197
+    def __auto_off__(self, key):
198
+        if key == self.KEY_OUTPUT_0:
199
+            self.set(key, False)
200
+
201
+    def command(self, command):
202
+        if command in self.COMMANDS:
203
+            if command == self.COMMANDS[0]:
204
+                self.print_formatted(self, self.KEY_OUTPUT_0, self.get(self.KEY_OUTPUT_0))
205
+            elif command == self.COMMANDS[1]:
206
+                self.set(self.KEY_OUTPUT_0, not self[self.KEY_OUTPUT_0])
207
+            elif command == self.COMMANDS[2]:
208
+                self.print_formatted(self, self.KEY_OUTPUT_1, self.get(self.KEY_OUTPUT_1))
209
+            elif command == self.COMMANDS[3]:
210
+                self.set(self.KEY_OUTPUT_1, not self[self.KEY_OUTPUT_1])
211
+            elif command == self.COMMANDS[4]:
212
+                self.print_formatted(self, self.KEY_INPUT_0, self.get(self.KEY_INPUT_0))
213
+            elif command == self.COMMANDS[5]:
214
+                self.set(self.KEY_INPUT_0, not self[self.KEY_INPUT_0])
215
+            elif command == self.COMMANDS[6]:
216
+                self.print_formatted(self, self.KEY_INPUT_1, self.get(self.KEY_INPUT_1))
217
+            elif command == self.COMMANDS[7]:
218
+                self.set(self.KEY_INPUT_1, not self[self.KEY_INPUT_1])
219
+            elif command == self.COMMANDS[8]:
220
+                self.set(self.KEY_INPUT_0, not self[self.KEY_INPUT_0])
221
+                time.sleep(0.4)
222
+                self.set(self.KEY_LONGPUSH_0, True)
223
+                time.sleep(0.1)
224
+                self.set(self.KEY_INPUT_0, not self[self.KEY_INPUT_0])
225
+                self.set(self.KEY_LONGPUSH_0, False)
226
+            elif command == self.COMMANDS[9]:
227
+                self.set(self.KEY_INPUT_1, not self[self.KEY_INPUT_1])
228
+                time.sleep(0.4)
229
+                self.set(self.KEY_LONGPUSH_1, True)
230
+                time.sleep(0.1)
231
+                self.set(self.KEY_INPUT_1, not self[self.KEY_INPUT_1])
232
+                self.set(self.KEY_LONGPUSH_1, False)
233
+            else:
234
+                print("%s: not yet implemented!" % command)
235
+        else:
236
+            print("Unknown command!")
237
+
238
+    def print_formatted(self, device, key, value):
239
+        if value is not None:
240
+            info = (" - %ds" % self.__output_0_auto_off__) if self.__output_0_auto_off__ is not None and value else ""
241
+            channel = "(%s%s)" % (self.names.get(key, key), info)
242
+            self.print_formatted_light(COLOR_SHELLY, value, channel)
243
+
244
+
245
+class my_powerplug(base):
246
+    KEY_OUTPUT_0 = "state"
247
+    #
248
+    COMMANDS = [
249
+        "get_output", "toggle_output",
250
+    ]
251
+
252
+    def __init__(self, mqtt_client, topic, channel):
253
+        super().__init__(mqtt_client, topic + '/' + "output/%d" % (channel + 1), default_values={self.KEY_OUTPUT_0: False})
254
+        #
255
+        self.add_callback(self.KEY_OUTPUT_0, None, self.print_formatted, True)
256
+        self.add_callback(self.KEY_OUTPUT_0, None, self.__send__, True)
257
+        #
258
+        self.__tx__((self.KEY_OUTPUT_0, ))
259
+
260
+    def __rx__(self, client, userdata, message):
261
+        if message.topic == self.topic + '/set':
262
+            payload = self.__payload_filter__(message.payload)
263
+            if payload == "toggle":
264
+                payload = not self.get(self.KEY_OUTPUT_0)
265
+            self.set(self.KEY_OUTPUT_0, payload)
266
+
267
+    def __tx__(self, keys_changed):
268
+        for key in keys_changed:
269
+            self.mqtt_client.send(self.topic, json.dumps(self.get(key)))
270
+
271
+    def command(self, command):
272
+        if command in self.COMMANDS:
273
+            if command == self.COMMANDS[0]:
274
+                self.print_formatted(self, self.KEY_OUTPUT_0, self.get(self.KEY_OUTPUT_0))
275
+            elif command == self.COMMANDS[1]:
276
+                self.set(self.KEY_OUTPUT_0, not self.get(self.KEY_OUTPUT_0))
277
+            else:
278
+                print("%s: not yet implemented!" % command)
279
+        else:
280
+            print("Unknown command!")
281
+
282
+    def print_formatted(self, device, key, value):
283
+        if value is not None:
284
+            self.print_formatted_light(COLOR_POWERPLUG, value, "(%s)" % self.names.get(key, "State"))
285
+
286
+
287
+class silvercrest_powerplug(base):
288
+    KEY_OUTPUT_0 = "state"
289
+    #
290
+    BOOL_KEYS = [KEY_OUTPUT_0, ]
291
+    #
292
+    COMMANDS = [
293
+        "get_output", "toggle_output",
294
+    ]
295
+
296
+    def __init__(self, mqtt_client, topic):
297
+        super().__init__(mqtt_client, topic, default_values={self.KEY_OUTPUT_0: False})
298
+        #
299
+        self.add_callback(self.KEY_OUTPUT_0, None, self.print_formatted, True)
300
+        self.add_callback(self.KEY_OUTPUT_0, None, self.__send__, True)
301
+        #
302
+        self.__tx__((self.KEY_OUTPUT_0, ))
303
+
304
+    def __rx__(self, client, userdata, message):
305
+        if message.topic == self.topic + '/set':
306
+            state = json.loads(message.payload).get('state')
307
+            self.set(self.KEY_OUTPUT_0, self.__ext_to_int__(self.KEY_OUTPUT_0, state))
308
+
309
+    def __tx__(self, keys_changed):
310
+        for key in keys_changed:
311
+            self.mqtt_client.send(self.topic + '/' + key, self.__int_to_ext__(self.KEY_OUTPUT_0, self.get(self.KEY_OUTPUT_0)))
312
+
313
+    def command(self, command):
314
+        if command in self.COMMANDS:
315
+            if command == self.COMMANDS[0]:
316
+                self.print_formatted(self, self.KEY_OUTPUT_0, self.get(self.KEY_OUTPUT_0))
317
+            elif command == self.COMMANDS[1]:
318
+                self.set(self.KEY_OUTPUT_0, not self.get(self.KEY_OUTPUT_0))
319
+            else:
320
+                print("%s: not yet implemented!" % command)
321
+        else:
322
+            print("Unknown command!")
323
+
324
+    def print_formatted(self, device, key, value):
325
+        if value is not None:
326
+            self.print_formatted_light(COLOR_POWERPLUG, value, "(%s)" % self.names.get(key, key))
327
+
328
+
329
+class tradfri_light(base):
330
+    KEY_OUTPUT_0 = "state"
331
+    KEY_BRIGHTNESS = "brightness"
332
+    KEY_COLOR_TEMP = "color_temp"
333
+    #
334
+    BOOL_KEYS = [KEY_OUTPUT_0, ]
335
+    #
336
+    STATE_COMMANDS = ("get_state", "toggle_state", )
337
+    BRIGHTNESS_COMMANDS = ("get_brightness", "set_brightness",)
338
+    COLOR_TEMP_COMMANDS = ("get_color_temp", "set_color_temp",)
339
+
340
+    def __init__(self, mqtt_client, topic, enable_state=True, enable_brightness=False, enable_color_temp=False, send_on_power_on=True):
341
+        default_values = {}
342
+        if enable_state:
343
+            default_values[self.KEY_OUTPUT_0] = False
344
+        if enable_brightness:
345
+            default_values[self.KEY_BRIGHTNESS] = 50
346
+        if enable_color_temp:
347
+            default_values[self.KEY_COLOR_TEMP] = 5
348
+        super().__init__(mqtt_client, topic, default_values=default_values)
349
+        #
350
+        self.send_on_power_on = send_on_power_on
351
+        #
352
+        if enable_state:
353
+            self.commands.extend(self.STATE_COMMANDS)
354
+        if enable_brightness:
355
+            self.commands.extend(self.BRIGHTNESS_COMMANDS)
356
+        if enable_color_temp:
357
+            self.commands.extend(self.COLOR_TEMP_COMMANDS)
358
+        #
359
+        self.add_callback(self.KEY_OUTPUT_0, None, self.print_formatted, True)
360
+        self.add_callback(self.KEY_BRIGHTNESS, None, self.print_formatted, True)
361
+        self.add_callback(self.KEY_COLOR_TEMP, None, self.print_formatted, True)
362
+        self.add_callback(self.KEY_OUTPUT_0, None, self.__send__, True)
363
+        self.add_callback(self.KEY_BRIGHTNESS, None, self.__send__, True)
364
+        self.add_callback(self.KEY_COLOR_TEMP, None, self.__send__, True)
365
+
366
+    def __ext_to_int__(self, key, data):
367
+        if key == self.KEY_BRIGHTNESS:
368
+            return round((data - 1) / 2.53, 0)
369
+        elif key == self.KEY_COLOR_TEMP:
370
+            return round((data - 250) / 20.4, 0)
371
+        else:
372
+            return super().__ext_to_int__(key, data)
373
+
374
+    def __int_to_ext__(self, key, data):
375
+        if key == self.KEY_BRIGHTNESS:
376
+            return 1 + round(2.53 * data, 0)
377
+        elif key == self.KEY_COLOR_TEMP:
378
+            return 250 + round(20.4 * data, 0)
379
+        else:
380
+            return super().__int_to_ext__(key, data)
381
+
382
+    def __rx__(self, client, userdata, message):
383
+        data = json.loads(message.payload)
384
+        if self.get(self.KEY_OUTPUT_0) or data.get(self.KEY_OUTPUT_0) in ['on', 'toggle']:     # prevent non power changes, if not powered on
385
+            if message.topic.startswith(self.topic) and message.topic.endswith('/set'):
386
+                for targetkey in data.keys():
387
+                    value = data[targetkey]
388
+                    if targetkey in self.keys():
389
+                        self.set(targetkey, self.__ext_to_int__(targetkey, value))
390
+            elif message.topic == self.topic + '/get':
391
+                self.__tx__(None)
392
+
393
+    def __tx__(self, keys_changed):
394
+        tx_data = dict(self)
395
+        for key in tx_data:
396
+            tx_data[key] = self.__int_to_ext__(key, self[key])
397
+        self.mqtt_client.send(self.topic, json.dumps(tx_data))
398
+
399
+    def command(self, command):
400
+        try:
401
+            command, value = command.split(' ')
402
+        except ValueError:
403
+            value = None
404
+        if command in self.capabilities():
405
+            if command == self.STATE_COMMANDS[0]:
406
+                self.print_formatted(self, self.KEY_OUTPUT_0, self.get(self.KEY_OUTPUT_0))
407
+            elif command == self.STATE_COMMANDS[1]:
408
+                self.set(self.KEY_OUTPUT_0, not self.get(self.KEY_OUTPUT_0))
409
+            elif command == self.BRIGHTNESS_COMMANDS[0]:
410
+                self.print_formatted(self, self.KEY_BRIGHTNESS, self.get(self.KEY_BRIGHTNESS))
411
+            elif command == self.BRIGHTNESS_COMMANDS[1]:
412
+                self.set(self.KEY_BRIGHTNESS, self.__command_int_value__(value))
413
+            elif command == self.COLOR_TEMP_COMMANDS[0]:
414
+                self.print_formatted(self, self.KEY_COLOR_TEMP, self.get(self.KEY_COLOR_TEMP))
415
+            elif command == self.COLOR_TEMP_COMMANDS[1]:
416
+                self.set(self.KEY_COLOR_TEMP, self.__command_int_value__(value))
417
+            else:
418
+                print("%s: not yet implemented!" % command)
419
+        else:
420
+            print("Unknown command!")
421
+
422
+    def power_off(self, device, key, value):
423
+        self.set(self.KEY_OUTPUT_0, False, block_callback=(self.__send__, ))
424
+
425
+    def power_on(self, device, key, value):
426
+        block_callback = [] if self.send_on_power_on else (self.__send__, )
427
+        self.set(self.KEY_OUTPUT_0, True, block_callback=block_callback)
428
+
429
+    def print_formatted(self, device, key, value):
430
+        if value is not None:
431
+            color = COLOR_LIGHT_ACTIVE
432
+            if key == self.KEY_OUTPUT_0:
433
+                self.print_formatted_light(COLOR_LIGHT_ACTIVE, value, "")
434
+                self.print_formatted(device, self.KEY_BRIGHTNESS, self.get(self.KEY_BRIGHTNESS))
435
+                self.print_formatted(device, self.KEY_COLOR_TEMP, self.get(self.KEY_COLOR_TEMP))
436
+            elif key in [self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
437
+                perc_value = round(value, 0) if key == self.KEY_BRIGHTNESS else round(10 * value, 0)
438
+                self.print_formatted_percent(
439
+                    COLOR_LIGHT_PASSIVE if not self.get(self.KEY_OUTPUT_0) else COLOR_LIGHT_ACTIVE,
440
+                    'B' if key == self.KEY_BRIGHTNESS else 'C',
441
+                    perc_value,
442
+                    "%3d%%" % perc_value,
443
+                    ""
444
+                )
445
+
446
+
447
+class brennenstuhl_heating_valve(base):
448
+    TEMP_RANGE = [10, 30]
449
+    #
450
+    KEY_TEMPERATURE_SETPOINT = "current_heating_setpoint"
451
+    KEY_TEMPERATURE = "local_temperature"
452
+    #
453
+    COMMANDS = [
454
+        "get_temperature_setpoint", "set_temperature_setpoint", "set_local_temperature",
455
+    ]
456
+
457
+    def __init__(self, mqtt_client, topic):
458
+        super().__init__(mqtt_client, topic, default_values={
459
+            self.KEY_TEMPERATURE_SETPOINT: 20,
460
+            self.KEY_TEMPERATURE: 20.7,
461
+        })
462
+        #
463
+        self.add_callback(self.KEY_TEMPERATURE_SETPOINT, None, self.print_formatted, True)
464
+        self.add_callback(self.KEY_TEMPERATURE_SETPOINT, None, self.__send__, True)
465
+        self.add_callback(self.KEY_TEMPERATURE, None, self.__send__, True)
466
+        #
467
+        self.__tx__((self.KEY_TEMPERATURE_SETPOINT, ))
468
+
469
+    def __rx__(self, client, userdata, message):
470
+        if message.topic.startswith(self.topic) and message.topic.endswith("/set"):
471
+            payload = self.__payload_filter__(message.payload)
472
+            for key in payload:
473
+                self.set(key, self.__ext_to_int__(key, payload[key]))
474
+
475
+    def __tx__(self, keys_changed):
476
+        tx_data = dict(self)
477
+        for key in tx_data:
478
+            tx_data[key] = self.__int_to_ext__(key, self[key])
479
+        self.mqtt_client.send(self.topic, json.dumps(tx_data))
480
+
481
+    def command(self, command):
482
+        try:
483
+            command, value = command.split(' ')
484
+        except ValueError:
485
+            value = None
486
+        if command in self.COMMANDS:
487
+            if command == self.COMMANDS[0]:
488
+                self.print_formatted(self, self.KEY_TEMPERATURE_SETPOINT, self.get(self.KEY_TEMPERATURE_SETPOINT))
489
+            elif command == self.COMMANDS[1]:
490
+                self.set(self.KEY_TEMPERATURE_SETPOINT, self.__command_float_value__(value))
491
+            elif command == self.COMMANDS[2]:
492
+                self.set(self.KEY_TEMPERATURE, self.__command_float_value__(value))
493
+
494
+    def print_formatted(self, device, key, value):
495
+        if key == self.KEY_TEMPERATURE_SETPOINT:
496
+            perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
497
+            perc = 100 if perc > 100 else perc
498
+            perc = 0 if perc < 0 else perc
499
+            self.print_formatted_percent(COLOR_HEATING_VALVE, '\u03d1', perc, "%4.1f°C" % value, "")
500
+
501
+
502
+class videv_light(base_videv):
503
+    KEY_OUTPUT_0 = "state"
504
+    KEY_BRIGHTNESS = "brightness"
505
+    KEY_COLOR_TEMP = "color_temp"
506
+    KEY_TIMER = "timer"
507
+    #
508
+    STATE_COMMANDS = ("get_state", "toggle_state", )
509
+    BRIGHTNESS_COMMANDS = ("get_brightness", "set_brightness", )
510
+    COLOR_TEMP_COMMANDS = ("get_color_temp", "set_color_temp", )
511
+    TIMER_COMMANDS = ("get_timer", )
512
+
513
+    def __init__(self, mqtt_client, topic, enable_state=True, enable_brightness=False, enable_color_temp=False, enable_timer=False):
514
+        default_values = {}
515
+        if enable_state:
516
+            default_values[self.KEY_OUTPUT_0] = False
517
+        if enable_brightness:
518
+            default_values[self.KEY_BRIGHTNESS] = 50
519
+        if enable_color_temp:
520
+            default_values[self.KEY_COLOR_TEMP] = 5
521
+        if enable_timer:
522
+            default_values[self.KEY_TIMER] = 0
523
+        super().__init__(mqtt_client, topic, default_values=default_values)
524
+        #
525
+        self.enable_state = enable_state
526
+        self.enable_brightness = enable_brightness
527
+        self.enable_color_temp = enable_color_temp
528
+        self.enable_timer = enable_timer
529
+        #
530
+        if enable_state:
531
+            self.commands.extend(self.STATE_COMMANDS)
532
+        if enable_brightness:
533
+            self.commands.extend(self.BRIGHTNESS_COMMANDS)
534
+        if enable_color_temp:
535
+            self.commands.extend(self.COLOR_TEMP_COMMANDS)
536
+        if enable_timer:
537
+            self.commands.extend(self.TIMER_COMMANDS)
538
+        #
539
+        self.timer_maxvalue = None
540
+        # add commands to be available
541
+        self.add_callback(self.KEY_OUTPUT_0, None, self.print_formatted, True)
542
+        self.add_callback(self.KEY_BRIGHTNESS, None, self.print_formatted, True)
543
+        self.add_callback(self.KEY_COLOR_TEMP, None, self.print_formatted, True)
544
+        self.add_callback(self.KEY_TIMER, None, self.print_formatted, True)
545
+        self.add_callback(self.KEY_OUTPUT_0, None, self.__send__, True)
546
+        self.add_callback(self.KEY_BRIGHTNESS, None, self.__send__, True)
547
+        self.add_callback(self.KEY_COLOR_TEMP, None, self.__send__, True)
548
+        self.add_callback(self.KEY_TIMER, None, self.__send__, True)
549
+
550
+    def __rx__(self, client, userdata, message):
551
+        value = self.__payload_filter__(message.payload)
552
+        if message.topic.startswith(self.topic):
553
+            targetkey = message.topic.split('/')[-1]
554
+            if targetkey in self.keys():
555
+                self.set(targetkey, value, block_callback=(self.__send__, ))
556
+            elif targetkey != "__info__":
557
+                print("Unknown key %s in %s::%s" % (targetkey, message.topic, self.__class__.__name__))
558
+
559
+    def __tx__(self, keys_changed):
560
+        for key in keys_changed:
561
+            topic = self.topic + '/' + key
562
+            self.mqtt_client.send(topic, json.dumps(self[key]))
563
+
564
+    def command(self, command):
565
+        try:
566
+            command, value = command.split(' ')
567
+        except ValueError:
568
+            value = None
569
+        if command in self.capabilities():
570
+            if command == self.STATE_COMMANDS[0]:
571
+                self.print_formatted(self, self.KEY_OUTPUT_0, self.get(self.KEY_OUTPUT_0))
572
+            elif command == self.STATE_COMMANDS[1]:
573
+                self.set(self.KEY_OUTPUT_0, not self.get(self.KEY_OUTPUT_0))
574
+            elif command == self.BRIGHTNESS_COMMANDS[0]:
575
+                self.print_formatted(self, self.KEY_BRIGHTNESS, self.get(self.KEY_BRIGHTNESS))
576
+            elif command == self.BRIGHTNESS_COMMANDS[1]:
577
+                self.set(self.KEY_BRIGHTNESS, self.__command_int_value__(value))
578
+            elif command == self.COLOR_TEMP_COMMANDS[0]:
579
+                self.print_formatted(self, self.KEY_COLOR_TEMP, self.get(self.KEY_COLOR_TEMP))
580
+            elif command == self.COLOR_TEMP_COMMANDS[1]:
581
+                self.set(self.KEY_COLOR_TEMP, self.__command_int_value__(value))
582
+            elif command == self.TIMER_COMMANDS[0]:
583
+                self.print_formatted(self, self.KEY_TIMER, self.get(self.KEY_TIMER))
584
+            else:
585
+                print("%s: not yet implemented!" % command)
586
+        else:
587
+            print("Unknown command!")
588
+
589
+    def print_formatted(self, device, key, value):
590
+        if value is not None:
591
+            if key == self.KEY_OUTPUT_0:
592
+                self.print_formatted_videv(COLOR_GUI_ACTIVE, value, "")
593
+            elif key in [self.KEY_BRIGHTNESS, self.KEY_COLOR_TEMP]:
594
+                perc_value = round(value * 10 if key == self.KEY_COLOR_TEMP else value, 0)
595
+                self.print_formatted_percent(
596
+                    COLOR_GUI_ACTIVE,
597
+                    'B' if key == self.KEY_BRIGHTNESS else 'C',
598
+                    perc_value,
599
+                    "%3d%%" % perc_value,
600
+                    ""
601
+                )
602
+            elif key == self.KEY_TIMER:
603
+                if value > 0:
604
+                    if self.timer_maxvalue is None and value != 0:
605
+                        self.timer_maxvalue = value
606
+                    disp_value = value
607
+                    try:
608
+                        perc = disp_value / self.timer_maxvalue * 100
609
+                    except ZeroDivisionError:
610
+                        perc = 0
611
+                else:
612
+                    disp_value = 0
613
+                    perc = 0
614
+                    self.timer_maxvalue = None
615
+                self.print_formatted_percent(COLOR_GUI_ACTIVE, 't', perc, '%3d%%' % perc, '(%.1f)' % disp_value)
616
+
617
+
618
+class videv_heating(base_videv):
619
+    TEMP_RANGE = [10, 30]
620
+    #
621
+    KEY_USER_TEMPERATURE_SETPOINT = 'user_temperature_setpoint'
622
+    KEY_VALVE_TEMPERATURE_SETPOINT = 'valve_temperature_setpoint'
623
+    KEY_AWAY_MODE = 'away_mode'
624
+    KEY_SUMMER_MODE = 'summer_mode'
625
+    KEY_SET_DEFAULT_TEMPERATURE = 'set_default_temperature'
626
+    KEY_START_BOOST = 'start_boost'
627
+    KEY_BOOST_TIMER = 'boost_timer'
628
+    #
629
+    KEY_TEMPERATURE = 'temperature'
630
+    #
631
+    COMMANDS = ["get_temperature_setpoint", "set_temperature_setpoint", "toggle_away_mode",
632
+                "toggle_summer_mode", "trigger_default_temperature", "trigger_boost"]
633
+
634
+    def __init__(self, mqtt_client, topic):
635
+        super().__init__(mqtt_client, topic, default_values={
636
+            self.KEY_USER_TEMPERATURE_SETPOINT: 20,
637
+            self.KEY_VALVE_TEMPERATURE_SETPOINT: 20,
638
+            self.KEY_TEMPERATURE: 20.7,
639
+            self.KEY_AWAY_MODE: False,
640
+            self.KEY_SUMMER_MODE: False,
641
+            self.KEY_BOOST_TIMER: 0
642
+        })
643
+        self.add_callback(self.KEY_USER_TEMPERATURE_SETPOINT, None, self.print_formatted, True)
644
+        self.add_callback(self.KEY_VALVE_TEMPERATURE_SETPOINT, None, self.print_formatted, True)
645
+        self.add_callback(self.KEY_TEMPERATURE, None, self.print_formatted, True)
646
+        self.add_callback(self.KEY_AWAY_MODE, None, self.print_formatted, True)
647
+        self.add_callback(self.KEY_SUMMER_MODE, None, self.print_formatted, True)
648
+        self.add_callback(self.KEY_BOOST_TIMER, None, self.print_formatted, True)
649
+        self.add_callback(self.KEY_USER_TEMPERATURE_SETPOINT, None, self.__send__, True)
650
+        self.add_callback(self.KEY_TEMPERATURE, None, self.__send__, True)
651
+        self.add_callback(self.KEY_AWAY_MODE, None, self.__send__, True)
652
+        self.add_callback(self.KEY_SUMMER_MODE, None, self.__send__, True)
653
+        #
654
+        self.timer_maxvalue = None
655
+
656
+    def __rx__(self, client, userdata, message):
657
+        value = self.__payload_filter__(message.payload)
658
+        if message.topic.startswith(self.topic):
659
+            targetkey = message.topic.split('/')[-1]
660
+            if targetkey in self.keys():
661
+                self.set(targetkey, value, block_callback=(self.__send__, ))
662
+            elif targetkey not in ["__info__", self.KEY_SET_DEFAULT_TEMPERATURE, self.KEY_START_BOOST]:
663
+                print("Unknown key %s in %s::%s" % (targetkey, message.topic, self.__class__.__name__))
664
+
665
+    def __tx__(self, keys_changed):
666
+        for key in keys_changed:
667
+            topic = self.topic + '/' + key
668
+            try:
669
+                self.mqtt_client.send(topic, json.dumps(self[key]))
670
+            except KeyError:
671
+                self.mqtt_client.send(topic, json.dumps(True))
672
+
673
+    def command(self, command):
674
+        try:
675
+            command, value = command.split(' ')
676
+        except ValueError:
677
+            value = None
678
+        if command in self.capabilities():
679
+            if command == self.commands[0]:
680
+                self.print_formatted(self, self.KEY_USER_TEMPERATURE_SETPOINT, self.get(self.KEY_USER_TEMPERATURE_SETPOINT))
681
+            elif command == self.commands[1]:
682
+                self.set(self.KEY_USER_TEMPERATURE_SETPOINT, self.__command_float_value__(value))
683
+            elif command == self.commands[2]:
684
+                self.set(self.KEY_AWAY_MODE, not self.get(self.KEY_AWAY_MODE))
685
+            elif command == self.commands[3]:
686
+                self.set(self.KEY_SUMMER_MODE, not self.get(self.KEY_SUMMER_MODE))
687
+            elif command == self.commands[4]:
688
+                self.set(self, self.KEY_SET_DEFAULT_TEMPERATURE, True)
689
+            elif command == self.commands[5]:
690
+                self.set(self, self.KEY_START_BOOST, True)
691
+            else:
692
+                print("%s: not yet implemented!" % command)
693
+        else:
694
+            print("Unknown command!")
695
+
696
+    def print_formatted(self, device, key, value):
697
+        desc_temp_dict = {
698
+            self.KEY_TEMPERATURE: "Current Temperature",
699
+            self.KEY_USER_TEMPERATURE_SETPOINT: "User Setpoint",
700
+            self.KEY_VALVE_TEMPERATURE_SETPOINT: "Valve Setpoint"
701
+        }
702
+        if value is not None:
703
+            if key in [self.KEY_TEMPERATURE, self.KEY_USER_TEMPERATURE_SETPOINT, self.KEY_VALVE_TEMPERATURE_SETPOINT]:
704
+                perc = 100 * (value - self.TEMP_RANGE[0]) / (self.TEMP_RANGE[1] - self.TEMP_RANGE[0])
705
+                perc = 100 if perc > 100 else perc
706
+                perc = 0 if perc < 0 else perc
707
+                self.print_formatted_percent(COLOR_GUI_ACTIVE, '\u03d1', perc, "%4.1f°C" % value, "(%s)" % desc_temp_dict[key])
708
+            elif key in [self.KEY_AWAY_MODE, self.KEY_SUMMER_MODE]:
709
+                self.print_formatted_videv(COLOR_GUI_ACTIVE, value, "(%s)" % "Away-Mode" if key == self.KEY_AWAY_MODE else "Summer-Mode")
710
+            elif key == self.KEY_BOOST_TIMER:
711
+                if value > 0:
712
+                    if self.timer_maxvalue is None and value != 0:
713
+                        self.timer_maxvalue = value
714
+                    disp_value = value
715
+                    try:
716
+                        perc = disp_value / self.timer_maxvalue * 100
717
+                    except ZeroDivisionError:
718
+                        perc = 0
719
+                else:
720
+                    disp_value = 0
721
+                    perc = 0
722
+                    self.timer_maxvalue = None
723
+                self.print_formatted_percent(COLOR_GUI_ACTIVE, 't', perc, '%3d%%' % perc, '(%.1f)' % disp_value)
724
+
725
+
726
+# class silvercrest_motion_sensor(base):
727
+#     KEY_OCCUPANCY = "occupancy"
728
+#     COMMANDS = ['motion']
729
+
730
+#     def __init__(self, mqtt_client, topic):
731
+#         super().__init__(mqtt_client, topic)
732
+#         self.data[self.KEY_OCCUPANCY] = False
733
+#         self.add_callback(self.KEY_OCCUPANCY, self.print_formatted, None)
734
+
735
+#     def __rx__(self, client, userdata, message):
736
+#         pass
737
+
738
+#     def command(self, command):
739
+#         try:
740
+#             command, value = command.split(' ')
741
+#         except ValueError:
742
+#             value = None
743
+#         else:
744
+#             value = json.loads(value)
745
+#         if command == self.COMMANDS[0]:
746
+#             self.store_data(**{self.KEY_OCCUPANCY: True})
747
+#             time.sleep(value or 10)
748
+#             self.store_data(**{self.KEY_OCCUPANCY: False})
749
+
750
+#     def print_formatted(self, device, key, value):
751
+#         if value is not None:
752
+#             print_light(COLOR_MOTION_SENSOR, value, self.topic, "")
753
+
754
+
755
+# class tradfri_button(base):
756
+#     KEY_ACTION = "action"
757
+#     #
758
+#     ACTION_TOGGLE = "toggle"
759
+#     ACTION_BRIGHTNESS_UP = "brightness_up_click"
760
+#     ACTION_BRIGHTNESS_DOWN = "brightness_down_click"
761
+#     ACTION_RIGHT = "arrow_right_click"
762
+#     ACTION_LEFT = "arrow_left_click"
763
+#     ACTION_BRIGHTNESS_UP_LONG = "brightness_up_hold"
764
+#     ACTION_BRIGHTNESS_DOWN_LONG = "brightness_down_hold"
765
+#     ACTION_RIGHT_LONG = "arrow_right_hold"
766
+#     ACTION_LEFT_LONG = "arrow_left_hold"
767
+#     #
768
+#     COMMANDS = [ACTION_TOGGLE, ACTION_LEFT, ACTION_RIGHT, ACTION_BRIGHTNESS_UP, ACTION_BRIGHTNESS_DOWN,
769
+#                 ACTION_LEFT_LONG, ACTION_RIGHT_LONG, ACTION_BRIGHTNESS_UP_LONG, ACTION_BRIGHTNESS_DOWN_LONG]
770
+
771
+#     def __init__(self, mqtt_client, topic):
772
+#         super().__init__(mqtt_client, topic)
773
+
774
+#     def __rx__(self, client, userdata, message):
775
+#         pass
776
+
777
+#     def command(self, command):
778
+#         try:
779
+#             command, value = command.split(' ')
780
+#         except ValueError:
781
+#             value = None
782
+#         else:
783
+#             value = json.loads(value)
784
+#         if command in self.capabilities():
785
+#             action = self.COMMANDS[self.COMMANDS.index(command)]
786
+#             if self.COMMANDS.index(command) <= 4:
787
+#                 self.mqtt_client.send(self.topic, json.dumps({self.KEY_ACTION: action}))
788
+#             elif self.COMMANDS.index(command) <= 8:
789
+#                 self.mqtt_client.send(self.topic, json.dumps({self.KEY_ACTION: action}))
790
+#                 time.sleep(value or 0.5)
791
+#                 action = '_'.join(action.split('_')[:-1] + ['release'])
792
+#                 self.mqtt_client.send(self.topic, json.dumps({self.KEY_ACTION: action}))
793
+
794
+
795
+# class remote(base):
796
+#     def __rx__(self, client, userdata, message):
797
+#         if message.topic == self.topic + "/VOLUP":
798
+#             if self.__payload_filter__(message.payload):
799
+#                 icon = u'\u1403'
800
+#             else:
801
+#                 icon = u'\u25a1'
802
+#         elif message.topic == self.topic + "/VOLDOWN":
803
+#             if self.__payload_filter__(message.payload):
804
+#                 icon = u'\u1401'
805
+#             else:
806
+#                 icon = u'\u25a1'
807
+#         else:
808
+#             return
809
+#         devicename = ' - '.join(self.topic.split('/')[1:-1])
810
+#         print(COLOR_REMOTE + 10 * ' ' + icon + 6 * ' ' + devicename + colored.attr("reset"))

+ 268
- 0
simulation/rooms.py View File

@@ -0,0 +1,268 @@
1
+import config
2
+from simulation.devices import shelly, silvercrest_powerplug, tradfri_light, my_powerplug, brennenstuhl_heating_valve
3
+from simulation.devices import videv_light, videv_heating
4
+import inspect
5
+
6
+
7
+class base(object):
8
+    def getmembers(self, prefix=''):
9
+        rv = []
10
+        for name, obj in inspect.getmembers(self):
11
+            if prefix:
12
+                full_name = prefix + '.' + name
13
+            else:
14
+                full_name = name
15
+            if not name.startswith('_'):
16
+                try:
17
+                    if obj.__module__.endswith('devices'):
18
+                        rv.append(full_name)
19
+                    else:
20
+                        rv.extend(obj.getmembers(full_name))
21
+                except AttributeError:
22
+                    pass
23
+        return rv
24
+
25
+    def getobjbyname(self, name):
26
+        obj = self
27
+        for subname in name.split('.'):
28
+            obj = getattr(obj, subname)
29
+        return obj
30
+
31
+    def command(self, full_command):
32
+        try:
33
+            parameter = " " + full_command.split(' ')[1]
34
+        except IndexError:
35
+            parameter = ""
36
+        command = full_command.split(' ')[0].split('.')[-1] + parameter
37
+        device_name = '.'.join(full_command.split(' ')[0].split('.')[:-1])
38
+        self.getobjbyname(device_name).command(command)
39
+
40
+
41
+class gfw_floor(base):
42
+    def __init__(self, mqtt_client):
43
+        self.main_light = shelly(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
44
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
45
+        self.main_light_zigbee_1 = tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_ZIGBEE % 1, True, True, True, False)
46
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee_1.power_on)
47
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee_1.power_off)
48
+        self.main_light_zigbee_2 = tradfri_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_ZIGBEE % 2, True, True, True, False)
49
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee_2.power_on)
50
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee_2.power_off)
51
+
52
+        #
53
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_VIDEV, True, True, True)
54
+
55
+
56
+class gfw_marion(base):
57
+    def __init__(self, mqtt_client):
58
+        self.main_light = shelly(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
59
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
60
+
61
+        self.heating_valve = brennenstuhl_heating_valve(mqtt_client, config.TOPIC_GFW_MARION_HEATING_VALVE_ZIGBEE)
62
+
63
+        #
64
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_GFW_MARION_MAIN_LIGHT_VIDEV, True, False, False)
65
+        self.videv_heating = videv_heating(mqtt_client, config.TOPIC_GFW_MARION_HEATING_VALVE_VIDEV)
66
+
67
+
68
+class gfw_dirk(base):
69
+    def __init__(self, mqtt_client):
70
+        self.main_light = shelly(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
71
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
72
+        self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_ZIGBEE, True, True, True)
73
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee.power_on)
74
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee.power_off)
75
+
76
+        self.amplifier = my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG, 0)
77
+        self.amplifier.add_channel_name(my_powerplug.KEY_OUTPUT_0, "Amplifier")
78
+        self.desk_light = my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG, 1)
79
+        self.desk_light.add_channel_name(my_powerplug.KEY_OUTPUT_0, "Desk Light")
80
+        self.cd_player = my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG, 2)
81
+        self.cd_player.add_channel_name(my_powerplug.KEY_OUTPUT_0, "CD_Player")
82
+        self.pc_dock = my_powerplug(mqtt_client, config.TOPIC_GFW_DIRK_POWERPLUG, 3)
83
+        self.pc_dock.add_channel_name(my_powerplug.KEY_OUTPUT_0, "PC_Dock")
84
+        self.desk_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_ZIGBEE, True, True, True)
85
+        self.desk_light.add_callback(my_powerplug.KEY_OUTPUT_0, True, self.desk_light_zigbee.power_on)
86
+        self.desk_light.add_callback(my_powerplug.KEY_OUTPUT_0, False, self.desk_light_zigbee.power_off)
87
+
88
+        self.heating_valve = brennenstuhl_heating_valve(mqtt_client, config.TOPIC_GFW_DIRK_HEATING_VALVE_ZIGBEE)
89
+
90
+        #
91
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_GFW_DIRK_MAIN_LIGHT_VIDEV, True, True, True)
92
+        self.videv_amplifier = videv_light(mqtt_client, config.TOPIC_GFW_DIRK_AMPLIFIER_VIDEV, True, False, False)
93
+        self.videv_desk_light = videv_light(mqtt_client, config.TOPIC_GFW_DIRK_DESK_LIGHT_VIDEV, True, True, True)
94
+        self.videv_cd_player = videv_light(mqtt_client, config.TOPIC_GFW_DIRK_CD_PLAYER_VIDEV, True, False, False)
95
+        self.videv_pc_dock = videv_light(mqtt_client, config.TOPIC_GFW_DIRK_PC_DOCK_VIDEV, True, False, False)
96
+        self.videv_heating = videv_heating(mqtt_client, config.TOPIC_GFW_DIRK_HEATING_VALVE_VIDEV)
97
+
98
+
99
+class gfw(base):
100
+    def __init__(self, mqtt_client):
101
+        self.floor = gfw_floor(mqtt_client)
102
+        self.marion = gfw_marion(mqtt_client)
103
+        self.dirk = gfw_dirk(mqtt_client)
104
+
105
+
106
+class ffw_julian(base):
107
+    def __init__(self, mqtt_client):
108
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
109
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
110
+        self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_ZIGBEE, True, True, True)
111
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee.power_on)
112
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee.power_off)
113
+
114
+        #
115
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_VIDEV, True, True, True)
116
+
117
+
118
+class ffw_livingroom(base):
119
+    def __init__(self, mqtt_client):
120
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
121
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
122
+        self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_ZIGBEE, True, True, True)
123
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee.power_on)
124
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee.power_off)
125
+
126
+        #
127
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_VIDEV, True, True, True)
128
+
129
+
130
+class ffw_sleep(base):
131
+    def __init__(self, mqtt_client):
132
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
133
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
134
+        self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_ZIGBEE, True, True, True)
135
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee.power_on)
136
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee.power_off)
137
+
138
+        #
139
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_VIDEV, True, True, False)
140
+
141
+
142
+class ffw_bath(base):
143
+    def __init__(self, mqtt_client):
144
+        self.heating_valve = brennenstuhl_heating_valve(mqtt_client, config.TOPIC_FFW_BATH_HEATING_VALVE_ZIGBEE)
145
+        #
146
+        self.videv_heating = videv_heating(mqtt_client, config.TOPIC_FFW_BATH_HEATING_VALVE_VIDEV)
147
+
148
+
149
+class ffw(base):
150
+    def __init__(self, mqtt_client):
151
+        self.julian = ffw_julian(mqtt_client)
152
+        self.livingroom = ffw_livingroom(mqtt_client)
153
+        self.sleep = ffw_sleep(mqtt_client)
154
+        self.bath = ffw_bath(mqtt_client)
155
+
156
+
157
+class ffe_floor(base):
158
+    def __init__(self, mqtt_client):
159
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
160
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
161
+
162
+        #
163
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_VIDEV, True, False, False)
164
+
165
+
166
+class ffe_kitchen(base):
167
+    def __init__(self, mqtt_client):
168
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
169
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
170
+
171
+        self.circulation_pump = shelly(mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_SHELLY,
172
+                                       input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER, output_0_auto_off=10*60)
173
+        self.circulation_pump.add_channel_name(shelly.KEY_OUTPUT_0, "Circulation Pump")
174
+
175
+        #
176
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_VIDEV, True, False, False)
177
+        self.videv_circulation_pump = videv_light(mqtt_client, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_VIDEV, True, False, False, True)
178
+
179
+
180
+class ffe_diningroom(base):
181
+    def __init__(self, mqtt_client):
182
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
183
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
184
+
185
+        self.floor_lamp = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_POWERPLUG)
186
+        self.floor_lamp.add_channel_name(silvercrest_powerplug.KEY_OUTPUT_0, "Floor Lamp")
187
+
188
+        if config.CHRISTMAS:
189
+            self.garland = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_POWERPLUG)
190
+            self.garland.add_channel_name(silvercrest_powerplug, "Garland")
191
+
192
+        #
193
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_VIDEV, True, False, False)
194
+        self.videv_floor_lamp = videv_light(mqtt_client, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_VIDEV, True, False, False)
195
+        if config.CHRISTMAS:
196
+            self.videv_garland = videv_light(mqtt_client, config.TOPIC_FFE_DININGROOM_GARLAND_VIDEV, True, False, False)
197
+
198
+
199
+class ffe_sleep(base):
200
+    def __init__(self, mqtt_client):
201
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
202
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
203
+        self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_ZIGBEE, True, True, True)
204
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee.power_on)
205
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee.power_off)
206
+
207
+        self.bed_light_di_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_ZIGBEE, True, True, False)
208
+        self.bed_light_ma = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_POWERPLUG)
209
+
210
+        self.heating_valve = brennenstuhl_heating_valve(mqtt_client, config.TOPIC_FFE_SLEEP_HEATING_VALVE_ZIGBEE)
211
+
212
+        #
213
+        self.videv_bed_light_ma = videv_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_VIDEV, True, False, False)
214
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_VIDEV, True, True, True)
215
+        self.videv_bed_light_di = videv_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_VIDEV, True, True, False)
216
+        self.videv_bed_light_ma = videv_light(mqtt_client, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_VIDEV, True, False, False)
217
+
218
+        self.videv_heating = videv_heating(mqtt_client, config.TOPIC_FFE_SLEEP_HEATING_VALVE_VIDEV)
219
+
220
+
221
+class ffe_livingroom(base):
222
+    def __init__(self, mqtt_client):
223
+        self.main_light = shelly(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
224
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
225
+        self.main_light_zigbee = tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_ZIGBEE, True, True, True)
226
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, True, self.main_light_zigbee.power_on)
227
+        self.main_light.add_callback(shelly.KEY_OUTPUT_0, False, self.main_light_zigbee.power_off)
228
+
229
+        for i in range(1, 7):
230
+            setattr(self, "floor_lamp_zigbee_%d" % i, tradfri_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_ZIGBEE % i, True, True, True))
231
+
232
+        if config.CHRISTMAS:
233
+            self.xmas_tree = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_POWERPLUG)
234
+            self.xmas_tree.add_channel_name(silvercrest_powerplug, "Xmas-Tree")
235
+            self.xmas_star = silvercrest_powerplug(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_STAR_POWERPLUG)
236
+            self.xmas_star.add_channel_name(silvercrest_powerplug, "Xmas-Star")
237
+
238
+        #
239
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_VIDEV, True, True, True)
240
+        self.videv_floor_lamp = videv_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_VIDEV, True, True, True)
241
+        if config.CHRISTMAS:
242
+            self.videv_xmas_tree = videv_light(mqtt_client, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_VIDEV)
243
+
244
+
245
+class ffe(base):
246
+    def __init__(self, mqtt_client):
247
+        self.floor = ffe_floor(mqtt_client)
248
+        self.kitchen = ffe_kitchen(mqtt_client)
249
+        self.diningroom = ffe_diningroom(mqtt_client)
250
+        self.sleep = ffe_sleep(mqtt_client)
251
+        self.livingroom = ffe_livingroom(mqtt_client)
252
+
253
+
254
+class stairway(base):
255
+    def __init__(self, mqtt_client):
256
+        self.main_light = shelly(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_SHELLY, input_0_func=shelly.INPUT_FUNC_OUT1_TRIGGER)
257
+        self.main_light.add_channel_name(shelly.KEY_OUTPUT_0, "Main Light")
258
+
259
+        #
260
+        self.videv_main_light = videv_light(mqtt_client, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_VIDEV, True, False, False, True)
261
+
262
+
263
+class house(base):
264
+    def __init__(self, mqtt_client):
265
+        self.gfw = gfw(mqtt_client)
266
+        self.ffw = ffw(mqtt_client)
267
+        self.ffe = ffe(mqtt_client)
268
+        self.stairway = stairway(mqtt_client)

+ 85
- 0
smart_brain_test.py View File

@@ -0,0 +1,85 @@
1
+import config
2
+import logging
3
+import mqtt
4
+import readline
5
+import report
6
+from simulation.rooms import house
7
+from tests import test_smarthome
8
+import time
9
+
10
+if __name__ == "__main__":
11
+    report.stdoutLoggingConfigure(
12
+        ((config.APP_NAME, logging.WARNING), ), report.SHORT_FMT)
13
+    #
14
+    mc = mqtt.mqtt_client(host=config.MQTT_SERVER, port=config.MQTT_PORT, username=config.MQTT_USER,
15
+                          password=config.MQTT_PASSWORD, name=config.APP_NAME + '_simulation')
16
+    #
17
+    COMMANDS = ['quit', 'help']
18
+    #
19
+    h = house(mc)
20
+    for name in h.getmembers():
21
+        d = h.getobjbyname(name)
22
+        for c in d.capabilities():
23
+            COMMANDS.append(name + '.' + c)
24
+    #
25
+    ts = test_smarthome(h)
26
+    for name in ts.getmembers():
27
+        d = ts.getobjbyname(name)
28
+        for c in d.capabilities():
29
+            COMMANDS.append('test.' + name + '.' + c)
30
+
31
+    def reduced_list(text):
32
+        """
33
+        Create reduced completation list
34
+        """
35
+        reduced_list = {}
36
+        for cmd in COMMANDS:
37
+            next_dot = cmd[len(text):].find('.')
38
+            if next_dot >= 0:
39
+                reduced_list[cmd[:len(text) + next_dot + 1]] = None
40
+            else:
41
+                reduced_list[cmd] = None
42
+        return reduced_list.keys()
43
+
44
+    def completer(text, state):
45
+        """
46
+        Our custom completer function
47
+        """
48
+        options = [x for x in reduced_list(text) if x.startswith(text)]
49
+        return options[state]
50
+
51
+    def complete(text, state):
52
+        for cmd in COMMANDS:
53
+            if cmd.startswith(text):
54
+                if not state:
55
+                    hit = ""
56
+                    index = 0
57
+                    sub_list = cmd.split('.')
58
+                    while len(text) >= len(hit):
59
+                        hit += sub_list[index] + '.'
60
+                    return hit  # cmd
61
+                else:
62
+                    state -= 1
63
+
64
+    readline.parse_and_bind("tab: complete")
65
+    readline.set_completer(completer)
66
+    time.sleep(0.3)
67
+    print("\nEnter command: ")
68
+
69
+    while True:
70
+        userfeedback = input('')
71
+        command = userfeedback.split(' ')[0]
72
+        if userfeedback == 'quit':
73
+            break
74
+        elif userfeedback == 'help':
75
+            print("Help is not yet implemented!")
76
+        elif userfeedback.startswith("test"):
77
+            ts.command(userfeedback)
78
+        elif userfeedback == 'test.smoke':
79
+            ts.smoke()
80
+        elif command in COMMANDS[2:]:
81
+            h.command(userfeedback)
82
+        elif userfeedback != "":
83
+            print("Unknown command!")
84
+        else:
85
+            print()

+ 1
- 0
task

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

+ 396
- 0
tests/__init__.py View File

@@ -0,0 +1,396 @@
1
+import colored
2
+import config
3
+import inspect
4
+import simulation.devices as devices
5
+import time
6
+
7
+
8
+DT_TOGGLE = 0.3
9
+
10
+
11
+TEST_FULL = 'full'
12
+TEST_SMOKE = 'smoke'
13
+#
14
+COLOR_SUCCESS = colored.fg("light_green")
15
+COLOR_FAIL = colored.fg("light_red")
16
+
17
+
18
+class test_smarthome(object):
19
+    def __init__(self, rooms):
20
+        # add testcases for switching devices
21
+        for name in rooms.getmembers():
22
+            obj = rooms.getobjbyname(name)
23
+            if obj.__class__.__name__ == "videv_light":
24
+                common_name = '.'.join(name.split('.')[:-1]) + '.' + name.split('.')[-1][6:]
25
+                try:
26
+                    li_device = rooms.getobjbyname(common_name + '_zigbee') if obj.enable_brightness or obj.enable_color_temp else None
27
+                except AttributeError:
28
+                    li_device = rooms.getobjbyname(common_name + '_zigbee_1') if obj.enable_brightness or obj.enable_color_temp else None
29
+                try:
30
+                    sw_device = rooms.getobjbyname(common_name) if obj.enable_state else None
31
+                except AttributeError:
32
+                    # must be a device without switching device
33
+                    sw_device = li_device
34
+                setattr(self, common_name.replace('.', '_'), testcase_light(obj, sw_device, li_device))
35
+        # add testcases for heating devices
36
+        for name in rooms.getmembers():
37
+            obj = rooms.getobjbyname(name)
38
+            if obj.__class__.__name__ == "videv_heating":
39
+                common_name = '.'.join(name.split('.')[:-1]) + '.' + name.split('.')[-1][6:]
40
+                heat_device = rooms.getobjbyname(common_name + '_valve')
41
+                setattr(self, common_name.replace('.', '_'), testcase_heating(obj, heat_device))
42
+        # synchronisation
43
+        self.gfw_dirk_cd_player_amplifier_sync = testcase_synchronisation(
44
+            rooms.gfw.dirk.cd_player, None, None,
45
+            rooms.gfw.dirk.amplifier)
46
+        self.gfw_floor_main_light_sync = testcase_synchronisation(
47
+            rooms.gfw.floor.main_light, rooms.gfw.floor.videv_main_light, rooms.gfw.floor.videv_main_light,
48
+            rooms.gfw.floor.main_light_zigbee_1, rooms.gfw.floor.main_light_zigbee_2
49
+        )
50
+        self.ffe_diningroom_main_light_floor_lamp_sync = testcase_synchronisation(
51
+            rooms.ffe.diningroom.main_light, None, None,
52
+            rooms.ffe.diningroom.floor_lamp)
53
+        self.ffe_livingroom_main_light_floor_lamp_sync = testcase_synchronisation(
54
+            rooms.ffe.livingroom.main_light, rooms.ffe.livingroom.videv_floor_lamp, rooms.ffe.livingroom.videv_floor_lamp,
55
+            *[getattr(rooms.ffe.livingroom, "floor_lamp_zigbee_%d" % i) for i in range(1, 7)]
56
+        )
57
+        # add test collection
58
+        self.all = test_collection(self)
59
+
60
+    def getmembers(self, prefix=''):
61
+        rv = []
62
+        for name, obj in inspect.getmembers(self):
63
+            if prefix:
64
+                full_name = prefix + '.' + name
65
+            else:
66
+                full_name = name
67
+            if not name.startswith('_'):
68
+                try:
69
+                    if obj.__class__.__bases__[0].__name__ == "testcase" or obj.__class__.__name__ == "test_collection":
70
+                        rv.append(full_name)
71
+                    else:
72
+                        rv.extend(obj.getmembers(full_name))
73
+                except AttributeError:
74
+                    pass
75
+        return rv
76
+
77
+    def getobjbyname(self, name):
78
+        if name.startswith("test."):
79
+            name = name[5:]
80
+        obj = self
81
+        for subname in name.split('.'):
82
+            obj = getattr(obj, subname)
83
+        return obj
84
+
85
+    def command(self, full_command):
86
+        try:
87
+            parameter = " " + full_command.split(' ')[1]
88
+        except IndexError:
89
+            parameter = ""
90
+        command = full_command.split(' ')[0].split('.')[-1] + parameter
91
+        device_name = '.'.join(full_command.split(' ')[0].split('.')[:-1])
92
+        self.getobjbyname(device_name).command(command)
93
+
94
+
95
+class test_result_base(object):
96
+    def __init__(self):
97
+        self.__init_test_counters__()
98
+
99
+    def __init_test_counters__(self):
100
+        self.test_counter = 0
101
+        self.success_tests = 0
102
+        self.failed_tests = 0
103
+
104
+    def statistic(self):
105
+        return (self.test_counter, self.success_tests, self.failed_tests)
106
+
107
+    def print_statistic(self):
108
+        color = COLOR_SUCCESS if self.test_counter == self.success_tests else COLOR_FAIL
109
+        print(color + "*** SUCCESS: (%4d/%4d)   FAIL: (%4d/%4d) ***\n" % (self.success_tests,
110
+              self.test_counter, self.failed_tests, self.test_counter) + colored.attr("reset"))
111
+
112
+
113
+class test_collection(test_result_base):
114
+    def __init__(self, test_instance):
115
+        super().__init__()
116
+        self.test_instance = test_instance
117
+
118
+    def capabilities(self):
119
+        return [TEST_FULL, TEST_SMOKE]
120
+
121
+    def command(self, command):
122
+        self.__init_test_counters__()
123
+        for member in self.test_instance.getmembers():
124
+            obj = self.test_instance.getobjbyname(member)
125
+            if id(obj) != id(self):
126
+                obj.test_all(command)
127
+                num, suc, fail = obj.statistic()
128
+                self.test_counter += num
129
+                self.success_tests += suc
130
+                self.failed_tests += fail
131
+        self.print_statistic()
132
+
133
+
134
+class testcase(test_result_base):
135
+    def __init__(self):
136
+        super().__init__()
137
+        self.__test_list__ = []
138
+
139
+    def capabilities(self):
140
+        if len(self.__test_list__) > 0 and not 'test_all' in self.__test_list__:
141
+            self.__test_list__.append('test_all')
142
+        self.__test_list__.sort()
143
+        return self.__test_list__
144
+
145
+    def test_all(self, test=TEST_FULL):
146
+        test_counter = 0
147
+        success_tests = 0
148
+        failed_tests = 0
149
+        for tc_name in self.capabilities():
150
+            if tc_name != "test_all":
151
+                self.command(tc_name, test)
152
+                test_counter += self.test_counter
153
+                success_tests += self.success_tests
154
+                failed_tests += self.failed_tests
155
+        self.test_counter = test_counter
156
+        self.success_tests = success_tests
157
+        self.failed_tests = failed_tests
158
+
159
+    def command(self, command, test=TEST_FULL):
160
+        self.__init_test_counters__()
161
+        tc = getattr(self, command)
162
+        self.__init_test_counters__()
163
+        tc(test)
164
+        self.print_statistic()
165
+
166
+    def heading(self, desciption):
167
+        print(desciption)
168
+
169
+    def sub_heading(self, desciption):
170
+        print(2 * " " + desciption)
171
+
172
+    def result(self, desciption, success):
173
+        self.test_counter += 1
174
+        if success:
175
+            self.success_tests += 1
176
+        else:
177
+            self.failed_tests += 1
178
+        print(4 * " " + ("SUCCESS - " if success else "FAIL    - ") + desciption)
179
+
180
+
181
+class testcase_light(testcase):
182
+    def __init__(self, videv, sw_device, li_device):
183
+        self.videv = videv
184
+        self.sw_device = sw_device
185
+        self.li_device = li_device
186
+        self.__test_list__ = []
187
+        if self.videv.enable_state:
188
+            self.__test_list__.append('test_power_on_off')
189
+        if self.videv.enable_brightness:
190
+            self.__test_list__.append('test_brightness')
191
+        if self.videv.enable_color_temp:
192
+            self.__test_list__.append('test_color_temp')
193
+
194
+    def test_power_on_off(self, test=TEST_FULL):
195
+        self.heading("Power On/ Off test (%s)" % self.videv.topic)
196
+        #
197
+        sw_state = self.sw_device.get(self.sw_device.KEY_OUTPUT_0)
198
+        #
199
+        for i in range(0, 2):
200
+            self.sub_heading("State change of switching device")
201
+            #
202
+            self.sw_device.set(self.sw_device.KEY_OUTPUT_0, not self.sw_device.get(self.sw_device.KEY_OUTPUT_0))
203
+            time.sleep(DT_TOGGLE)
204
+            self.result("Virtual device state after Switch on by switching device", sw_state != self.videv.get(self.videv.KEY_OUTPUT_0))
205
+            self.result("Switching device state after Switch on by switching device",
206
+                        sw_state != self.sw_device.get(self.sw_device.KEY_OUTPUT_0))
207
+
208
+            self.sub_heading("State change of virtual device")
209
+            #
210
+            self.videv.set(self.videv.KEY_OUTPUT_0, not self.videv.get(self.videv.KEY_OUTPUT_0))
211
+            time.sleep(DT_TOGGLE)
212
+            self.result("Virtual device state after Switch off by virtual device", sw_state == self.videv.get(self.videv.KEY_OUTPUT_0))
213
+            self.result("Switching device state after Switch on by switching device",
214
+                        sw_state == self.sw_device.get(self.sw_device.KEY_OUTPUT_0))
215
+
216
+    def test_brightness(self, test=TEST_FULL):
217
+        self.heading("Brightness test (%s)" % self.videv.topic)
218
+        #
219
+        br_state = self.li_device.get(self.li_device.KEY_BRIGHTNESS)
220
+        delta = -15 if br_state > 50 else 15
221
+
222
+        self.sw_device.set(self.sw_device.KEY_OUTPUT_0, True)
223
+        time.sleep(DT_TOGGLE)
224
+
225
+        for i in range(0, 2):
226
+            self.sub_heading("Brightness change by light device")
227
+            #
228
+            self.li_device.set(self.li_device.KEY_BRIGHTNESS, br_state + delta)
229
+            time.sleep(DT_TOGGLE)
230
+            self.result("Virtual device state after setting brightness by light device",
231
+                        br_state + delta == self.videv.get(self.videv.KEY_BRIGHTNESS))
232
+            self.result("Light device state after setting brightness by light device", br_state +
233
+                        delta == self.li_device.get(self.li_device.KEY_BRIGHTNESS))
234
+
235
+            self.sub_heading("Brightness change by virtual device")
236
+            #
237
+            self.videv.set(self.videv.KEY_BRIGHTNESS, br_state)
238
+            time.sleep(DT_TOGGLE)
239
+            self.result("Virtual device state after setting brightness by light device", br_state == self.videv.get(self.videv.KEY_BRIGHTNESS))
240
+            self.result("Light device state after setting brightness by light device",
241
+                        br_state == self.li_device.get(self.li_device.KEY_BRIGHTNESS))
242
+
243
+        self.sw_device.set(self.sw_device.KEY_OUTPUT_0, False)
244
+        time.sleep(DT_TOGGLE)
245
+
246
+    def test_color_temp(self, test=TEST_FULL):
247
+        self.heading("Color temperature test (%s)" % self.videv.topic)
248
+        #
249
+        ct_state = self.li_device.get(self.li_device.KEY_COLOR_TEMP)
250
+        delta = -3 if ct_state > 5 else 3
251
+
252
+        self.sw_device.set(self.sw_device.KEY_OUTPUT_0, True)
253
+        time.sleep(DT_TOGGLE)
254
+
255
+        for i in range(0, 2):
256
+            self.sub_heading("Color temperature change by light device")
257
+            #
258
+            self.li_device.set(self.li_device.KEY_COLOR_TEMP, ct_state + delta)
259
+            time.sleep(DT_TOGGLE)
260
+            self.result("Virtual device state after setting color temperature by light device",
261
+                        ct_state + delta == self.videv.get(self.videv.KEY_COLOR_TEMP))
262
+            self.result("Light device state after setting color temperature by light device", ct_state +
263
+                        delta == self.li_device.get(self.li_device.KEY_COLOR_TEMP))
264
+
265
+            self.sub_heading("Color temperature change by virtual device")
266
+            #
267
+            self.videv.set(self.videv.KEY_COLOR_TEMP, ct_state)
268
+            time.sleep(DT_TOGGLE)
269
+            self.result("Virtual device state after setting color temperature by light device",
270
+                        ct_state == self.videv.get(self.videv.KEY_COLOR_TEMP))
271
+            self.result("Light device state after setting color temperature by light device",
272
+                        ct_state == self.li_device.get(self.li_device.KEY_COLOR_TEMP))
273
+
274
+        self.sw_device.set(self.sw_device.KEY_OUTPUT_0, False)
275
+        time.sleep(DT_TOGGLE)
276
+
277
+
278
+class testcase_synchronisation(testcase):
279
+    def __init__(self, sw_master, br_master, ct_master, *follower):
280
+        super().__init__()
281
+        self.sw_master = sw_master
282
+        self.br_master = br_master
283
+        self.ct_master = ct_master
284
+        self.follower = follower
285
+        self.__test_list__ = []
286
+        if sw_master is not None:
287
+            self.__test_list__.append('test_power_on_off_sync')
288
+        if br_master is not None:
289
+            self.__test_list__.append('test_brightness_sync')
290
+
291
+    def test_power_on_off_sync(self, test=TEST_FULL):
292
+        self.heading("Power On/ Off sychronisation test")
293
+        #
294
+        # set sw_master to slave state as precondition
295
+        f_state = self.follower[0].get(self.follower[0].KEY_OUTPUT_0)
296
+        self.sw_master.set(self.sw_master.KEY_OUTPUT_0, f_state)
297
+        time.sleep(DT_TOGGLE)
298
+        for i in range(0, 2):
299
+            self.sub_heading("State change of sw_master device")
300
+            #
301
+            self.sw_master.set(self.sw_master.KEY_OUTPUT_0, not f_state)
302
+            time.sleep(DT_TOGGLE)
303
+            f_state = not f_state
304
+            for fo in self.follower:
305
+                self.result("Follower device state after switching (%s)" % fo.topic,
306
+                            f_state == fo.get(fo.KEY_OUTPUT_0))
307
+
308
+    def test_brightness_sync(self, test=TEST_FULL):
309
+        self.heading("Power On/ Off sychronisation test")
310
+        #
311
+        # set precondition
312
+        sw_state = self.sw_master.get(self.sw_master.KEY_OUTPUT_0)
313
+        self.sw_master.set(self.sw_master.KEY_OUTPUT_0, True)
314
+        time.sleep(DT_TOGGLE)
315
+
316
+        target = self.follower[0].get(self.follower[0].KEY_BRIGHTNESS)
317
+        delta = 15 if target < 50 else -15
318
+        for i in range(0, 2):
319
+            target = target + delta
320
+            self.sub_heading("State change of br_master device")
321
+            #
322
+            self.br_master.set(self.br_master.KEY_BRIGHTNESS, target)
323
+            time.sleep(DT_TOGGLE)
324
+            for fo in self.follower:
325
+                self.result("Follower device brightness after change (%s)" % fo.topic,
326
+                            target == fo.get(fo.KEY_BRIGHTNESS))
327
+            delta = -delta
328
+
329
+        # reset changes of precondition
330
+        self.sw_master.set(self.sw_master.KEY_OUTPUT_0, sw_state)
331
+        time.sleep(DT_TOGGLE)
332
+
333
+
334
+class testcase_heating(testcase):
335
+    def __init__(self, videv, valve):
336
+        self.__videv__ = videv
337
+        self.__valve__ = valve
338
+        self.__default_temperature__ = config.DEFAULT_TEMPERATURE.get(self.__valve__.topic)
339
+        self.__test_list__ = ["test_user_temperature_setpoint", "test_default_temperature", ]
340
+
341
+    def test_user_temperature_setpoint(self, test=TEST_FULL):
342
+        self.heading("User temperature setpoint test (%s)" % self.__videv__.topic)
343
+        #
344
+        mtemp = round(self.__valve__.TEMP_RANGE[0] + (self.__valve__.TEMP_RANGE[1] - self.__valve__.TEMP_RANGE[0]) / 2, 1)
345
+        setp = self.__valve__.get(self.__valve__.KEY_TEMPERATURE_SETPOINT)
346
+        delta = 5 if setp < mtemp else -5
347
+
348
+        for i in range(0, 2):
349
+            self.sub_heading("Setpoint change by valve device")
350
+            #
351
+            self.__valve__.set(self.__valve__.KEY_TEMPERATURE_SETPOINT, setp + delta)
352
+            time.sleep(DT_TOGGLE)
353
+            self.result("Virtual device valve temperature after setting temperature by valve device",
354
+                        setp + delta == self.__videv__.get(self.__videv__.KEY_VALVE_TEMPERATURE_SETPOINT))
355
+            self.result("Virtual device user temperature after setting temperature by valve device",
356
+                        setp + delta == self.__videv__.get(self.__videv__.KEY_USER_TEMPERATURE_SETPOINT))
357
+
358
+            self.sub_heading("Setpoint change by videv device")
359
+            #
360
+            self.__videv__.set(self.__videv__.KEY_USER_TEMPERATURE_SETPOINT, setp)
361
+            time.sleep(DT_TOGGLE)
362
+            self.result("Valve device temperature setpoint after setting temperature by videv device",
363
+                        setp == self.__valve__.get(self.__valve__.KEY_TEMPERATURE_SETPOINT))
364
+            self.result("Virtual device valve temperature after setting temperature by videv device",
365
+                        setp == self.__videv__.get(self.__videv__.KEY_VALVE_TEMPERATURE_SETPOINT))
366
+
367
+    def test_default_temperature(self, test=TEST_FULL):
368
+        self.heading("Default temperature setpoint test (%s)" % self.__videv__.topic)
369
+        #
370
+        dtemp = config.DEFAULT_TEMPERATURE.get(self.__valve__.topic)
371
+        mtemp = round(self.__valve__.TEMP_RANGE[0] + (self.__valve__.TEMP_RANGE[1] - self.__valve__.TEMP_RANGE[0]) / 2, 1)
372
+        ptemp = dtemp + (5 if dtemp < mtemp else -5)
373
+
374
+        self.sub_heading("Setting setpoint to a value unequal default as precondition")
375
+        self.__valve__.set(self.__valve__.KEY_TEMPERATURE_SETPOINT, ptemp)
376
+        time.sleep(DT_TOGGLE)
377
+        self.result("Valve device temperature setpoint after setting precondition temperature",
378
+                    ptemp == self.__valve__.get(self.__valve__.KEY_TEMPERATURE_SETPOINT))
379
+
380
+        self.sub_heading("Triggering default temperature by videv device")
381
+        self.__videv__.set(self.__videv__.KEY_SET_DEFAULT_TEMPERATURE, None)
382
+        time.sleep(DT_TOGGLE)
383
+        self.result("Valve device temperature setpoint after setting default temperature",
384
+                    dtemp == self.__valve__.get(self.__valve__.KEY_TEMPERATURE_SETPOINT))
385
+
386
+    def test_summer_mode(self, test=TEST_FULL):
387
+        pass
388
+
389
+    def test_away_mode(self, test=TEST_FULL):
390
+        pass
391
+
392
+    def test_boost_mode(self, test=TEST_FULL):
393
+        pass
394
+        if test == TEST_FULL:
395
+            # test boost timer
396
+            pass

Loading…
Cancel
Save