Skip to content

Commit e016f8a

Browse files
authored
Merge pull request #16 from Enapter/zigbee2mqtt_example
Add Zigbee2Mqtt integration example
2 parents d825c9b + 5611011 commit e016f8a

File tree

8 files changed

+319
-0
lines changed

8 files changed

+319
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.10-alpine3.16
2+
3+
WORKDIR /app
4+
5+
RUN apk add build-base
6+
7+
RUN python -m venv .venv
8+
COPY requirements.txt requirements.txt
9+
RUN .venv/bin/pip install -r requirements.txt
10+
11+
COPY script.py script.py
12+
13+
CMD [".venv/bin/python", "script.py"]
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Zigbee Sensor (MQTT)
2+
3+
This example describes the implementation of the [Standalone UCM](https://handbook.enapter.com/software/virtual_ucm/) concept using the opensource [Enapter python-sdk](https://github.com/Enapter/python-sdk) for monitoring Zigbee Sensor via MQTT protocol (Zigbee2Mqtt).
4+
5+
In order to use this UCM you need to have [Zigbee2MQTT](https://www.zigbee2mqtt.io/guide/installation/) and some MQTT broker (for example [Mosquitto](https://mosquitto.org)) running.
6+
7+
As an example in this guide we will use the following dummy settings for configuration:
8+
9+
MQTT Broker Address: 192.168.192.190
10+
11+
MQTT Broker Port: 9883
12+
13+
MQTT User: mqtt_user
14+
15+
MQTT Password: mqtt_password
16+
17+
Device MQTT topic: zigbee2mqtt/MyDevice
18+
19+
## Requirements
20+
21+
It is recommended to run this UCM using Docker and Docker Compose. This will ensure that environment is correct.
22+
23+
The MQTT broker must be reachable from the computer where the Docker Container will be running.
24+
25+
## Step 1. Create Standalone UCM in Enapter Cloud
26+
27+
Log in to the Enapter Cloud, navigate to the Site where you want to create Standalone UCM and click on `Add new` button in the Standalone Device section.
28+
29+
After creating Standalone UCM, you need to Generate and save Configuration string also known as ENAPTER_VUCM_BLOB as well as save UCM ID which will be needed for the next step
30+
31+
More information you can find on [this page](https://developers.enapter.com/docs/tutorial/software-ucms/standalone).
32+
33+
## Step 2. Upload Blueprint into the Cloud
34+
35+
The general case [Enapter Blueprint](https://marketplace.enapter.com/about) consists of two files - declaration in YAML format (manifest.yaml) and logic written in Lua. Howerver for this case the logic is written in Python as Lua implementation doesn't have SNMP integration.
36+
37+
But for both cases we need to tell Enapter Cloud which telemetry we are going to send and store and how to name it.
38+
39+
The easiest way to do that - using [Enapter CLI](https://github.com/Enapter/enapter-cli) to upload manifest.yaml into Cloud. The other option is to use [Web IDE](https://developers.enapter.com/docs/tutorial/uploading-blueprint).
40+
41+
```bash
42+
user@pc zigbee2mqtt % enapter devices upload --blueprint-dir . --hardware-id REAL_UCM_ID
43+
upload started with operation id 25721
44+
[#25721] 2023-07-20T16:27:33Z [INFO] Started uploading blueprint[id=dcb05efe-1618-4b01-877b-6105960690bc] on device[hardware_id=REAL_UCM_ID]
45+
[#25721] 2023-07-20T16:27:33Z [INFO] Generating configuration for uploading
46+
[#25721] 2023-07-20T16:27:33Z [INFO] Updating configuration in the cloud platform
47+
[#25721] 2023-07-20T16:27:33Z [INFO] Updating configuration on the gateway
48+
[#25721] 2023-07-20T16:27:35Z [INFO] Uploading blueprint finished successfully
49+
Done!
50+
```
51+
52+
## Step 3. Configuring Standalone UCM
53+
54+
Open `docker-compose.yaml` in any editor.
55+
56+
Set environment variables according to your configuration settings. With dummy settings your file will look like this:
57+
58+
```yaml
59+
version: "3"
60+
services:
61+
zigbee2mqtt-ucm:
62+
build: .
63+
image: enapter-vucm-examples/zigbee2mqtt:latest
64+
environment:
65+
- ENAPTER_VUCM_BLOB: "REALENAPTERVUCMBLOBMUSTBEHERE="
66+
- ZIGBEE_MQTT_HOST: "192.168.192.190"
67+
- ZIGBEE_MQTT_PORT: "9883"
68+
- ZIGBEE_MQTT_USER: "mqtt_user"
69+
- ZIGBEE_MQTT_PASSWORD: "mqtt_password"
70+
- ZIGBEE_MQTT_TOPIC: "zigbee2mqtt/MyDevice"
71+
- ZIGBEE_SENSOR_MANUFACTURER: "Device Manufacturer"
72+
- ZIGBEE_SENSOR_MODEL: "Device Model"
73+
```
74+
75+
## Step 4. Build Docker Image with Standalone UCM
76+
77+
> You can you can skip this step and go directly to th Step 5.
78+
> Docker Compose will automatically build your image before starting container.
79+
80+
Build your Docker image by running `bash docker_build.sh` command in directory with UCM.
81+
82+
```bash
83+
user@pc zigbee2mqtt % bash docker_build.sh
84+
#0 building with "desktop-linux" instance using docker driver
85+
86+
#1 [internal] load .dockerignore
87+
#1 transferring context: 2B done
88+
#1 DONE 0.0s
89+
90+
#2 [internal] load build definition from Dockerfile
91+
#2 transferring dockerfile: 281B done
92+
#2 DONE 0.0s
93+
94+
#3 [internal] load metadata for docker.io/library/python:3.10-alpine3.16
95+
#3 DONE 2.0s
96+
97+
#4 [1/7] FROM docker.io/library/python:3.10-alpine3.16@sha256:afe68972cc00883d70b3760ee0ffbb7375cf09706c122dda7063ffe64c5be21b
98+
#4 DONE 0.0s
99+
100+
#5 [internal] load build context
101+
#5 transferring context: 66B done
102+
#5 DONE 0.0s
103+
104+
#6 [3/7] RUN apk add build-base
105+
#6 CACHED
106+
107+
#7 [2/7] WORKDIR /app
108+
#7 CACHED
109+
110+
#8 [4/7] RUN python -m venv .venv
111+
#8 CACHED
112+
113+
#9 [5/7] COPY requirements.txt requirements.txt
114+
#9 CACHED
115+
116+
#10 [6/7] RUN .venv/bin/pip install -r requirements.txt
117+
#10 CACHED
118+
119+
#11 [7/7] COPY script.py script.py
120+
#11 CACHED
121+
122+
#12 exporting to image
123+
#12 exporting layers done
124+
#12 writing image sha256:92e1050debeabaff5837c6ca5bc26b0b966d09fc6f24e21b1d10cbb2f4d9aeec done
125+
#12 naming to docker.io/enapter-vucm-examples/zigbee2mqtt:latest done
126+
#12 DONE 0.0s
127+
```
128+
129+
Your `enapter-vucm-examples/zigbee2mqtt` image is now built and you can see it by running `docker images` command:
130+
131+
```bash
132+
user@pc zigbee2mqtt % docker images enapter-vucm-examples/zigbee2mqtt
133+
REPOSITORY TAG IMAGE ID CREATED SIZE
134+
enapter-vucm-examples/zigbee2mqtt latest 92e1050debea 5 hours ago 285MB
135+
```
136+
137+
## Step 5. Run your Standalone UCM Docker Container
138+
139+
Finally run your Standalone UCM with `docker-compose up` command:
140+
141+
On this step you can check that your UCM is now Online in the Cloud.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
version: "3"
2+
services:
3+
zigbee2mqtt-ucm:
4+
build: .
5+
image: enapter-vucm-examples/zigbee2mqtt:latest
6+
environment:
7+
- ENAPTER_VUCM_BLOB: "REALENAPTERVUCMBLOBMUSTBEHERE="
8+
- ZIGBEE_MQTT_HOST: "192.168.192.190"
9+
- ZIGBEE_MQTT_PORT: "9883"
10+
- ZIGBEE_MQTT_USER: "mqtt_user"
11+
- ZIGBEE_MQTT_PASSWORD: "mqtt_password"
12+
- ZIGBEE_MQTT_TOPIC: "zigbee2mqtt/MyDevice"
13+
- ZIGBEE_SENSOR_MANUFACTURER: "Device Manufacturer"
14+
- ZIGBEE_SENSOR_MODEL: "Device Model"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
7+
IMAGE_TAG="${IMAGE_TAG:-"enapter-vucm-examples/$(basename "$SCRIPT_DIR"):latest"}"
8+
9+
docker build --progress=plain --tag "$IMAGE_TAG" "$SCRIPT_DIR"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
IFS=$'\n\t'
5+
6+
SCRIPT_DIR="$(realpath "$(dirname "$0")")"
7+
IMAGE_TAG="${IMAGE_TAG:-"enapter-vucm-examples/$(basename "$SCRIPT_DIR"):latest"}"
8+
9+
bash $SCRIPT_DIR/docker_build.sh
10+
11+
docker run --rm -it \
12+
--network host \
13+
-e ENAPTER_LOG_LEVEL="${ENAPTER_LOG_LEVEL:-info}" \
14+
-e ENAPTER_VUCM_BLOB="$ENAPTER_VUCM_BLOB" \
15+
-e ZIGBEE_MQTT_HOST="$ZIGBEE_MQTT_HOST" \
16+
-e ZIGBEE_MQTT_PORT="$ZIGBEE_MQTT_PORT" \
17+
-e ZIGBEE_MQTT_USER="$ZIGBEE_MQTT_USER" \
18+
-e ZIGBEE_MQTT_PASSWORD="$ZIGBEE_MQTT_PASSWORD" \
19+
-e ZIGBEE_MQTT_TOPIC="$ZIGBEE_MQTT_TOPIC" \
20+
-e ZIGBEE_SENSOR_MANUFACTURER="$ZIGBEE_SENSOR_MANUFACTURER" \
21+
-e ZIGBEE_SENSOR_MODEL="$ZIGBEE_SENSOR_MODEL" \
22+
"$IMAGE_TAG"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
blueprint_spec: "device/1.0"
2+
3+
display_name: "Temperature/Humidity Sensor"
4+
icon: home
5+
license: MIT
6+
author: enapter
7+
support:
8+
url: https://go.enapter.com/enapter-blueprint-support
9+
email: support@enapter.com
10+
11+
communication_module:
12+
product: ENP-VIRTUAL
13+
14+
properties:
15+
manufacturer:
16+
display_name: Manufacturer
17+
type: string
18+
model:
19+
display_name: Model
20+
type: string
21+
22+
telemetry:
23+
temperature:
24+
display_name: Temperature
25+
type: float
26+
unit: "°C"
27+
battery:
28+
display_name: Battery Capacity
29+
type: float
30+
unit: "%"
31+
humidity:
32+
display_name: Humidity
33+
type: float
34+
unit: "%"
35+
linkquality:
36+
display_name: Connection Link Quality
37+
type: float
38+
39+
40+
.cloud:
41+
mobile_main_chart: temperature
42+
mobile_telemetry:
43+
- temperature
44+
- battery
45+
- humidity
46+
- linkquality
47+
48+
mobile_charts:
49+
- temperature
50+
- battery
51+
- humidity
52+
- linkquality
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enapter==0.9.1
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import asyncio
2+
import functools
3+
import json
4+
import os
5+
6+
import enapter
7+
8+
9+
async def main():
10+
env_prefix = "ZIGBEE_"
11+
mqtt_client_config = enapter.mqtt.Config.from_env(prefix=env_prefix)
12+
device_factory = functools.partial(
13+
ZigbeeMqtt,
14+
mqtt_client_config=mqtt_client_config,
15+
mqtt_topic=os.environ[env_prefix + "MQTT_TOPIC"],
16+
sensor_manufacturer=os.environ[env_prefix + "SENSOR_MANUFACTURER"],
17+
sensor_model=os.environ[env_prefix + "SENSOR_MODEL"],
18+
)
19+
await enapter.vucm.run(device_factory)
20+
21+
22+
class ZigbeeMqtt(enapter.vucm.Device):
23+
def __init__(
24+
self,
25+
mqtt_client_config,
26+
mqtt_topic,
27+
sensor_manufacturer,
28+
sensor_model,
29+
**kwargs,
30+
):
31+
super().__init__(**kwargs)
32+
33+
self.telemetry = {}
34+
35+
self.mqtt_client_config = mqtt_client_config
36+
self.mqtt_topic = mqtt_topic
37+
38+
self.sensor_manufacturer = sensor_manufacturer
39+
self.sensor_model = sensor_model
40+
41+
async def task_consume(self):
42+
async with enapter.mqtt.Client(self.mqtt_client_config) as client:
43+
async with client.subscribe(self.mqtt_topic) as messages:
44+
async for msg in messages:
45+
try:
46+
self.telemetry = json.loads(msg.payload)
47+
except json.JSONDecodeError as e:
48+
await self.log.error(f"failed to decode json payload: {e}")
49+
50+
async def task_telemetry_sender(self):
51+
while True:
52+
await self.send_telemetry(self.telemetry)
53+
await asyncio.sleep(1)
54+
55+
async def task_properties_sender(self):
56+
while True:
57+
await self.send_properties(
58+
{
59+
"model": self.sensor_model,
60+
"manufacturer": self.sensor_manufacturer,
61+
}
62+
)
63+
await asyncio.sleep(10)
64+
65+
66+
if __name__ == "__main__":
67+
asyncio.run(main())

0 commit comments

Comments
 (0)