Skip to content

Commit 02b3632

Browse files
greyeeebdraco
andauthored
Fix the voltage value of the relay switch (#272)
Co-authored-by: J. Nick Koston <[email protected]>
1 parent 6f297c9 commit 02b3632

File tree

6 files changed

+164
-9
lines changed

6 files changed

+164
-9
lines changed

switchbot/adv_parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from .adv_parsers.motion import process_wopresence
2626
from .adv_parsers.plug import process_woplugmini
2727
from .adv_parsers.relay_switch import (
28-
process_worelay_switch_1plus,
28+
process_worelay_switch_1,
2929
process_worelay_switch_1pm,
3030
)
3131
from .const import SwitchbotModel
@@ -191,9 +191,9 @@ class SwitchbotSupportedType(TypedDict):
191191
"manufacturer_id": 2409,
192192
},
193193
";": {
194-
"modelName": SwitchbotModel.RELAY_SWITCH_1_PLUS,
194+
"modelName": SwitchbotModel.RELAY_SWITCH_1,
195195
"modelFriendlyName": "Relay Switch 1",
196-
"func": process_worelay_switch_1plus,
196+
"func": process_worelay_switch_1,
197197
"manufacturer_id": 2409,
198198
},
199199
}

switchbot/adv_parsers/relay_switch.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def process_worelay_switch_1pm(
1919
}
2020

2121

22-
def process_worelay_switch_1plus(
22+
def process_worelay_switch_1(
2323
data: bytes | None, mfr_data: bytes | None
2424
) -> dict[str, bool | int]:
2525
"""Process WoStrip services data."""

switchbot/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class SwitchbotModel(StrEnum):
5555
HUB2 = "WoHub2"
5656
KEYPAD = "WoKeypad"
5757
RELAY_SWITCH_1PM = "Relay Switch 1PM"
58-
RELAY_SWITCH_1_PLUS = "Relay Switch 1"
58+
RELAY_SWITCH_1 = "Relay Switch 1"
5959

6060

6161
class LockStatus(Enum):

switchbot/devices/relay_switch.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1+
import asyncio
2+
import logging
13
import time
24
from typing import Any
35

46
from bleak.backends.device import BLEDevice
57
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
68

79
from ..const import SwitchbotModel
8-
from .device import SwitchbotSequenceDevice
10+
from ..models import SwitchBotAdvertisement
11+
from .device import SwitchbotDevice
12+
13+
_LOGGER = logging.getLogger(__name__)
914

1015
COMMAND_HEADER = "57"
1116
COMMAND_GET_CK_IV = f"{COMMAND_HEADER}0f2103"
1217
COMMAND_TURN_OFF = f"{COMMAND_HEADER}0f70010000"
1318
COMMAND_TURN_ON = f"{COMMAND_HEADER}0f70010100"
1419
COMMAND_TOGGLE = f"{COMMAND_HEADER}0f70010200"
1520
COMMAND_GET_VOLTAGE_AND_CURRENT = f"{COMMAND_HEADER}0f7106000000"
16-
PASSIVE_POLL_INTERVAL = 1 * 60
21+
PASSIVE_POLL_INTERVAL = 10 * 60
1722

1823

19-
class SwitchbotRelaySwitch(SwitchbotSequenceDevice):
24+
class SwitchbotRelaySwitch(SwitchbotDevice):
2025
"""Representation of a Switchbot relay switch 1pm."""
2126

2227
def __init__(
@@ -41,8 +46,30 @@ def __init__(
4146
self._key_id = key_id
4247
self._encryption_key = bytearray.fromhex(encryption_key)
4348
self._model: SwitchbotModel = model
49+
self._force_next_update = False
4450
super().__init__(device, None, interface, **kwargs)
4551

52+
def update_from_advertisement(self, advertisement: SwitchBotAdvertisement) -> None:
53+
"""Update device data from advertisement."""
54+
# Obtain voltage and current through command.
55+
adv_data = advertisement.data["data"]
56+
if previous_voltage := self._get_adv_value("voltage"):
57+
adv_data["voltage"] = previous_voltage
58+
if previous_current := self._get_adv_value("current"):
59+
adv_data["current"] = previous_current
60+
current_state = self._get_adv_value("sequence_number")
61+
super().update_from_advertisement(advertisement)
62+
new_state = self._get_adv_value("sequence_number")
63+
_LOGGER.debug(
64+
"%s: update advertisement: %s (seq before: %s) (seq after: %s)",
65+
self.name,
66+
advertisement,
67+
current_state,
68+
new_state,
69+
)
70+
if current_state != new_state:
71+
self._force_next_update = True
72+
4673
async def update(self, interface: int | None = None) -> None:
4774
"""Update state of device."""
4875
if info := await self.get_voltage_and_current():
@@ -56,13 +83,16 @@ async def get_voltage_and_current(self) -> dict[str, Any] | None:
5683
ok = self._check_command_result(result, 0, {1})
5784
if ok:
5885
return {
59-
"voltage": (result[9] << 8) + result[10],
86+
"voltage": ((result[9] << 8) + result[10]) / 10,
6087
"current": (result[11] << 8) + result[12],
6188
}
6289
return None
6390

6491
def poll_needed(self, seconds_since_last_poll: float | None) -> bool:
6592
"""Return if device needs polling."""
93+
if self._force_next_update:
94+
self._force_next_update = False
95+
return True
6696
if (
6797
seconds_since_last_poll is not None
6898
and seconds_since_last_poll < PASSIVE_POLL_INTERVAL

tests/test_adv_parser.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,3 +1672,68 @@ def test_parse_advertisement_data_keypad():
16721672
rssi=-67,
16731673
active=True,
16741674
)
1675+
1676+
1677+
def test_parse_advertisement_data_relay_switch_1pm():
1678+
"""Test parse_advertisement_data for the keypad."""
1679+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
1680+
adv_data = generate_advertisement_data(
1681+
manufacturer_data={2409: b"$X|\x0866G\x81\x00\x00\x001\x00\x00\x00\x00"},
1682+
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b"<\x00\x00\x00"},
1683+
rssi=-67,
1684+
)
1685+
result = parse_advertisement_data(
1686+
ble_device, adv_data, SwitchbotModel.RELAY_SWITCH_1PM
1687+
)
1688+
assert result == SwitchBotAdvertisement(
1689+
address="aa:bb:cc:dd:ee:ff",
1690+
data={
1691+
"data": {
1692+
"switchMode": True,
1693+
"sequence_number": 71,
1694+
"isOn": True,
1695+
"power": 4.9,
1696+
"voltage": 0,
1697+
"current": 0,
1698+
},
1699+
"isEncrypted": False,
1700+
"model": "<",
1701+
"modelFriendlyName": "Relay Switch 1PM",
1702+
"modelName": SwitchbotModel.RELAY_SWITCH_1PM,
1703+
"rawAdvData": b"<\x00\x00\x00",
1704+
},
1705+
device=ble_device,
1706+
rssi=-67,
1707+
active=True,
1708+
)
1709+
1710+
1711+
def test_parse_advertisement_data_relay_switch_1():
1712+
"""Test parse_advertisement_data for the keypad."""
1713+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
1714+
adv_data = generate_advertisement_data(
1715+
manufacturer_data={2409: b"$X|\x0866G\x81\x00\x00\x001\x00\x00\x00\x00"},
1716+
service_data={"0000fd3d-0000-1000-8000-00805f9b34fb": b";\x00\x00\x00"},
1717+
rssi=-67,
1718+
)
1719+
result = parse_advertisement_data(
1720+
ble_device, adv_data, SwitchbotModel.RELAY_SWITCH_1
1721+
)
1722+
assert result == SwitchBotAdvertisement(
1723+
address="aa:bb:cc:dd:ee:ff",
1724+
data={
1725+
"data": {
1726+
"switchMode": True,
1727+
"sequence_number": 71,
1728+
"isOn": True,
1729+
},
1730+
"isEncrypted": False,
1731+
"model": ";",
1732+
"modelFriendlyName": "Relay Switch 1",
1733+
"modelName": SwitchbotModel.RELAY_SWITCH_1,
1734+
"rawAdvData": b";\x00\x00\x00",
1735+
},
1736+
device=ble_device,
1737+
rssi=-67,
1738+
active=True,
1739+
)

tests/test_relay_switch.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from unittest.mock import AsyncMock
2+
3+
import pytest
4+
from bleak.backends.device import BLEDevice
5+
6+
from switchbot import SwitchBotAdvertisement, SwitchbotModel
7+
from switchbot.devices import relay_switch
8+
9+
from .test_adv_parser import generate_ble_device
10+
11+
12+
def create_device_for_command_testing(calibration=True, reverse_mode=False):
13+
ble_device = generate_ble_device("aa:bb:cc:dd:ee:ff", "any")
14+
relay_switch_device = relay_switch.SwitchbotRelaySwitch(
15+
ble_device, "ff", "ffffffffffffffffffffffffffffffff"
16+
)
17+
relay_switch_device.update_from_advertisement(make_advertisement_data(ble_device))
18+
return relay_switch_device
19+
20+
21+
def make_advertisement_data(ble_device: BLEDevice):
22+
"""Set advertisement data with defaults."""
23+
24+
return SwitchBotAdvertisement(
25+
address="aa:bb:cc:dd:ee:ff",
26+
data={
27+
"rawAdvData": b"$X|\x0866G\x81\x00\x00\x001\x00\x00\x00\x00",
28+
"data": {
29+
"switchMode": True,
30+
"sequence_number": 71,
31+
"isOn": True,
32+
"power": 4.9,
33+
"voltage": 0,
34+
"current": 0,
35+
},
36+
"isEncrypted": False,
37+
"model": "<",
38+
"modelFriendlyName": "Relay Switch 1PM",
39+
"modelName": SwitchbotModel.RELAY_SWITCH_1PM,
40+
},
41+
device=ble_device,
42+
rssi=-80,
43+
active=True,
44+
)
45+
46+
47+
@pytest.mark.asyncio
48+
async def test_turn_on():
49+
relay_switch_device = create_device_for_command_testing()
50+
relay_switch_device._send_command = AsyncMock(return_value=b"\x01")
51+
await relay_switch_device.turn_on()
52+
assert relay_switch_device.is_on() is True
53+
54+
55+
@pytest.mark.asyncio
56+
async def test_trun_off():
57+
relay_switch_device = create_device_for_command_testing()
58+
relay_switch_device._send_command = AsyncMock(return_value=b"\x01")
59+
await relay_switch_device.turn_off()
60+
assert relay_switch_device.is_on() is False

0 commit comments

Comments
 (0)