Skip to content

Commit

Permalink
feat: add Light sl02 and sl02 mini support (#805)
Browse files Browse the repository at this point in the history
* feat: add Light sl02 and sl02 mini support

* Update README.md

* Update README.md

Signed-off-by: YogevBokobza <yogevbokobza12@gmail.com>

* Update bridge.py, test_device_enum_helpers.py, and test_device_tools.py

* Update tools.py

* Update test_device_tools.py

* Update tools.py

* Update tools.py

* Update test_device_tools.py

* Update tools.py

* Update scripts.md and control_device.py

* Update test_device_parsing.py and test_a_dual_light_datagram_produces_device.txt

* Update test_device_parsing.py

* fix based on requested changes

---------

Signed-off-by: YogevBokobza <yogevbokobza12@gmail.com>
  • Loading branch information
YogevBokobza authored Oct 29, 2024
1 parent 5664360 commit 3963ab8
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 8 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ asyncio.run(print_devices(60))

asyncio.run(control_light(DeviceType.LIGHT_SL01, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
asyncio.run(control_light(DeviceType.LIGHT_SL01_MINI, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
asyncio.run(control_light(DeviceType.LIGHT_SL02, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
asyncio.run(control_light(DeviceType.LIGHT_SL02_MINI, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
```

</details>
Expand Down
26 changes: 25 additions & 1 deletion docs/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You can change the delay by passing an int argument: discover_devices.py 30

Switcher devices uses two protocol types:
Protocol type 1 (UDP port 20002 or port 10002), used by: Switcher Mini, Switcher Power Plug, Switcher Touch, Switcher V2 (esp), Switcher V2 (qualcomm), Switcher V4
Protocol type 2 (UDP port 20003 or port 10003), used by: Switcher Breeze, Switcher Runner, Switcher Runner Mini, Switcher Runner S11, Switcher Runner S12, Switcher Light SL01, Switcher Light SL01 Mini
Protocol type 2 (UDP port 20003 or port 10003), used by: Switcher Breeze, Switcher Runner, Switcher Runner Mini, Switcher Runner S11, Switcher Runner S12, Switcher Light SL01, Switcher Light SL01 Mini, Switcher Light SL02, Switcher Light SL02 Mini
You can change the scanned protocol type by passing an int argument: discover_devices.py -t 1

Note:
Expand Down Expand Up @@ -147,6 +147,14 @@ python control_device.py get_light_state -c "Switcher Light SL01" -k "zvVvd7JxtN
python control_device.py get_light_state -c "Switcher Light SL01 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"
python control_device.py get_light_state -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py get_light_state -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
python control_device.py get_light_state -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py get_light_state -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
python control_device.py turn_on_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py turn_on_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
Expand All @@ -157,6 +165,14 @@ python control_device.py turn_on_light -c "Switcher Light SL01" -k "zvVvd7JxtN7C
python control_device.py turn_on_light -c "Switcher Light SL01 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"
python control_device.py turn_on_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py turn_on_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
python control_device.py turn_on_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py turn_on_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
python control_device.py turn_off_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py turn_off_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
Expand All @@ -167,6 +183,14 @@ python control_device.py turn_off_light -c "Switcher Light SL01" -k "zvVvd7JxtN7
python control_device.py turn_off_light -c "Switcher Light SL01 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"
python control_device.py turn_off_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py turn_off_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
python control_device.py turn_off_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0
python control_device.py turn_off_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1
python control_device.py get_thermostat_state -c "Switcher Breeze" -d 3a20b7 -i "192.168.50.77"
python control_device.py control_thermostat -c "Switcher Breeze" -d 3a20b7 -i "192.168.50.77" -r ELEC7001 -s on
Expand Down
2 changes: 2 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ asyncio.run(

asyncio.run(control_light(DeviceType.LIGHT_SL01, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
asyncio.run(control_light(DeviceType.LIGHT_SL01_MINI, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
asyncio.run(control_light(DeviceType.LIGHT_SL02, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
asyncio.run(control_light(DeviceType.LIGHT_SL02_MINI, "111.222.11.22", "ab1c2d", "00", "zvVvd7JxtN7CgvkD1Psujw=="))
```
1. [SwitcherLightStateResponse](./codedocs.md#src.aioswitcher.api.messages.SwitcherLightStateResponse)
2. [SwitcherBaseResponse](./codedocs.md#src.aioswitcher.api.messages.SwitcherBaseResponse)
Expand Down
12 changes: 12 additions & 0 deletions scripts/control_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,28 @@
python control_device.py get_light_state -c "Switcher Runner S12" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py get_light_state -c "Switcher Light SL01" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py get_light_state -c "Switcher Light SL01 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py get_light_state -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py get_light_state -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py get_light_state -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py get_light_state -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py turn_on_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py turn_on_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py turn_on_light -c "Switcher Runner S12" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py turn_on_light -c "Switcher Light SL01" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py turn_on_light -c "Switcher Light SL01 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py turn_on_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py turn_on_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py turn_on_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py turn_on_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py turn_off_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py turn_off_light -c "Switcher Runner S11" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py turn_off_light -c "Switcher Runner S12" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py turn_off_light -c "Switcher Light SL01" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py turn_off_light -c "Switcher Light SL01 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22"\n
python control_device.py turn_off_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py turn_off_light -c "Switcher Light SL02" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py turn_off_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 0\n
python control_device.py turn_off_light -c "Switcher Light SL02 Mini" -k "zvVvd7JxtN7CgvkD1Psujw==" -d ab1c2d -i "111.222.11.22" -x 1\n
python control_device.py get_thermostat_state -c "Switcher Breeze" -d 3a20b7 -i "192.168.50.77"\n
Expand Down
37 changes: 35 additions & 2 deletions src/aioswitcher/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,12 @@ def _parse_device_from_datagram(
parser.get_thermostat_remote_id(),
)
)
elif device_type and device_type.category == DeviceCategory.LIGHT:

elif (
device_type
and device_type.category == DeviceCategory.LIGHT
and device_type in (DeviceType.LIGHT_SL01, DeviceType.LIGHT_SL01_MINI)
):
logger.debug("discovered a Light SL01 switcher device")
device_callback(
SwitcherLight(
Expand All @@ -278,6 +283,33 @@ def _parse_device_from_datagram(
],
)
)

elif (
device_type
and device_type.category == DeviceCategory.LIGHT
and device_type in (DeviceType.LIGHT_SL02, DeviceType.LIGHT_SL02_MINI)
):
logger.debug("discovered a Light SL02 switcher device")
device_callback(
SwitcherLight(
device_type,
DeviceState.ON,
parser.get_device_id(),
parser.get_device_key(),
parser.get_ip_type2(),
parser.get_mac_type2(),
parser.get_name(),
device_type.token_needed,
[
parser.get_light_state(
get_light_discovery_packet_index(device_type, 0)
),
parser.get_light_state(
get_light_discovery_packet_index(device_type, 1)
),
],
)
)
else:
warn("discovered an unknown switcher device")

Expand Down Expand Up @@ -408,7 +440,8 @@ def is_switcher_originator(self) -> bool:
or len(self.message) == 159 # Switcher Runner and RunnerMini
or len(self.message) == 203 # Switcher Runner S11 and Switcher Runner S12
or len(self.message)
== 207 # Switcher Light SL01 and Switcher Light SL01 Mini
== 207 # Switcher Light SL01, Switcher Light SL01 Mini,
# Switcher Light SL02 and Switcher Light SL02 Mini
)

def get_ip_type1(self) -> str:
Expand Down
14 changes: 14 additions & 0 deletions src/aioswitcher/device/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,20 @@ class DeviceType(Enum):
DeviceCategory.LIGHT,
True,
)
LIGHT_SL02 = (
"Switcher Light SL02",
"0f05",
2,
DeviceCategory.LIGHT,
True,
)
LIGHT_SL02_MINI = (
"Switcher Light SL02 Mini",
"0f08",
2,
DeviceCategory.LIGHT,
True,
)

def __new__(
cls,
Expand Down
13 changes: 12 additions & 1 deletion src/aioswitcher/device/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ def convert_str_to_devicetype(device_type: str) -> DeviceType:
return DeviceType.LIGHT_SL01
elif device_type == DeviceType.LIGHT_SL01_MINI.value:
return DeviceType.LIGHT_SL01_MINI
elif device_type == DeviceType.LIGHT_SL02.value:
return DeviceType.LIGHT_SL02
elif device_type == DeviceType.LIGHT_SL02_MINI.value:
return DeviceType.LIGHT_SL02_MINI
return DeviceType.MINI


Expand Down Expand Up @@ -241,6 +245,9 @@ async def validate_token(username: str, token: str) -> bool:
# Lights circuit is 0, shutter circuits are numbered 1 & 2.
# Light SL01 and Light SL01 Mini: has one lights circuits & has no shutter circuits ->
# Lights circuit is 0, get_shutter_discovery_packet_index would raise an error.
# Light SL02 and Light SL02 Mini: has two lights circuits & has no shutter circuits ->
# Lights circuits are numbered 0 & 1,
# get_shutter_discovery_packet_index would raise an error.
def get_shutter_discovery_packet_index(
device_type: DeviceType, circuit_number: int
) -> int:
Expand Down Expand Up @@ -272,7 +279,11 @@ def get_light_discovery_packet_index(
Used in retriving the light on/off status from the packet
(based of device type and circuit number).
"""
if device_type == DeviceType.RUNNER_S11:
if device_type in (
DeviceType.RUNNER_S11,
DeviceType.LIGHT_SL02,
DeviceType.LIGHT_SL02_MINI,
):
if circuit_number not in [0, 1]:
raise ValueError("Invalid circuit number")
return circuit_number
Expand Down
4 changes: 3 additions & 1 deletion tests/test_device_enum_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@
(DeviceType.RUNNER_S11, "Switcher Runner S11", "0f01", 2, DeviceCategory.SINGLE_SHUTTER_DUAL_LIGHT, True),
(DeviceType.RUNNER_S12, "Switcher Runner S12", "0f02", 2, DeviceCategory.DUAL_SHUTTER_SINGLE_LIGHT, True),
(DeviceType.LIGHT_SL01, "Switcher Light SL01", "0f04", 2, DeviceCategory.LIGHT, True),
(DeviceType.LIGHT_SL01_MINI, "Switcher Light SL01 Mini", "0f07", 2, DeviceCategory.LIGHT, True)
(DeviceType.LIGHT_SL01_MINI, "Switcher Light SL01 Mini", "0f07", 2, DeviceCategory.LIGHT, True),
(DeviceType.LIGHT_SL02, "Switcher Light SL02", "0f05", 2, DeviceCategory.LIGHT, True),
(DeviceType.LIGHT_SL02_MINI, "Switcher Light SL02 Mini", "0f08", 2, DeviceCategory.LIGHT, True),
],
)
def test_the_given_type_custom_properties_are_returning_the_expected_data(
Expand Down
9 changes: 9 additions & 0 deletions tests/test_device_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,12 @@ def test_a_light_datagram_produces_device(mock_device_cls, mock_device, resource
sut_datagram = Path(f'{resource_path}.txt').read_text().replace('\n', '').encode()
_parse_device_from_datagram(mock_callback, unhexlify(sut_datagram))
mock_callback.assert_called_once_with(mock_device)


@patch.object(SwitcherLight, "__new__")
@patch.object(DatagramParser, "is_switcher_originator", lambda s: True)
def test_a_dual_light_datagram_produces_device(mock_device_cls, mock_device, resource_path, mock_callback):
mock_device_cls.return_value = mock_device
sut_datagram = Path(f'{resource_path}.txt').read_text().replace('\n', '').encode()
_parse_device_from_datagram(mock_callback, unhexlify(sut_datagram))
mock_callback.assert_called_once_with(mock_device)
20 changes: 17 additions & 3 deletions tests/test_device_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ def test_watts_to_amps_with_parameterized_watts_should_procude_expected_amps(wat
("Switcher Runner S11", DeviceType.RUNNER_S11),
("Switcher Runner S12", DeviceType.RUNNER_S12),
("Switcher Light SL01", DeviceType.LIGHT_SL01),
("Switcher Light SL01 Mini", DeviceType.LIGHT_SL01_MINI)
("Switcher Light SL01 Mini", DeviceType.LIGHT_SL01_MINI),
("Switcher Light SL02", DeviceType.LIGHT_SL02),
("Switcher Light SL02 Mini", DeviceType.LIGHT_SL02_MINI),
])
def test_convert_str_to_devicetype_should_return_expected_devicetype(str, type):
assert_that(tools.convert_str_to_devicetype(str)).is_equal_to(type)
Expand Down Expand Up @@ -205,6 +207,10 @@ def test_get_shutter_discovery_packet_index_with_different_device_should_raise_e
(DeviceType.RUNNER_S12, 0, 0),
(DeviceType.LIGHT_SL01, 0, 0),
(DeviceType.LIGHT_SL01_MINI, 0, 0),
(DeviceType.LIGHT_SL02, 0, 0),
(DeviceType.LIGHT_SL02, 1, 1),
(DeviceType.LIGHT_SL02_MINI, 0, 0),
(DeviceType.LIGHT_SL02_MINI, 1, 1),
])
def test_get_light_discovery_packet_index_should_return_expected_index(device_type, circuit_number, index):
assert_that(tools.get_light_discovery_packet_index(device_type, circuit_number)).is_equal_to(index)
Expand All @@ -214,7 +220,9 @@ def test_get_light_discovery_packet_index_should_return_expected_index(device_ty
(DeviceType.RUNNER_S11, 2, ValueError, "Invalid circuit number"),
(DeviceType.RUNNER_S12, 1, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL01, 1, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL01_MINI, 1, ValueError, "Invalid circuit number")
(DeviceType.LIGHT_SL01_MINI, 1, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL02, 2, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL02_MINI, 2, ValueError, "Invalid circuit number"),
])
def test_get_light_discovery_packet_index_with_invalid_circuit_number_should_raise_error(device_type, circuit_number, error, error_msg):
assert_that(tools.get_light_discovery_packet_index).raises(error).when_called_with(
Expand Down Expand Up @@ -273,6 +281,10 @@ def test_get_shutter_api_packet_index_with_different_device_should_raise_error(d
(DeviceType.RUNNER_S12, 0, 1),
(DeviceType.LIGHT_SL01, 0, 1),
(DeviceType.LIGHT_SL01_MINI, 0, 1),
(DeviceType.LIGHT_SL02, 0, 1),
(DeviceType.LIGHT_SL02, 1, 2),
(DeviceType.LIGHT_SL02_MINI, 0, 1),
(DeviceType.LIGHT_SL02_MINI, 1, 2),
])
def test_get_light_api_packet_index_should_return_expected_index(device_type, circuit_number, index):
assert_that(tools.get_light_api_packet_index(device_type, circuit_number)).is_equal_to(index)
Expand All @@ -282,7 +294,9 @@ def test_get_light_api_packet_index_should_return_expected_index(device_type, ci
(DeviceType.RUNNER_S11, 2, ValueError, "Invalid circuit number"),
(DeviceType.RUNNER_S12, 1, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL01, 1, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL01_MINI, 1, ValueError, "Invalid circuit number")
(DeviceType.LIGHT_SL01_MINI, 1, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL02, 2, ValueError, "Invalid circuit number"),
(DeviceType.LIGHT_SL02_MINI, 2, ValueError, "Invalid circuit number"),
])
def test_get_light_api_packet_index_with_invalid_circuit_number_should_raise_error(device_type, circuit_number, error, error_msg):
assert_that(tools.get_light_api_packet_index).raises(error).when_called_with(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fef0cf0004020200000000003b9d01000000b185c80000008d78026700000000000000000000f0fe04005377697463686572204c696768745f33364242000000000000000000000000000f0800c0a801463494549536bb0103000000000000025377697463686572204c696768745f333642420000000000000000000000000002040000450003000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000053fed273

0 comments on commit 3963ab8

Please sign in to comment.