diff --git a/README.md b/README.md index 668939c..d8d65d8 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ See [Wiki](https://github.com/zewelor/bt-mqtt-gateway/wiki) for more information * Xiaomi Aqara thermometer via [mithermometer](https://github.com/hobbypunk90/mithermometer) * Bluetooth Low Power devices (BLE) * [Oral-B connected toothbrushes](https://oralb.com/en-us/products#viewtype:gridview/facets:feature=feature-bluetooth-connectivity/category:products/page:0/sortby:Featured-Sort/productsdisplayed:undefined/promotilesenabled:undefined/cwidth:3/pscroll:) +* [Switchbot](https://www.switch-bot.com/) ## Getting Started @@ -163,6 +164,8 @@ class TimeWorker(BaseWorker): * [**bbbenji**](https://github.com/bbbenji) - *Minor contributions* * [**elviosebastianelli**](https://github.com/elviosebastianelli) - *BLEscanmulti* * [**jumping2000**](https://github.com/jumping2000) - *BLEscan* +* [**AS137430**](https://github.com/AS137430) - *Switchbot* + ## License diff --git a/config.yaml.example b/config.yaml.example index e84b53c..c34ada7 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -41,7 +41,6 @@ manager: herbs: 00:11:22:33:44:55 topic_prefix: miflora update_interval: 300 - mithermometer: args: devices: @@ -68,3 +67,12 @@ manager: ia: 11:22:33:44:55:66 topic_prefix: toothbrush update_interval: 10 + switchbot: + args: + devices: + heater: 00:11:22:33:44:55 + topic_prefix: switchbot/bathroom + state_topic_prefix: switchbot/bathroom + topic_subscription: switchbot/+/+/set + update_interval: 60 + diff --git a/workers/switchbot.py b/workers/switchbot.py new file mode 100644 index 0000000..9cb6bf4 --- /dev/null +++ b/workers/switchbot.py @@ -0,0 +1,82 @@ +from builtins import staticmethod +import logging + +from mqtt import MqttMessage + +from workers.base import BaseWorker +import logger + +REQUIREMENTS = ['bluepy'] +_LOGGER = logger.get(__name__) + +STATE_ON = 'ON' +STATE_OFF = 'OFF' + + +class SwitchbotWorker(BaseWorker): + def _setup(self): + + _LOGGER.info("Adding %d %s devices", len(self.devices), repr(self)) + for name, mac in self.devices.items(): + _LOGGER.info("Adding %s device '%s' (%s)", repr(self), name, mac) + self.devices[name] = {"bot":None,"state":STATE_OFF,"mac":mac} + + def format_state_topic(self, *args): + return '/'.join([self.state_topic_prefix, *args]) + + def status_update(self): + from bluepy import btle + + ret = [] + _LOGGER.debug("Updating %d %s devices", len(self.devices), repr(self)) + for name, bot in self.devices.items(): + _LOGGER.debug("Updating %s device '%s' (%s)", repr(self), name, bot["mac"]) + try: + ret += self.update_device_state(name, bot["state"]) + except btle.BTLEException as e: + logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), name, bot["mac"], type(e).__name__, suppress=True) + return ret + + def on_command(self, topic, value): + from bluepy import btle + import binascii + from bluepy.btle import Peripheral + _, _ , device_name, _ = topic.split('/') + + bot = self.devices[device_name] + + value = value.decode('utf-8') + + # It needs to be on separate if because first if can change method + + _LOGGER.debug("Setting %s on %s device '%s' (%s)", value, repr(self), device_name, bot["mac"]) + try: + bot["bot"] = Peripheral(bot["mac"],"random") + hand_service = bot["bot"].getServiceByUUID("cba20d00-224d-11e6-9fb8-0002a5d5c51b") + hand = hand_service.getCharacteristics("cba20002-224d-11e6-9fb8-0002a5d5c51b")[0] + if value == STATE_ON: + hand.write(binascii.a2b_hex("570101")) + elif value == STATE_OFF: + hand.write(binascii.a2b_hex("570102")) + elif value == "PRESS": + hand.write(binascii.a2b_hex("570100")) + bot["bot"].disconnect() + except btle.BTLEException as e: + logger.log_exception(_LOGGER, "Error setting %s on %s device '%s' (%s): %s", value, repr(self), device_name, bot["mac"], type(e).__name__) + return [] + + try: + return self.update_device_state(device_name, value) + except btle.BTLEException as e: + logger.log_exception(_LOGGER, "Error during update of %s device '%s' (%s): %s", repr(self), device_name, bot["mac"], type(e).__name__, suppress=True) + return [] + + def update_device_state(self, name, value): + ret = [] + ret.append(MqttMessage(topic=self.format_state_topic(name), payload=value)) + return ret + + def device_for(self, mac): + for key in self.devices: + if key.addr == mac: + return key