|
@@ -110,6 +110,48 @@ class _MQTTControlledActor(abc.ABC):
|
|
|
) -> None:
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
+ @abc.abstractmethod
|
|
|
+ def _get_device(self) -> switchbot.SwitchbotDevice:
|
|
|
+ raise NotImplementedError()
|
|
|
+
|
|
|
+ def _update_device_info(self) -> None:
|
|
|
+ log_queue: queue.Queue[logging.LogRecord] = queue.Queue(maxsize=0)
|
|
|
+ logging.getLogger("switchbot").addHandler(_QueueLogHandler(log_queue))
|
|
|
+ try:
|
|
|
+ self._get_device().update()
|
|
|
+
|
|
|
+
|
|
|
+ while not log_queue.empty():
|
|
|
+ log_record = log_queue.get()
|
|
|
+ if log_record.exc_info:
|
|
|
+ exc: typing.Optional[BaseException] = log_record.exc_info[1]
|
|
|
+ if (
|
|
|
+ isinstance(exc, bluepy.btle.BTLEManagementError)
|
|
|
+ and exc.emsg == "Permission Denied"
|
|
|
+ ):
|
|
|
+ raise exc
|
|
|
+ except bluepy.btle.BTLEManagementError as exc:
|
|
|
+ if (
|
|
|
+ exc.emsg == "Permission Denied"
|
|
|
+ and exc.message == "Failed to execute management command 'le on'"
|
|
|
+ ):
|
|
|
+ raise PermissionError(
|
|
|
+ "bluepy-helper failed to enable low energy mode"
|
|
|
+ " due to insufficient permissions."
|
|
|
+ "\nSee https://github.com/IanHarvey/bluepy/issues/313#issuecomment-428324639"
|
|
|
+ ", https://github.com/fphammerle/switchbot-mqtt/pull/31#issuecomment-846383603"
|
|
|
+ ", and https://github.com/IanHarvey/bluepy/blob/v/1.3.0/bluepy"
|
|
|
+ "/bluepy-helper.c#L1260."
|
|
|
+ "\nInsecure workaround:"
|
|
|
+ "\n1. sudo apt-get install --no-install-recommends libcap2-bin"
|
|
|
+ f"\n2. sudo setcap cap_net_admin+ep {shlex.quote(bluepy.btle.helperExe)}"
|
|
|
+ "\n3. restart switchbot-mqtt"
|
|
|
+ "\nIn docker-based setups, you could use"
|
|
|
+ " `sudo docker run --cap-drop ALL --cap-add NET_ADMIN --user 0 …`"
|
|
|
+ " (seriously insecure)."
|
|
|
+ ) from exc
|
|
|
+ raise
|
|
|
+
|
|
|
@classmethod
|
|
|
def _mqtt_command_callback(
|
|
|
cls,
|
|
@@ -216,13 +258,16 @@ class _ButtonAutomator(_MQTTControlledActor):
|
|
|
def __init__(
|
|
|
self, *, mac_address: str, retry_count: int, password: typing.Optional[str]
|
|
|
) -> None:
|
|
|
- self._device = switchbot.Switchbot(
|
|
|
+ self.__device = switchbot.Switchbot(
|
|
|
mac=mac_address, password=password, retry_count=retry_count
|
|
|
)
|
|
|
super().__init__(
|
|
|
mac_address=mac_address, retry_count=retry_count, password=password
|
|
|
)
|
|
|
|
|
|
+ def _get_device(self) -> switchbot.SwitchbotDevice:
|
|
|
+ return self.__device
|
|
|
+
|
|
|
def execute_command(
|
|
|
self,
|
|
|
mqtt_message_payload: bytes,
|
|
@@ -231,7 +276,7 @@ class _ButtonAutomator(_MQTTControlledActor):
|
|
|
) -> None:
|
|
|
|
|
|
if mqtt_message_payload.lower() == b"on":
|
|
|
- if not self._device.turn_on():
|
|
|
+ if not self.__device.turn_on():
|
|
|
_LOGGER.error("failed to turn on switchbot %s", self._mac_address)
|
|
|
else:
|
|
|
_LOGGER.info("switchbot %s turned on", self._mac_address)
|
|
@@ -239,7 +284,7 @@ class _ButtonAutomator(_MQTTControlledActor):
|
|
|
self.report_state(mqtt_client=mqtt_client, state=b"ON")
|
|
|
|
|
|
elif mqtt_message_payload.lower() == b"off":
|
|
|
- if not self._device.turn_off():
|
|
|
+ if not self.__device.turn_off():
|
|
|
_LOGGER.error("failed to turn off switchbot %s", self._mac_address)
|
|
|
else:
|
|
|
_LOGGER.info("switchbot %s turned off", self._mac_address)
|
|
@@ -285,7 +330,7 @@ class _CurtainMotor(_MQTTControlledActor):
|
|
|
) -> None:
|
|
|
|
|
|
|
|
|
- self._device = switchbot.SwitchbotCurtain(
|
|
|
+ self.__device = switchbot.SwitchbotCurtain(
|
|
|
mac=mac_address,
|
|
|
password=password,
|
|
|
retry_count=retry_count,
|
|
@@ -295,6 +340,9 @@ class _CurtainMotor(_MQTTControlledActor):
|
|
|
mac_address=mac_address, retry_count=retry_count, password=password
|
|
|
)
|
|
|
|
|
|
+ def _get_device(self) -> switchbot.SwitchbotDevice:
|
|
|
+ return self.__device
|
|
|
+
|
|
|
def _report_position(self, mqtt_client: paho.mqtt.client.Client) -> None:
|
|
|
|
|
|
|
|
@@ -305,47 +353,12 @@ class _CurtainMotor(_MQTTControlledActor):
|
|
|
|
|
|
self._mqtt_publish(
|
|
|
topic_levels=self._MQTT_POSITION_TOPIC_LEVELS,
|
|
|
- payload=str(int(self._device.get_position())).encode(),
|
|
|
+ payload=str(int(self.__device.get_position())).encode(),
|
|
|
mqtt_client=mqtt_client,
|
|
|
)
|
|
|
|
|
|
def _update_position(self, mqtt_client: paho.mqtt.client.Client) -> None:
|
|
|
- log_queue: queue.Queue[logging.LogRecord] = queue.Queue(maxsize=0)
|
|
|
- logging.getLogger("switchbot").addHandler(_QueueLogHandler(log_queue))
|
|
|
- try:
|
|
|
- self._device.update()
|
|
|
-
|
|
|
-
|
|
|
- while not log_queue.empty():
|
|
|
- log_record = log_queue.get()
|
|
|
- if log_record.exc_info:
|
|
|
- exc: typing.Optional[BaseException] = log_record.exc_info[1]
|
|
|
- if (
|
|
|
- isinstance(exc, bluepy.btle.BTLEManagementError)
|
|
|
- and exc.emsg == "Permission Denied"
|
|
|
- ):
|
|
|
- raise exc
|
|
|
- except bluepy.btle.BTLEManagementError as exc:
|
|
|
- if (
|
|
|
- exc.emsg == "Permission Denied"
|
|
|
- and exc.message == "Failed to execute management command 'le on'"
|
|
|
- ):
|
|
|
- raise PermissionError(
|
|
|
- "bluepy-helper failed to enable low energy mode"
|
|
|
- " due to insufficient permissions."
|
|
|
- "\nSee https://github.com/IanHarvey/bluepy/issues/313#issuecomment-428324639"
|
|
|
- ", https://github.com/fphammerle/switchbot-mqtt/pull/31#issuecomment-846383603"
|
|
|
- ", and https://github.com/IanHarvey/bluepy/blob/v/1.3.0/bluepy"
|
|
|
- "/bluepy-helper.c#L1260."
|
|
|
- "\nInsecure workaround:"
|
|
|
- "\n1. sudo apt-get install --no-install-recommends libcap2-bin"
|
|
|
- f"\n2. sudo setcap cap_net_admin+ep {shlex.quote(bluepy.btle.helperExe)}"
|
|
|
- "\n3. restart switchbot-mqtt"
|
|
|
- "\nIn docker-based setups, you could use"
|
|
|
- " `sudo docker run --cap-drop ALL --cap-add NET_ADMIN --user 0 …`"
|
|
|
- " (seriously insecure)."
|
|
|
- ) from exc
|
|
|
- raise
|
|
|
+ self._update_device_info()
|
|
|
self._report_position(mqtt_client=mqtt_client)
|
|
|
|
|
|
def execute_command(
|
|
@@ -356,7 +369,7 @@ class _CurtainMotor(_MQTTControlledActor):
|
|
|
) -> None:
|
|
|
|
|
|
if mqtt_message_payload.lower() == b"open":
|
|
|
- if not self._device.open():
|
|
|
+ if not self.__device.open():
|
|
|
_LOGGER.error("failed to open switchbot curtain %s", self._mac_address)
|
|
|
else:
|
|
|
_LOGGER.info("switchbot curtain %s opening", self._mac_address)
|
|
@@ -364,14 +377,14 @@ class _CurtainMotor(_MQTTControlledActor):
|
|
|
|
|
|
self.report_state(mqtt_client=mqtt_client, state=b"opening")
|
|
|
elif mqtt_message_payload.lower() == b"close":
|
|
|
- if not self._device.close():
|
|
|
+ if not self.__device.close():
|
|
|
_LOGGER.error("failed to close switchbot curtain %s", self._mac_address)
|
|
|
else:
|
|
|
_LOGGER.info("switchbot curtain %s closing", self._mac_address)
|
|
|
|
|
|
self.report_state(mqtt_client=mqtt_client, state=b"closing")
|
|
|
elif mqtt_message_payload.lower() == b"stop":
|
|
|
- if not self._device.stop():
|
|
|
+ if not self.__device.stop():
|
|
|
_LOGGER.error("failed to stop switchbot curtain %s", self._mac_address)
|
|
|
else:
|
|
|
_LOGGER.info("switchbot curtain %s stopped", self._mac_address)
|