瀏覽代碼

First basic home emulation

master
Dirk Alders 1 年之前
父節點
當前提交
0459c0d2d2
共有 18 個文件被更改,包括 454 次插入0 次删除
  1. 4
    0
      .gitignore
  2. 9
    0
      .gitmodules
  3. 16
    0
      .vscode/launch.json
  4. 15
    0
      .vscode/settings.json
  5. 1
    0
      config.py
  6. 23
    0
      devices/__init__.py
  7. 29
    0
      devices/base.py
  8. 6
    0
      devices/livarno.py
  9. 69
    0
      devices/shelly.py
  10. 116
    0
      devices/tradfri.py
  11. 42
    0
      docker_config/Makefile
  12. 21
    0
      docker_config/docker-compose.yml
  13. 二進制
      docker_config/mqtt.tgz
  14. 二進制
      docker_config/nodered.tgz
  15. 1
    0
      geo
  16. 100
    0
      home_emulation.py
  17. 1
    0
      mqtt
  18. 1
    0
      report

+ 4
- 0
.gitignore 查看文件

@@ -1,3 +1,7 @@
1
+# Docker data
2
+docker_config/nodered
3
+docker_config/mqtt
4
+
1 5
 # ---> Linux
2 6
 *~
3 7
 

+ 9
- 0
.gitmodules 查看文件

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

+ 16
- 0
.vscode/launch.json 查看文件

@@ -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}/home_emulation.py",
12
+            "console": "integratedTerminal",
13
+            "justMyCode": true
14
+        }
15
+    ]
16
+}

+ 15
- 0
.vscode/settings.json 查看文件

@@ -0,0 +1,15 @@
1
+{
2
+  "python.defaultInterpreterPath": "./venv/bin/python",
3
+  "autopep8.args": ["--max-line-length=150"],
4
+  "python.formatting.provider": "none",
5
+  "[python]": {
6
+    "editor.defaultFormatter": "ms-python.python",
7
+    "editor.formatOnSave": true
8
+  },
9
+  "editor.formatOnSave": true,
10
+  "editor.fontSize": 14,
11
+  "emmet.includeLanguages": { "django-html": "html" },
12
+  "python.testing.pytestArgs": ["-v", "--cov", "--cov-report=xml", "__test__"],
13
+  "python.testing.unittestEnabled": false,
14
+  "python.testing.pytestEnabled": true
15
+}

+ 1
- 0
config.py 查看文件

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

+ 23
- 0
devices/__init__.py 查看文件

@@ -0,0 +1,23 @@
1
+import devices.base
2
+import devices.livarno
3
+import devices.shelly
4
+import devices.tradfri
5
+
6
+
7
+class null(devices.base.base):
8
+    """A dummy device for not yet existing devicetypes
9
+
10
+    Args:
11
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
12
+        topic (str): the base topic for this device
13
+    """
14
+    def __init__(self, mqtt_client, topic, **kwargs):
15
+        super().__init__(mqtt_client, topic, **kwargs)
16
+        self.mqtt_client.add_callback(self.topic, self.__rx__)
17
+        self.mqtt_client.add_callback(self.topic + '/#', self.__rx__)
18
+
19
+    def __rx__(self, client, userdata, message):
20
+        self.logger.warning("Got messaqge for device with missing implementation: topic=%s, payload=%s", message.topic, repr(message.payload))
21
+
22
+    def set_state(self, value):
23
+        self.logger.warning("Got set_state call for device with missing implementation.")

+ 29
- 0
devices/base.py 查看文件

@@ -0,0 +1,29 @@
1
+import logging
2
+
3
+
4
+class base(dict):
5
+    """A base device for all devicetypes
6
+
7
+    Args:
8
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
9
+        topic (str): the base topic for this device
10
+    """
11
+    PROPERTIES = []
12
+
13
+    def __init__(self, mqtt_client, topic, **kwargs):
14
+        super().__init__()
15
+        self.mqtt_client = mqtt_client
16
+        self.topic = topic
17
+        for key in kwargs:
18
+            setattr(self, key, kwargs[key])
19
+        #
20
+        self.logger = logging.getLogger('devices')
21
+        for entry in self.topic.split('/'):
22
+            self.logger = self.logger.getChild(entry)
23
+
24
+    def __set__(self, key, data):
25
+        if key in self.PROPERTIES:
26
+            self.logger.debug("Setting new property %s to %s", key, repr(data))
27
+            self[key] = data
28
+        else:
29
+            self.logger.warning("Ignoring unsupported property %s", key)

+ 6
- 0
devices/livarno.py 查看文件

@@ -0,0 +1,6 @@
1
+import devices.tradfri
2
+
3
+
4
+class sw_br_ct(devices.tradfri.sw_br_ct):
5
+    def set_state(self, value):
6
+        self.__set__("state", "on" if value else "off")

+ 69
- 0
devices/shelly.py 查看文件

@@ -0,0 +1,69 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+#
4
+""" Communication (MQTT)
5
+    shelly
6
+        +- relay
7
+        |      +- 0 ["on" / "off"]              <- status 
8
+        |      |  +- command ["on"/ "off"]      <- command
9
+        |      |  +- energy [numeric]           <- status
10
+        |      +- 1 ["on" / "off"]              <- status
11
+        |         +- command ["on"/ "off"]      <- command
12
+        |         +- energy [numeric]           <- status
13
+        +- input
14
+        |      +- 0 [0 / 1]                     <- status
15
+        |      +- 1 [0 / 1]                     <- status
16
+        +- input_event
17
+        |      +- 0                             <- status
18
+        |      +- 1                             <- status
19
+        +- logpush
20
+        |      +- 0 [0 / 1]                     <- status
21
+        |      +- 1 [0 / 1]                     <- status
22
+        +- temperature [numeric] °C             <- status
23
+        +- temperature_f [numeric] F            <- status
24
+        +- overtemperature [0 / 1]              <- status
25
+        +- id                                   <- status
26
+        +- model                                <- status
27
+        +- mac                                  <- status
28
+        +- ip                                   <- status
29
+        +- new_fw                               <- status
30
+        +- fw_ver                               <- status
31
+"""
32
+
33
+from devices.base import base
34
+import json
35
+
36
+
37
+class sw_plain(base):
38
+    """A shelly device with switching functionality
39
+
40
+    Args:
41
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
42
+        topic (str): the base topic for this device
43
+        kwargs (**dict): cd_r0=list of devices connected to relay/0
44
+    """
45
+    PROPERTIES = [
46
+        "relay/0",
47
+    ]
48
+    def __init__(self, mqtt_client, topic, **kwargs):
49
+        super().__init__(mqtt_client, topic, **kwargs)
50
+        if getattr(self, 'cd_r0', None) is None:
51
+            self.cd_r0 = []
52
+        self["state"] = "off"
53
+        #
54
+        self.mqtt_client.add_callback(self.topic + '/relay/0/command', self.__rx_set__)
55
+
56
+    def __rx_set__(self, client, userdata, message):
57
+        data = message.payload.decode('utf-8')
58
+        key = message.topic.split('/')[-3] + '/' + message.topic.split('/')[-2]
59
+        self.logger.info("Received set data for %s: %s", key, repr(data))
60
+        self.__set__(key, data)
61
+        self.send_device_status(key)
62
+        if key == "relay/0":
63
+            for d in self.cd_r0:
64
+                d.set_state(data.lower() == "on")
65
+
66
+    def send_device_status(self, key):
67
+        data = self[key]
68
+        self.logger.info("Sending status for %s: %s", key, repr(data))
69
+        self.mqtt_client.send(self.topic + '/' + key, data)

+ 116
- 0
devices/tradfri.py 查看文件

@@ -0,0 +1,116 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+#
4
+"""
5
+tradfri devices
6
+===============
7
+
8
+**Author:**
9
+
10
+* Dirk Alders <sudo-dirk@mount-mockery.de>
11
+
12
+**Description:**
13
+
14
+    Emulation of tradfri devices
15
+
16
+    Communication (MQTT)
17
+
18
+        tradfri_light {
19
+                    |      "state": ["ON" / "OFF" / "TOGGLE"]
20
+                    |      "linkquality": [0...255] lqi
21
+                    |      "brightness": [0...254]
22
+                    |      "color_mode": ["color_temp"]
23
+                    |      "color_temp": ["coolest", "cool", "neutral", "warm", "warmest", 250...454]
24
+                    |      "color_temp_startup": ["coolest", "cool", "neutral", "warm", "warmest", "previous", 250...454]
25
+                    |      "update": []
26
+                    | }
27
+                    +- get {
28
+                    |           "state": ""
29
+                    |      }
30
+                    +- set {
31
+                                "state": ["ON" / "OFF"]
32
+                                "brightness": [0...256]
33
+                                "color_temp": [250...454]
34
+                                "transition": [0...] seconds
35
+                                "brightness_move": [-X...0...X] X/s
36
+                                "brightness_step": [-X...0...X]
37
+                                "color_temp_move": [-X...0...X] X/s
38
+                                "color_temp_step": [-X...0...X]
39
+                            }
40
+"""
41
+
42
+from devices.base import base
43
+import json
44
+
45
+
46
+class sw(base):
47
+    """A tradfri device with switching functionality
48
+
49
+    Args:
50
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
51
+        topic (str): the base topic for this device
52
+        kwargs (**dict): cd_st=list of devices connected to state
53
+    """
54
+    PROPERTIES = [
55
+        "state",
56
+    ]
57
+    def __init__(self, mqtt_client, topic, **kwargs):
58
+        super().__init__(mqtt_client, topic, **kwargs)
59
+        if getattr(self, 'cd_st', None) is None:
60
+            self.cd_st = []
61
+        self["state"] = "off"
62
+        #
63
+        self.mqtt_client.add_callback(self.topic + '/set', self.__rx_set__)
64
+        self.mqtt_client.add_callback(self.topic + '/get', self.__rx_get__)
65
+
66
+    def set_state(self, value):
67
+        self.__set__("state", "on" if value else "off")
68
+        self.send_device_status()
69
+
70
+    def __rx_set__(self, client, userdata, message):
71
+        data = json.loads(message.payload)
72
+        self.logger.info("Received set data: %s", repr(data))
73
+        for key in data:
74
+            self.__set__(key, data[key])
75
+        self.send_device_status()
76
+        if "state" in data:
77
+            for d in self.cd_st:
78
+                d.set_state(data["state"].lower() == "on")
79
+
80
+    def __rx_get__(self, client, userdata, message):
81
+        self.send_device_status()
82
+
83
+    def send_device_status(self):
84
+        data = json.dumps(self)
85
+        self.logger.info("Sending status: %s", repr(data))
86
+        self.mqtt_client.send(self.topic, data)
87
+
88
+
89
+class sw_br(sw):
90
+    """A tradfri device with switching and brightness functionality
91
+
92
+    Args:
93
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
94
+        topic (str): the base topic for this device
95
+    """
96
+    PROPERTIES = sw.PROPERTIES + [
97
+        "brightness",
98
+    ]
99
+    def __init__(self, mqtt_client, topic, **kwargs):
100
+        super().__init__(mqtt_client, topic, **kwargs)
101
+        self["brightness"] = 64
102
+
103
+
104
+class sw_br_ct(sw_br):
105
+    """A tradfri device with switching, brightness and colortemp functionality
106
+
107
+    Args:
108
+        mqtt_client (mqtt.mqtt_client): A MQTT Client instance
109
+        topic (str): the base topic for this device
110
+    """
111
+    PROPERTIES = sw_br.PROPERTIES + [
112
+        "color_temp",
113
+    ]
114
+    def __init__(self, mqtt_client, topic, **kwargs):
115
+        super().__init__(mqtt_client, topic, **kwargs)
116
+        self["color_temp"] = 413

+ 42
- 0
docker_config/Makefile 查看文件

@@ -0,0 +1,42 @@
1
+DOCKER_COMP = sudo docker-compose
2
+
3
+help:
4
+	echo Help is not yet implemented...
5
+
6
+nodered:
7
+	if [ ! -d nodered ]; then tar -xvzf nodered.tgz; fi
8
+mqtt:
9
+	if [ ! -d mqtt ]; then tar -xvzf mqtt.tgz; fi
10
+build: mqtt nodered
11
+	$(DOCKER_COMP) build
12
+
13
+up: mqtt nodered  ## Starts the docker hub
14
+	$(DOCKER_COMP) up
15
+
16
+up_%: mqtt nodered  ## Start a single service
17
+	$(DOCKER_COMP) up $(subst up_,,$@)
18
+
19
+down: ## Stops the docker hub
20
+	$(DOCKER_COMP) down --remove-orphans
21
+
22
+restart: ## Restarts the docker hub
23
+	$(DOCKER_COMP) restart
24
+
25
+status: ## Prompt Containers
26
+	$(DOCKER_COMP) ps
27
+
28
+sh_%: ## Connects to the application container
29
+	$(DOCKER_COMP) exec $(subst sh_,,$@) sh
30
+
31
+bash_%: ## Connects to the application container
32
+	$(DOCKER_COMP) exec $(subst bash_,,$@) bash
33
+
34
+logs: ## Displays the logs of the application container
35
+	$(DOCKER_COMP) logs -f
36
+
37
+logs_%: ## Displays the logs of the application container
38
+	$(DOCKER_COMP) logs -f $(subst logs_,,$@)
39
+
40
+cleanall:
41
+	rm -rf mqtt nodered
42
+	sudo docker system prune -a

+ 21
- 0
docker_config/docker-compose.yml 查看文件

@@ -0,0 +1,21 @@
1
+version: '3'
2
+services:
3
+
4
+  my_nodered:
5
+    restart: always
6
+    image: nodered/node-red:latest
7
+    container_name: nodered
8
+    depends_on:
9
+      - mqtt
10
+    network_mode: host
11
+    volumes:
12
+      - ./nodered:/data
13
+
14
+  mqtt:
15
+    build: mqtt
16
+    restart: always
17
+    ports:
18
+      - "1883:1883"
19
+      - "9001:9001"
20
+    volumes:
21
+      - ./mqtt:/mosquitto

二進制
docker_config/mqtt.tgz 查看文件


二進制
docker_config/nodered.tgz 查看文件


+ 1
- 0
geo

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

+ 100
- 0
home_emulation.py 查看文件

@@ -0,0 +1,100 @@
1
+import config
2
+import devices
3
+import logging
4
+import mqtt
5
+import report
6
+import time
7
+
8
+
9
+class device_creator(dict):
10
+    def __init__(self, mqtt_client):
11
+        self.mqtt_client = mqtt_client
12
+        #
13
+        # ground floor west
14
+        # floor
15
+        l1 = self.add_device(devices.livarno.sw_br_ct, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_ZIGBEE % 1)
16
+        l2 = self.add_device(devices.livarno.sw_br_ct, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_ZIGBEE % 2)
17
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_GFW_FLOOR_MAIN_LIGHT_SHELLY, cd_r0=[l1, l2])
18
+
19
+        # marion
20
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_GFW_MARION_MAIN_LIGHT_SHELLY)
21
+        self.add_device(devices.null, config.TOPIC_GFW_MARION_HEATING_VALVE_ZIGBEE)
22
+
23
+        # dirk
24
+        l = self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_GFW_DIRK_MAIN_LIGHT_ZIGBEE)
25
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_GFW_DIRK_MAIN_LIGHT_SHELLY, cd_r0=[l])
26
+        self.add_device(devices.null, config.TOPIC_GFW_DIRK_INPUT_DEVICE)
27
+        self.add_device(devices.null, config.TOPIC_GFW_DIRK_POWERPLUG)
28
+        self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_GFW_DIRK_DESK_LIGHT_ZIGBEE)
29
+        self.add_device(devices.null, config.TOPIC_GFW_DIRK_HEATING_VALVE_ZIGBEE)
30
+
31
+        # first floor west
32
+        # julian
33
+        l = self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_ZIGBEE)
34
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFW_JULIAN_MAIN_LIGHT_SHELLY, cd_r0=[l])
35
+
36
+        # bath
37
+        self.add_device(devices.null, config.TOPIC_FFW_BATH_HEATING_VALVE_ZIGBEE)
38
+
39
+        # livingroom
40
+        l = self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_ZIGBEE)
41
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFW_LIVINGROOM_MAIN_LIGHT_SHELLY, cd_r0=[l])
42
+
43
+        # sleep
44
+        l = self.add_device(devices.tradfri.sw_br, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_ZIGBEE)
45
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFW_SLEEP_MAIN_LIGHT_SHELLY, cd_r0=[l])
46
+
47
+
48
+        # first floor east
49
+        # floor
50
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFE_FLOOR_MAIN_LIGHT_SHELLY)
51
+
52
+        # kitchen
53
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFE_KITCHEN_MAIN_LIGHT_SHELLY)
54
+        self.add_device(devices.null, config.TOPIC_FFE_KITCHEN_CIRCULATION_PUMP_SHELLY)
55
+
56
+        # diningroom
57
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFE_DININGROOM_MAIN_LIGHT_SHELLY)
58
+        self.add_device(devices.null, config.TOPIC_FFE_DININGROOM_FLOOR_LAMP_POWERPLUG)
59
+        self.add_device(devices.null, config.TOPIC_FFE_DININGROOM_GARLAND_POWERPLUG)
60
+
61
+        # sleep
62
+        l = self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_ZIGBEE)
63
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFE_SLEEP_MAIN_LIGHT_SHELLY, cd_r0=[l])
64
+        self.add_device(devices.null, config.TOPIC_FFE_SLEEP_INPUT_DEVICE)
65
+        self.add_device(devices.tradfri.sw_br, config.TOPIC_FFE_SLEEP_BED_LIGHT_DI_ZIGBEE)
66
+        self.add_device(devices.null, config.TOPIC_FFE_SLEEP_BED_LIGHT_MA_POWERPLUG)
67
+        self.add_device(devices.null, config.TOPIC_FFE_SLEEP_HEATING_VALVE_ZIGBEE)
68
+
69
+        # livingroom
70
+        l = self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_ZIGBEE)
71
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_FFE_LIVINGROOM_MAIN_LIGHT_SHELLY, cd_r0=[l])
72
+        for i in range(1,7):
73
+            self.add_device(devices.tradfri.sw_br_ct, config.TOPIC_FFE_LIVINGROOM_FLOOR_LAMP_ZIGBEE % i)
74
+        self.add_device(devices.null, config.TOPIC_FFE_LIVINGROOM_XMAS_TREE_POWERPLUG)
75
+        self.add_device(devices.null, config.TOPIC_FFE_LIVINGROOM_XMAS_STAR_POWERPLUG)
76
+
77
+
78
+        # first floor east
79
+        # floor
80
+        self.add_device(devices.shelly.sw_plain, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_SHELLY)
81
+        self.add_device(devices.null, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_FF)
82
+        self.add_device(devices.null, config.TOPIC_STW_STAIRWAY_MAIN_LIGHT_MOTION_SENSOR_GF)
83
+
84
+    def add_device(self, deviceclass, topic, **kwargs):
85
+        self[topic] = deviceclass(self.mqtt_client, topic, **kwargs)
86
+        return self[topic]
87
+
88
+if __name__ == "__main__":
89
+    report.stdoutLoggingConfigure((
90
+        (config.APP_NAME, logging.DEBUG),
91
+        ('devices', logging.DEBUG),
92
+    ), report.SHORT_FMT)
93
+    
94
+    mc = mqtt.mqtt_client(host=config.MQTT_SERVER, port=config.MQTT_PORT, username=config.MQTT_USER,
95
+                          password=config.MQTT_PASSWORD, name='home_emulation')
96
+
97
+    device_dict = device_creator(mc)
98
+
99
+    while (True):
100
+        time.sleep(1)

+ 1
- 0
mqtt

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

+ 1
- 0
report

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

Loading…
取消
儲存