Skip to content

Commit e01f74e

Browse files
committed
Generic version to invoke all switcbot API services using GET and POST method
1 parent ac233c9 commit e01f74e

File tree

3 files changed

+71
-72
lines changed

3 files changed

+71
-72
lines changed

.env.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ MQTT_PASSWORD=
55
MQTT_CLIENT_ID=
66
MQTT_TOPIC_COMMAND=
77
MQTT_TOPIC_STATUS=
8+
MQTT_TOPIC_RESPONSE=
89
SWITCHBOT_TOKEN=
910
SWITCHBOT_SECRET=
10-
SWITCHBOT_DEVICE_ID=
1111
LOG_FILE=
12+
API_BASEURL=

README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,30 @@ 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+
***Please, use version 0.1 if you would like to control your smart lock pro in a simple way***
12+
1113
## Usage
1214

1315
- Copy *.env-template* in *.env* and populate config variables according to your setup
1416
- Follow this guide to obtain your authentication secrets: https://github.com/OpenWonderLabs/SwitchBotAPI?tab=readme-ov-file#getting-started
1517
- Run switchbot_api2mqtt.py using your python environment OR run with docker compose
16-
- Send commands to *MQTT_TOPIC_COMMAND* topic using an MQTT publisher
17-
- Get response and status subscribing to *MQTT_TOPIC_STATUS* topic
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
21+
22+
### Payload template
23+
24+
{"method": "[get/post]", "service": "[path_url]", "payload": ["generic_payload_for_POST_call"]}
25+
26+
## *Version 0.2*
27+
- Generic version to invoke all switcbot API services using GET and POST method
28+
- MQTT payload containts all the details related to API call
29+
- Added *MQTT_TOPIC_RESPONSE* topic is used to obtain the last call response
30+
- Added *API_BASEURL* configuration property to set the API baseurl
31+
- Removed the *SWITCHBOT_DEVICE_ID* configuration property
32+
- No more automatic status retrieving
1833

19-
## *version 0.1*
34+
## *Version 0.1*
2035
- Initial version to test the Smart Lock.
2136
- The commands are sent as simple strings in the Mqtt message
2237
- Valid commands are *lock*, *unlock*, *devices*, *status*.

switchbot_api2mqtt.py

Lines changed: 51 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,37 @@
99
import base64
1010
import requests
1111
import paho.mqtt.client as mqtt
12+
import importlib.metadata
1213
from threading import Thread
1314
from dotenv import load_dotenv
1415

15-
_VERSION = "0.1"
16+
_VERSION = "0.2"
1617

17-
load_dotenv()
18+
load_dotenv()
1819
log_file = os.getenv("LOG_FILE", "/logs/switchbot_api2mqtt.log")
19-
# --- Logging ---
20+
2021
logging.basicConfig(
2122
level=logging.INFO,
2223
format="%(asctime)s [%(levelname)s] %(message)s",
2324
handlers=[
24-
logging.StreamHandler(),
25-
logging.FileHandler(log_file, encoding='utf-8')
25+
logging.StreamHandler(),
26+
logging.FileHandler(log_file, encoding='utf-8')
2627
]
2728
)
2829
logger = logging.getLogger("switchbot-api2mqtt")
2930

30-
# --- Configurazioni ---
3131
MQTT_BROKER = os.getenv("MQTT_BROKER", "localhost")
3232
MQTT_PORT = int(os.getenv("MQTT_PORT", 1883))
3333
MQTT_USERNAME = os.getenv("MQTT_USERNAME")
3434
MQTT_PASSWORD = os.getenv("MQTT_PASSWORD")
3535
MQTT_CLIENT_ID = os.getenv("MQTT_CLIENT_ID", "switchbot_api2mqtt")
3636
MQTT_TOPIC_COMMAND = os.getenv("MQTT_TOPIC_COMMAND", "switchbot/lock/cmd")
3737
MQTT_TOPIC_STATUS = os.getenv("MQTT_TOPIC_STATUS", "switchbot/lock/status")
38+
MQTT_TOPIC_RESPONSE = os.getenv("MQTT_TOPIC_RESPONSE", "switchbot/lock/response")
3839
SWITCHBOT_TOKEN = os.getenv("SWITCHBOT_TOKEN")
3940
SWITCHBOT_SECRET = os.getenv("SWITCHBOT_SECRET")
40-
SWITCHBOT_DEVICE_ID = os.getenv("SWITCHBOT_DEVICE_ID")
41+
API_BASEURL = os.getenv("API_BASEURL", "https://api.switch-bot.com/v1.1/")
4142

42-
# --- Funzione per generare headers autenticati ---
4343
def generate_headers():
4444
t = str(int(time.time() * 1000))
4545
nonce = str(uuid.uuid4())
@@ -54,76 +54,62 @@ def generate_headers():
5454
"Content-Type": "application/json; charset=utf8"
5555
}
5656

57-
# --- Funzioni per SwitchBot ---
58-
def get_lock_status():
59-
logger.info(f"Get Status")
60-
url = f"https://api.switch-bot.com/v1.1/devices/{SWITCHBOT_DEVICE_ID}/status"
61-
headers = generate_headers()
62-
try:
63-
response = requests.get(url, headers=headers)
64-
data = response.json()
65-
if data.get("message") == "success":
66-
return data.get("body")
67-
except Exception as e:
68-
logger.error(f"Error getting status: {e}")
69-
return None
70-
71-
def get_devices():
72-
url = f"https://api.switch-bot.com/v1.1/devices"
57+
def post(service_url, payload):
58+
url = f"{API_BASEURL}{service_url}"
7359
headers = generate_headers()
60+
logger.info(f"Call POST {url}")
61+
logger.debug(f"Payload {payload}")
62+
logger.debug(f"headers {headers}")
7463
try:
75-
response = requests.get(url, headers=headers)
76-
data = response.json()
77-
if data.get("message") == "success":
78-
return data.get("body")
64+
response = requests.post(url, headers=headers, json=payload)
65+
logger.debug(f"Response {response}")
66+
return response
7967
except Exception as e:
80-
logger.error(f"Error getting devices: {e}")
81-
return None
68+
logger.error(f"Error sending POST request: {e}")
69+
return False
8270

83-
def send_command(command):
84-
url = f"https://api.switch-bot.com/v1.1/devices/{SWITCHBOT_DEVICE_ID}/commands"
71+
def get(service_url):
72+
url = f"{API_BASEURL}{service_url}"
8573
headers = generate_headers()
86-
payload = {
87-
"command": command,
88-
"parameter": "default",
89-
"commandType": "command"
90-
}
74+
logger.info(f"Call GET {url}")
75+
logger.debug(f"headers {headers}")
9176
try:
92-
response = requests.post(url, headers=headers, json=payload)
93-
logger.info(f"Response {response}")
94-
return response.status_code == 200
77+
response = requests.get(url, headers=headers)
78+
logger.debug(f"Response {response}")
79+
return response
9580
except Exception as e:
96-
logger.error(f"Error send command: {e}")
81+
logger.error(f"Error sending GET request: {e}")
9782
return False
9883

99-
# --- MQTT Handlers ---
10084
def on_connect(client, userdata, flags, rc, properties=None):
101-
logger.info(f"MQTT connection successfully")
85+
logger.info(f"MQTT connection successfully ({rc})")
10286
client.subscribe(MQTT_TOPIC_COMMAND)
10387

10488
def on_message(client, userdata, msg):
105-
command = msg.payload.decode().strip().lower()
106-
logger.info(f"Command {command}")
107-
if command in ["lock", "unlock"]:
108-
success = send_command(command)
109-
if not success:
110-
logger.error("Error executing command")
111-
elif command == "devices":
112-
res = get_devices()
113-
client.publish(MQTT_TOPIC_STATUS, json.dumps(res))
114-
elif command == "status":
115-
res = get_lock_status()
116-
client.publish(MQTT_TOPIC_STATUS, json.dumps(res))
117-
118-
# --- Thread per aggiornamento stato periodico ---
119-
def poll_status_loop(client):
120-
while True:
121-
status = get_lock_status()
122-
if status:
123-
client.publish(MQTT_TOPIC_STATUS, json.dumps(status))
124-
time.sleep(60) # ogni minuto
89+
try:
90+
payload = msg.payload.decode('utf-8')
91+
data = json.loads(payload)
92+
method = data.get("method")
93+
service = data.get("service")
94+
logger.info(f"method {method}")
95+
logger.info(f"service {service}")
96+
res = None
97+
if service and method == "get":
98+
res = get(service)
99+
elif service and method == "post":
100+
res = post(service, data.get("payload"))
101+
else:
102+
logger.error(f"Invalid payload")
103+
if res is not None:
104+
if res.status_code == 200:
105+
client.publish(MQTT_TOPIC_RESPONSE, json.dumps(res.json()))
106+
else:
107+
logger.error("Invalid response {res}")
108+
except json.JSONDecodeError as jsonerror:
109+
logger.error("Invalid JSON message: {jsonerror}")
110+
except Exception as e:
111+
logger.error(f"Generic error: {e}")
125112

126-
# --- Main ---
127113
if __name__ == "__main__":
128114
logger.info(f"START version {_VERSION}")
129115
mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, MQTT_CLIENT_ID)
@@ -137,10 +123,7 @@ def poll_status_loop(client):
137123
logger.error(f"MQTT Connection error: {e}")
138124
exit()
139125

140-
poller = Thread(target=poll_status_loop, args=(mqtt_client,), daemon=True)
141-
poller.start()
142-
143-
logger.info("Waiting for messages... CTRL+C to exit.")
126+
logger.info("Waiting for messages...")
144127
try:
145128
mqtt_client.loop_forever()
146129
except KeyboardInterrupt:

0 commit comments

Comments
 (0)