Skip to content
Open
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
2 changes: 2 additions & 0 deletions switchbot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
from .devices.light_strip import (
SwitchbotLightStrip,
SwitchbotRgbicLight,
SwitchbotRgbicNeonLight,
SwitchbotStripLight3,
)
from .devices.lock import SwitchbotLock
Expand Down Expand Up @@ -111,6 +112,7 @@
"SwitchbotRelaySwitch",
"SwitchbotRelaySwitch2PM",
"SwitchbotRgbicLight",
"SwitchbotRgbicNeonLight",
"SwitchbotRollerShade",
"SwitchbotSmartThermostatRadiator",
"SwitchbotStripLight3",
Expand Down
24 changes: 24 additions & 0 deletions switchbot/adv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions switchbot/const/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
40 changes: 40 additions & 0 deletions switchbot/devices/light_strip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Comment on lines +365 to +375
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verify_encryption_key on SwitchbotRgbicNeonLight also defaults model to SwitchbotModel.RGBICWW_STRIP_LIGHT. This should align with the neon model(s), otherwise encryption verification may be performed against the wrong model identifier when callers use the default.

Copilot uses AI. Check for mistakes.

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

Comment on lines +377 to +381
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwitchbotRgbicNeonLight.color_modes reports RGB-only support, but the class still inherits _set_color_temp_command from SwitchbotLightStrip, making set_color_temp appear supported. If neon devices truly don't support color temperature, override _set_color_temp_command to an empty string (or override set_color_temp to raise) so unsupported operations fail fast and match color_modes.

Copilot uses AI. Check for mistakes.
@property
def color_mode(self) -> ColorMode:
"""Return the current color mode."""
return ColorMode.RGB
16 changes: 16 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
38 changes: 38 additions & 0 deletions tests/test_adv_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
Comment on lines +3578 to +3592
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description mentions both "RGBIC Neon Rope Light" and "RGBIC Neon Wire Rope Light", but tests only cover SwitchbotModel.RGBIC_NEON_ROPE_LIGHT. Add analogous advertisement parsing test cases for the wire rope light model IDs (...\xd0\xb5) so both additions are validated.

Copilot uses AI. Check for mistakes.
AdvTestCase(
b"\xb0\xe9\xfe\xe4\xbf\xd8\x0b\x01\x11f\x00\x16M\x15",
b"\x00\x00M\x00\x10\xfb\xa8",
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions tests/test_strip_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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),
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixture adds SwitchbotRgbicNeonLight into the same parametrized device set as devices that support color temperature. Many of the tests below assume color-temp support (e.g., set/validate color_temp and set_color_temp), which is inconsistent with SwitchbotRgbicNeonLight.color_modes returning only ColorMode.RGB. Consider splitting the fixture (or parametrizing expected capabilities) so tests only exercise operations supported by each device model.

Suggested change
(RGBIC_NEON_LIGHT_INFO, light_strip.SwitchbotRgbicNeonLight),

Copilot uses AI. Check for mistakes.
]
)
def device_case(request):
Expand All @@ -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]

Expand Down Expand Up @@ -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)
Comment on lines 99 to 103
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color_modes assertion was weakened from an exact set check to only verifying ColorMode.RGB is present. This reduces coverage for models that should also support ColorMode.COLOR_TEMP (e.g., Strip Light 3 / RGBICWW devices). Prefer asserting the full expected set per model so regressions in supported modes are caught, while still allowing neon to be RGB-only.

Copilot uses AI. Check for mistakes.
assert device.color_temp == 3200
assert device.brightness == adv_info.data["brightness"]
Expand Down
Loading