Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 7 additions & 52 deletions switchbot/devices/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Switchbot Bot/WoHand constructor."""
super().__init__(*args, **kwargs)
self._inverse: bool = kwargs.pop("inverse_mode", False)
self._settings: dict[str, Any] = {}

async def update(self, interface: int | None = None) -> None:
"""Update mode, battery percent and state of device."""
Expand All @@ -33,86 +32,42 @@ async def update(self, interface: int | None = None) -> None:
async def turn_on(self) -> bool:
"""Turn device on."""
result = await self._sendcommand(ON_KEY)

if result[0] == 1:
return True

if result[0] == 5:
_LOGGER.debug(
"%s: Bot is in press mode and doesn't have on state", self.name
)
return True

return False
return self._check_command_result(result, 0, {1, 5})

async def turn_off(self) -> bool:
"""Turn device off."""
result = await self._sendcommand(OFF_KEY)
if result[0] == 1:
return True

if result[0] == 5:
_LOGGER.debug(
"%s: Bot is in press mode and doesn't have off state", self.name
)
return True

return False
return self._check_command_result(result, 0, {1, 5})

async def hand_up(self) -> bool:
"""Raise device arm."""
result = await self._sendcommand(UP_KEY)
if result[0] == 1:
return True

if result[0] == 5:
_LOGGER.debug("%s: Bot is in press mode", self.name)
return True

return False
return self._check_command_result(result, 0, {1, 5})

async def hand_down(self) -> bool:
"""Lower device arm."""
result = await self._sendcommand(DOWN_KEY)
if result[0] == 1:
return True

if result[0] == 5:
_LOGGER.debug("%s: Bot is in press mode", self.name)
return True

return False
return self._check_command_result(result, 0, {1, 5})

async def press(self) -> bool:
"""Press command to device."""
result = await self._sendcommand(PRESS_KEY)
if result[0] == 1:
return True

if result[0] == 5:
_LOGGER.debug("%s: Bot is in switch mode", self.name)
return True

return False
return self._check_command_result(result, 0, {1, 5})

async def set_switch_mode(
self, switch_mode: bool = False, strength: int = 100, inverse: bool = False
) -> bool:
"""Change bot mode."""
mode_key = format(switch_mode, "b") + format(inverse, "b")
strength_key = f"{strength:0{2}x}" # to hex with padding to double digit

result = await self._sendcommand(DEVICE_SET_MODE_KEY + strength_key + mode_key)

return result[0] == 1
return self._check_command_result(result, 0, {1})

async def set_long_press(self, duration: int = 0) -> bool:
"""Set bot long press duration."""
duration_key = f"{duration:0{2}x}" # to hex with padding to double digit

result = await self._sendcommand(DEVICE_SET_EXTENDED_KEY + "08" + duration_key)

return result[0] == 1
return self._check_command_result(result, 0, {1})

async def get_basic_info(self) -> dict[str, Any] | None:
"""Get device basic settings."""
Expand Down
54 changes: 7 additions & 47 deletions switchbot/devices/bulb.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,58 +26,22 @@
_LOGGER = logging.getLogger(__name__)

from .device import ColorMode
from .base_light import SwitchbotBaseLight


class SwitchbotBulb(SwitchbotSequenceDevice):
class SwitchbotBulb(SwitchbotBaseLight):
"""Representation of a Switchbot bulb."""

def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Switchbot bulb constructor."""
super().__init__(*args, **kwargs)
self._state: dict[str, Any] = {}

@property
def on(self) -> bool | None:
"""Return if bulb is on."""
return self.is_on()

@property
def rgb(self) -> tuple[int, int, int] | None:
"""Return the current rgb value."""
if "r" not in self._state or "g" not in self._state or "b" not in self._state:
return None
return self._state["r"], self._state["g"], self._state["b"]

@property
def color_temp(self) -> int | None:
"""Return the current color temp value."""
return self._state.get("cw") or self.min_temp

@property
def brightness(self) -> int | None:
"""Return the current brightness value."""
return self._get_adv_value("brightness") or 0

@property
def color_mode(self) -> ColorMode:
"""Return the current color mode."""
return ColorMode(self._get_adv_value("color_mode") or 0)

@property
def color_modes(self) -> set[ColorMode]:
"""Return the supported color modes."""
return {ColorMode.RGB, ColorMode.COLOR_TEMP}

@property
def min_temp(self) -> int:
"""Return minimum color temp."""
return 2700

@property
def max_temp(self) -> int:
"""Return maximum color temp."""
return 6500

async def update(self) -> None:
"""Update state of device."""
result = await self._sendcommand(BULB_REQUEST)
Expand All @@ -87,20 +51,20 @@ async def turn_on(self) -> bool:
"""Turn device on."""
result = await self._sendcommand(BULB_ON_KEY)
self._update_state(result)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

async def turn_off(self) -> bool:
"""Turn device off."""
result = await self._sendcommand(BULB_OFF_KEY)
self._update_state(result)
return result[1] == 0x00
return self._check_command_result(result, 1, {0x00})

async def set_brightness(self, brightness: int) -> bool:
"""Set brightness."""
assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
result = await self._sendcommand(f"{BRIGHTNESS_KEY}{brightness:02X}")
self._update_state(result)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

async def set_color_temp(self, brightness: int, color_temp: int) -> bool:
"""Set color temp."""
Expand All @@ -110,7 +74,7 @@ async def set_color_temp(self, brightness: int, color_temp: int) -> bool:
f"{CW_BRIGHTNESS_KEY}{brightness:02X}{color_temp:04X}"
)
self._update_state(result)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

async def set_rgb(self, brightness: int, r: int, g: int, b: int) -> bool:
"""Set rgb."""
Expand All @@ -122,11 +86,7 @@ async def set_rgb(self, brightness: int, r: int, g: int, b: int) -> bool:
f"{RGB_BRIGHTNESS_KEY}{brightness:02X}{r:02X}{g:02X}{b:02X}"
)
self._update_state(result)
return result[1] == 0x80

def is_on(self) -> bool | None:
"""Return bulb state from cache."""
return self._get_adv_value("isOn")
return self._check_command_result(result, 1, {0x80})

def _update_state(self, result: bytes) -> None:
"""Update device state."""
Expand Down
8 changes: 4 additions & 4 deletions switchbot/devices/curtain.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,24 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:
async def open(self) -> bool:
"""Send open command."""
result = await self._sendcommand(OPEN_KEY)
return result[0] == 1
return self._check_command_result(result, 0, {1})

async def close(self) -> bool:
"""Send close command."""
result = await self._sendcommand(CLOSE_KEY)
return result[0] == 1
return self._check_command_result(result, 0, {1})

async def stop(self) -> bool:
"""Send stop command to device."""
result = await self._sendcommand(STOP_KEY)
return result[0] == 1
return self._check_command_result(result, 0, {1})

async def set_position(self, position: int) -> bool:
"""Send position command (0-100) to device."""
position = (100 - position) if self._reverse else position
hex_position = "%0.2X" % position
result = await self._sendcommand(POSITION_KEY + hex_position)
return result[0] == 1
return self._check_command_result(result, 0, {1})

async def update(self, interface: int | None = None) -> None:
"""Update position, battery percent and light level of device."""
Expand Down
16 changes: 16 additions & 0 deletions switchbot/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ class CharacteristicMissingError(Exception):
"""Raised when a characteristic is missing."""


class SwitchbotOperationError(Exception):
"""Raised when an operation fails."""


def _sb_uuid(comms_type: str = "service") -> UUID | str:
"""Return Switchbot UUID."""

Expand Down Expand Up @@ -406,8 +410,20 @@ def _unsub() -> None:
async def update(self) -> None:
"""Update state of device."""

def _check_command_result(
self, result: bytes, index: int, values: set[int]
) -> bool:
"""Check command result."""
if not result:
raise SwitchbotOperationError(
f"{self.name}: Sending command failed (rssi={self.rssi})"
)
return result[index] in values


class SwitchbotSequenceDevice(SwitchbotDevice):
"""A Switchbot sequence device."""

def update_from_advertisement(self, advertisement: SwitchBotAdvertisement) -> None:
"""Update device data from advertisement."""
current_state = self._get_adv_value("sequence_number")
Expand Down
8 changes: 4 additions & 4 deletions switchbot/devices/light_strip.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,20 @@ async def turn_on(self) -> bool:
"""Turn device on."""
result = await self._sendcommand(STRIP_ON_KEY)
self._update_state(result)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

async def turn_off(self) -> bool:
"""Turn device off."""
result = await self._sendcommand(STRIP_OFF_KEY)
self._update_state(result)
return result[1] == 0x00
return self._check_command_result(result, 1, {0x00})

async def set_brightness(self, brightness: int) -> bool:
"""Set brightness."""
assert 0 <= brightness <= 100, "Brightness must be between 0 and 100"
result = await self._sendcommand(f"{BRIGHTNESS_KEY}{brightness:02X}")
self._update_state(result)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

async def set_color_temp(self, brightness: int, color_temp: int) -> bool:
"""Set color temp."""
Expand All @@ -72,7 +72,7 @@ async def set_rgb(self, brightness: int, r: int, g: int, b: int) -> bool:
f"{RGB_BRIGHTNESS_KEY}{brightness:02X}{r:02X}{g:02X}{b:02X}"
)
self._update_state(result)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

def _update_state(self, result: bytes) -> None:
"""Update device state."""
Expand Down
4 changes: 2 additions & 2 deletions switchbot/devices/plug.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ async def update(self, interface: int | None = None) -> None:
async def turn_on(self) -> bool:
"""Turn device on."""
result = await self._sendcommand(PLUG_ON_KEY)
return result[1] == 0x80
return self._check_command_result(result, 1, {0x80})

async def turn_off(self) -> bool:
"""Turn device off."""
result = await self._sendcommand(PLUG_OFF_KEY)
return result[1] == 0x00
return self._check_command_result(result, 1, {0x00})

def is_on(self) -> bool | None:
"""Return switch state from cache."""
Expand Down