diff --git a/switchbot/__init__.py b/switchbot/__init__.py index f5981fb6..01d18b37 100644 --- a/switchbot/__init__.py +++ b/switchbot/__init__.py @@ -49,6 +49,7 @@ from .devices.light_strip import ( SwitchbotLightStrip, SwitchbotRgbicLight, + SwitchbotRgbicNeonLight, SwitchbotStripLight3, ) from .devices.lock import SwitchbotLock @@ -111,6 +112,7 @@ "SwitchbotRelaySwitch", "SwitchbotRelaySwitch2PM", "SwitchbotRgbicLight", + "SwitchbotRgbicNeonLight", "SwitchbotRollerShade", "SwitchbotSmartThermostatRadiator", "SwitchbotStripLight3", diff --git a/switchbot/adv_parser.py b/switchbot/adv_parser.py index 7c121029..93521867 100644 --- a/switchbot/adv_parser.py +++ b/switchbot/adv_parser.py @@ -659,6 +659,30 @@ class SwitchbotSupportedType(TypedDict): "func": process_rgbic_light, "manufacturer_id": 2409, }, + b"\x00\x10\xd0\xb5": { + "modelName": SwitchbotModel.RGBIC_NEON_WIRE_ROPE_LIGHT, + "modelFriendlyName": "RGBIC Neon Wire Rope Light", + "func": process_wostrip, + "manufacturer_id": 2409, + }, + b"\x00\x10\xd0\xb6": { + "modelName": SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, + "modelFriendlyName": "RGBIC Neon Rope Light", + "func": process_wostrip, + "manufacturer_id": 2409, + }, + b"\x01\x10\xd0\xb5": { + "modelName": SwitchbotModel.RGBIC_NEON_WIRE_ROPE_LIGHT, + "modelFriendlyName": "RGBIC Neon Wire Rope Light", + "func": process_wostrip, + "manufacturer_id": 2409, + }, + b"\x01\x10\xd0\xb6": { + "modelName": SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, + "modelFriendlyName": "RGBIC Neon Rope Light", + "func": process_wostrip, + "manufacturer_id": 2409, + }, b"\x00\x10\xfb\xa8": { "modelName": SwitchbotModel.K11_VACUUM, "modelFriendlyName": "K11+ Vacuum", diff --git a/switchbot/const/__init__.py b/switchbot/const/__init__.py index 4e6e2512..bff99166 100644 --- a/switchbot/const/__init__.py +++ b/switchbot/const/__init__.py @@ -97,6 +97,8 @@ class SwitchbotModel(StrEnum): PLUG_MINI_EU = "Plug Mini (EU)" RGBICWW_STRIP_LIGHT = "RGBICWW Strip Light" RGBICWW_FLOOR_LAMP = "RGBICWW Floor Lamp" + RGBIC_NEON_ROPE_LIGHT = "RGBIC Neon Rope Light" + RGBIC_NEON_WIRE_ROPE_LIGHT = "RGBIC Neon Wire Rope Light" K11_VACUUM = "K11+ Vacuum" CLIMATE_PANEL = "Climate Panel" SMART_THERMOSTAT_RADIATOR = "Smart Thermostat Radiator" diff --git a/switchbot/devices/light_strip.py b/switchbot/devices/light_strip.py index 7a5dd250..4fa9b936 100644 --- a/switchbot/devices/light_strip.py +++ b/switchbot/devices/light_strip.py @@ -343,3 +343,43 @@ def color_mode(self) -> ColorMode: """Return the current color mode.""" device_mode = RGBICStripLightColorMode(self._get_adv_value("color_mode") or 10) return _RGBICWW_STRIP_LIGHT_COLOR_MODE_MAP.get(device_mode, ColorMode.OFF) + + +class SwitchbotRgbicNeonLight(SwitchbotEncryptedDevice, SwitchbotLightStrip): + """Support for Switchbot RGBIC Neon lights.""" + + _effect_dict = RGBIC_EFFECTS + + def __init__( + self, + device: BLEDevice, + key_id: str, + encryption_key: str, + interface: int = 0, + model: SwitchbotModel = SwitchbotModel.RGBICWW_STRIP_LIGHT, + **kwargs: Any, + ) -> None: + super().__init__(device, key_id, encryption_key, model, interface, **kwargs) + + @classmethod + async def verify_encryption_key( + cls, + device: BLEDevice, + key_id: str, + encryption_key: str, + model: SwitchbotModel = SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, + **kwargs: Any, + ) -> bool: + return await super().verify_encryption_key( + device, key_id, encryption_key, model, **kwargs + ) + + @property + def color_modes(self) -> set[ColorMode]: + """Return the supported color modes.""" + return {ColorMode.RGB} + + @property + def color_mode(self) -> ColorMode: + """Return the current color mode.""" + return ColorMode.RGB diff --git a/tests/__init__.py b/tests/__init__.py index e6ea3e06..762ec029 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -81,6 +81,22 @@ class AdvTestCase: SwitchbotModel.RGBICWW_FLOOR_LAMP, ) +RGBIC_NEON_LIGHT_INFO = AdvTestCase( + b'\xdc\x06u\xa6\xfb\xb2y\x9e"\x00\x11\xb8\x00', + b"\x00\x00\x00\x00\x10\xd0\xb6", + { + "sequence_number": 121, + "isOn": True, + "brightness": 30, + "delay": False, + "network_state": 2, + "color_mode": 2, + }, + b"\x00\x10\xd0\xb6", + "Rgbic Neon Rope Light", + SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, +) + SMART_THERMOSTAT_RADIATOR_INFO = AdvTestCase( b"\xb0\xe9\xfe\xa2T|6\xe4\x00\x9c\xa3A\x00", diff --git a/tests/test_adv_parser.py b/tests/test_adv_parser.py index c71c3b30..259cadf0 100644 --- a/tests/test_adv_parser.py +++ b/tests/test_adv_parser.py @@ -3575,6 +3575,21 @@ def test_humidifer_with_empty_data() -> None: "RGBICWW Strip Light", SwitchbotModel.RGBICWW_STRIP_LIGHT, ), + AdvTestCase( + b"@L\xca!pz/\x8b'\x00\x11:\x00", + b"\x00\x00\x00\x00\x10\xd0\xb6", + { + "sequence_number": 47, + "isOn": True, + "brightness": 11, + "delay": False, + "network_state": 2, + "color_mode": 7, + }, + b"\x00\x10\xd0\xb6", + "RGBIC Neon Rope Light", + SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, + ), AdvTestCase( b"\xb0\xe9\xfe\xe4\xbf\xd8\x0b\x01\x11f\x00\x16M\x15", b"\x00\x00M\x00\x10\xfb\xa8", @@ -3947,6 +3962,21 @@ def test_adv_active(test_case: AdvTestCase) -> None: "RGBICWW Strip Light", SwitchbotModel.RGBICWW_STRIP_LIGHT, ), + AdvTestCase( + b"@L\xca!pz/\x8b'\x00\x11:\x00", + None, + { + "sequence_number": 47, + "isOn": True, + "brightness": 11, + "delay": False, + "network_state": 2, + "color_mode": 7, + }, + b"\x00\x10\xd0\xb6", + "RGBIC Neon Rope Light", + SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, + ), AdvTestCase( b"\xb0\xe9\xfe\xe4\xbf\xd8\x0b\x01\x11f\x00\x16M\x15", None, @@ -4300,6 +4330,14 @@ def test_adv_passive(test_case: AdvTestCase) -> None: "Keypad Vision", SwitchbotModel.KEYPAD_VISION, ), + AdvTestCase( + None, + b"\x00\x00\x00\x00\x10\xd0\xb6", + {}, + b"\x00\x10\xd0\xb6", + "RGBIC Neon Rope Light", + SwitchbotModel.RGBIC_NEON_ROPE_LIGHT, + ), AdvTestCase( None, b"\x00\x00`\x01\x11Q\x98", diff --git a/tests/test_strip_light.py b/tests/test_strip_light.py index bc667157..faa726c2 100644 --- a/tests/test_strip_light.py +++ b/tests/test_strip_light.py @@ -11,6 +11,7 @@ from . import ( FLOOR_LAMP_INFO, + RGBIC_NEON_LIGHT_INFO, RGBICWW_FLOOR_LAMP_INFO, RGBICWW_STRIP_LIGHT_INFO, STRIP_LIGHT_3_INFO, @@ -24,6 +25,7 @@ (FLOOR_LAMP_INFO, light_strip.SwitchbotStripLight3), (RGBICWW_STRIP_LIGHT_INFO, light_strip.SwitchbotRgbicLight), (RGBICWW_FLOOR_LAMP_INFO, light_strip.SwitchbotRgbicLight), + (RGBIC_NEON_LIGHT_INFO, light_strip.SwitchbotRgbicNeonLight), ] ) def device_case(request): @@ -38,6 +40,7 @@ def expected_effects(device_case): SwitchbotModel.FLOOR_LAMP: ("christmas", "halloween", "sunset"), SwitchbotModel.RGBICWW_STRIP_LIGHT: ("romance", "energy", "heartbeat"), SwitchbotModel.RGBICWW_FLOOR_LAMP: ("romance", "energy", "heartbeat"), + SwitchbotModel.RGBIC_NEON_ROPE_LIGHT: ("romance", "energy", "heartbeat"), } return EXPECTED[adv_info.modelName] @@ -96,10 +99,7 @@ async def test_default_info(device_case, expected_effects): assert device.is_on() is True assert device.on is True assert device.color_mode == ColorMode.RGB - assert device.color_modes == { - ColorMode.RGB, - ColorMode.COLOR_TEMP, - } + assert ColorMode.RGB in device.color_modes assert device.rgb == (30, 0, 0) assert device.color_temp == 3200 assert device.brightness == adv_info.data["brightness"]