Skip to content

Commit 7b778eb

Browse files
committed
Added webhook support + major improvements
1 parent 43d7347 commit 7b778eb

File tree

9 files changed

+351
-76
lines changed

9 files changed

+351
-76
lines changed

.env.example

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
MQTT_BROKER=192.168.1.50
2+
MQTT_PORT=1883
3+
MQTT_USERNAME=test
4+
MQTT_PASSWORD=xxxx
5+
MQTT_CLIENT_ID=switchbot_api2mqtt
6+
MQTT_SMARTLOCK_BASE_TOPIC=smarthome/smartlock/
7+
MQTT_GENERIC_BASE_TOPIC=switchbot/api/generic/
8+
SWITCHBOT_TOKEN=XXXXXXXXX
9+
SWITCHBOT_SECRET=YYYYYYYYYY
10+
API_BASEURL=https://api.switch-bot.com/v1.1/
11+
HTTP_PORT=5100
12+
SWITCHBOT_VALID_DEVICE_TYPES='["WoLockPro", "WoKeypadTouch", "WoHub2"]'
13+
SWITCHBOT_VALID_DEVICE_ID='["AAAAA", "BBBBB", "CCCCC"]'
14+
SWITCHBOT_SMARTLOCK_VALID_COMMAND='["lock", "unlock", "status"]'
15+
SWITCHBOT_POLLING_INTERVAL_SEC=3600

.env.template

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ MQTT_PORT=
33
MQTT_USERNAME=
44
MQTT_PASSWORD=
55
MQTT_CLIENT_ID=
6-
MQTT_TOPIC_COMMAND=
7-
MQTT_TOPIC_STATUS=
8-
MQTT_TOPIC_RESPONSE=
6+
MQTT_SMARTLOCK_BASE_TOPIC=
7+
MQTT_GENERIC_BASE_TOPIC=
98
SWITCHBOT_TOKEN=
109
SWITCHBOT_SECRET=
11-
LOG_FILE=
12-
API_BASEURL=
10+
API_BASEURL=
11+
HTTP_PORT=
12+
SWITCHBOT_VALID_DEVICE_TYPES=
13+
SWITCHBOT_VALID_DEVICE_ID=
14+
SWITCHBOT_SMARTLOCK_VALID_COMMAND=
15+
SWITCHBOT_POLLING_INTERVAL_SEC=

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ RUN pip install --no-cache-dir -r requirements.txt
99

1010
# Copy source code
1111
COPY switchbot_api2mqtt.py .
12+
COPY http_utils.py .
1213
COPY .env .
14+
COPY logging.json .
1315

1416
# Default command
1517
CMD ["python", "switchbot_api2mqtt.py"]

README.md

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,86 @@ Designed for ease of deployment, this solution can be run natively or as a Docke
88

99
By translating commands and status updates between SwitchBot's proprietary API and standard MQTT messages, this solution enables you to control and monitor your SwitchBot devices directly from your MQTT-enabled smart home platform. This eliminates the need for separate apps or complex workarounds, providing a unified and efficient control experience.
1010

11+
Tested on Smart Lock Pro device
12+
1113
***Please, use version 0.1 if you would like to control your smart lock pro in a simple way***
1214

15+
***Please, use version 0.2 if you would like to operate in a generic way***
16+
1317
## Usage
1418

15-
- Copy *.env-template* in *.env* and populate config variables according to your setup
19+
- Copy *.env-template* in *.env* and populate config variables according to your setup (see .env.example for help)
20+
- Make the appropriate changes to the logging.json file for management of application log
1621
- Follow this guide to obtain your authentication secrets: https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#getting-started
1722
- Run switchbot_api2mqtt.py using your python environment OR run with docker compose
18-
- Send GET and POST call to Switchbot API to *MQTT_TOPIC_COMMAND* topic using an MQTT publisher
19-
- Get responses subscribing to *MQTT_TOPIC_RESPONSE* topic
20-
- Actually the *MQTT_TOPIC_STATUS* topic is not used
23+
- Send commands to *MQTT_SMARTLOCK_BASE_TOPIC/[deviceid]/cmnd* topic to interact with smartlock devices. Payload is a plain string with command name. Responses will be published on *MQTT_SMARTLOCK_BASE_TOPIC/[deviceid]/response* topic
24+
- Send generic GET and POST call to Switchbot API to *MQTT_GENERIC_BASE_TOPIC* topic using an MQTT publisher. Responses will be published on *MQTT_GENERIC_BASE_TOPIC/response* topic
25+
- Webhook events will be published on *MQTT_SMARTLOCK_BASE_TOPIC/[deviceid]/event* topic
26+
- It is possible to enable a polling thread to obtain state information periodically (this is mandatory to periodically update battery information not managed by webhooks)
2127

22-
### Payload template
28+
### Payload template for generic GET/POST call
2329

2430
{"method": "[get/post]", "service": "[path_url]", "payload": ["generic_payload_for_POST_call"]}
2531

32+
### Environment configuration
33+
34+
- *MQTT_BROKER*=**mqtt broker ip address**
35+
- *MQTT_PORT*=**mqtt broker port**
36+
- *MQTT_USERNAME*=**mqtt broker username (if needed)**
37+
- *MQTT_PASSWORD*=**mqtt broker password (if needed)**
38+
- *MQTT_CLIENT_ID*=**mqtt_client_id**
39+
- *MQTT_SMARTLOCK_BASE_TOPIC*=**base topic for smart lock pro interaction**
40+
- *MQTT_GENERIC_BASE_TOPIC*=**base topic for generic api interaction**
41+
- *SWITCHBOT_TOKEN*=**switchbot authentication token** see https://github.com/OpenWonderLabs/SwitchBotAPI?-tab=readme-ov-file#getting-started
42+
- *SWITCHBOT_SECRET*=**switchbot authentication secret** see https://github.com/OpenWonderLabs/SwitchBotAPI?-tab=readme-ov-file#getting-started
43+
- *API_BASEURL*=**switchbot api base url**
44+
- *HTTP_PORT*=**http port listening for webhook events**
45+
- *SWITCHBOT_VALID_DEVICE_TYPES*=**array of valid device types managed by bridge** see https://github.com/-OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file
46+
- *SWITCHBOT_VALID_DEVICE_ID*=**array of valid device id managed by bridge**
47+
- *SWITCHBOT_SMARTLOCK_VALID_COMMAND*=**array of valid smartlock commands managed by bridge**
48+
- *SWITCHBOT_POLLING_INTERVAL_SEC*=**polling interval in seconds. 0 means no polling**
49+
50+
### MQTT Topics
51+
52+
- *MQTT_SMARTLOCK_BASE_TOPIC/[deviceid]/cmnd*=**to send command to smart lock pro device**
53+
- *MQTT_SMARTLOCK_BASE_TOPIC/[deviceid]/response*=**to receive command response from smart lock pro device**
54+
- *MQTT_SMARTLOCK_BASE_TOPIC/[deviceid]/event*=**to receive webhook events from smart lock pro device**
55+
- *MQTT_GENERIC_BASE_TOPIC/cmnd*=**to send generic get/post call to switchbot api**
56+
- *MQTT_GENERIC_BASE_TOPIC/response*=**to receive response from switchbot api generic get/post call**
57+
58+
### WEBHOOK configuration
59+
60+
A simple video about webhook: https://youtu.be/FxlmHL5eWR8
61+
62+
To enable webhook integration you must configure webhook using API. See https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#webhook
63+
64+
So that webhook can work you need to have a public url to receive events.
65+
66+
This can be directly managed by this bridge or by a frontend http server which through Reverse Proxy forwards requests to this bridge.
67+
68+
It is important to know and manage security aspects before exposing services on the internet.
69+
70+
As an example, this is a valid setup:
71+
72+
![Webhook setup](assets/webhook01.jpg)
73+
74+
- *Reception of the Webhook (Cloud Server)*: Switchbot webhook calls are sent to the public url of your cloud server (for example, http://www.example.com/switchbot_webhook). Being the server publicly accessible, it is able to receive these requests from Switchbot.
75+
- *SSH tunnel*: the cloud server is connected to your local Raspberry Pi via an SSH tunnel. This tunnel creates a safe and persistent connection between the external server and your home network, allowing the cloud server to forward the traffic to the Raspberry Pi.
76+
- *Forwarding of the request (Raspberry PI)*: once the webhook arrives at the Cloud server, this forwards it through the SSH tunnel to the Raspberry Pi. The Raspberry Pi, which acts as "bridge" for the webhooks, receives the request and can therefore process it.
77+
- *Local processing*: the bridge running on the Raspberry PI performs the desired actions based on the content of the Webhook request, such as checking the status of switchbot devices or activating specific automations within your home network.
78+
79+
This setup allows you to receive the Switchbot Webhooks while keeping your Raspberry Pi and your home network protected behind a firewall, exposing only an intermediate server.
80+
81+
## *Version 1.0 beta*
82+
- Added webhooks support using an internal http server (**flask**)
83+
- Added full support for **Smart Lock Pro** devices
84+
- Added polling thread for status information
85+
- Removed *LOG_FILE*, *MQTT_TOPIC_COMMAND*, *MQTT_TOPIC_STATUS* and *MQTT_TOPIC_RESPONSE* configuration properties
86+
- Added *MQTT_SMARTLOCK_BASE_TOPIC*, *MQTT_GENERIC_BASE_TOPIC*, *HTTP_PORT*, *SWITCHBOT_VALID_DEVICE_TYPES*, *SWITCHBOT_VALID_DEVICE_ID*, *SWITCHBOT_SMARTLOCK_VALID_COMMAND*, *SWITCHBOT_POLLING_INTERVAL_SEC* configuration properties
87+
- Added external logger configuration using logging.json file
88+
- Added .env.example file
89+
- Code refactoring
90+
2691
## *Version 0.2*
2792
- Generic version to invoke all switcbot API services using GET and POST method
2893
- MQTT payload containts all the details related to API call

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ services:
22
switchbot-proxy:
33
build: .
44
container_name: switchbot_api2mqtt
5-
restart: no
5+
restart: always
66
env_file:
77
- .env
88
network_mode: host

http_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import logging
2+
import os
3+
import requests
4+
from dotenv import load_dotenv
5+
6+
load_dotenv()
7+
logger = logging.getLogger("http_utils")
8+
9+
def http_post(service_url, payload, headers):
10+
logger.debug(f"Call POST {service_url}")
11+
logger.debug(f"Payload {payload}")
12+
logger.debug(f"headers {headers}")
13+
try:
14+
response = requests.post(service_url, headers=headers, json=payload)
15+
logger.debug(f"Response {response}")
16+
return response
17+
except Exception as e:
18+
logger.error(f"Error sending POST request: {e}")
19+
return False
20+
21+
def http_get(service_url, headers):
22+
logger.debug(f"Call GET {service_url}")
23+
logger.debug(f"headers {headers}")
24+
try:
25+
response = requests.get(service_url, headers=headers)
26+
logger.debug(f"Response {response}")
27+
return response
28+
except Exception as e:
29+
logger.error(f"Error sending GET request: {e}")
30+
return False

logging.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"version": 1,
3+
"formatters": {
4+
"standard": {
5+
"format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
6+
}
7+
},
8+
"handlers": {
9+
"console": {
10+
"class": "logging.StreamHandler",
11+
"formatter": "standard",
12+
"level": "DEBUG"
13+
},
14+
"file": {
15+
"class": "logging.FileHandler",
16+
"formatter": "standard",
17+
"level": "DEBUG",
18+
"filename": "/logs/switchbot_api2mqtt.log"
19+
}
20+
},
21+
"loggers": {
22+
"switchbot_api2mqtt": {
23+
"handlers": ["console", "file"],
24+
"level": "INFO",
25+
"propagate": false
26+
},
27+
"http_utils": {
28+
"handlers": ["console", "file"],
29+
"level": "INFO",
30+
"propagate": false
31+
}
32+
},
33+
"root": {
34+
"level": "INFO",
35+
"handlers": ["console", "file"]
36+
}
37+
}

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
paho-mqtt>=1.6.1
22
requests
33
python-dotenv
4+
flask

0 commit comments

Comments
 (0)