Skip to content

Commit

Permalink
66 support for wyze switch (#104)
Browse files Browse the repository at this point in the history
* Initial support

* Updated documentation for switches
  • Loading branch information
shauntarves authored Dec 9, 2022
1 parent 27b5b66 commit f4a7231
Show file tree
Hide file tree
Showing 14 changed files with 483 additions and 44 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ Whether you're building a custom app, or integrating into a third-party service

The **Python Wyze SDK** allows interaction with:

- `wyze_sdk.client.bulbs`: for controlling Wyze Bulb and Wyze Bulb Color
- `wyze_sdk.client.bulbs`: for controlling Wyze Bulb, Wyze Bulb Color, Wyze Bulb White, and Wyze Light Strip
- `wyze_sdk.client.entry_sensors`: for interacting with Wyze Entry Sensor
- `wyze_sdk.client.cameras`: for interacting with Wyze Cameras
- `wyze_sdk.client.events`: for managing Wyze alarm events
- `wyze_sdk.client.locks`: for interacting with Wyze Lock
- `wyze_sdk.client.locks`: for interacting with Wyze Lock and Wyze Lock Keypad
- `wyze_sdk.client.motion_sensors`: for interacting with Wyze Motion Sensor
- `wyze_sdk.client.plugs`: for controlling Wyze Plug and Wyze Plug Outdoor
- `wyze_sdk.client.scales`: for controlling Wyze Scale
- `wyze_sdk.client.thermostats`: for controlling Wyze Thermostat
- `wyze_sdk.client.switches`: for controlling Wyze Switch
- `wyze_sdk.client.thermostats`: for controlling Wyze Thermostat and Wyze Room Sensor
- `wyze_sdk.client.vacuums`: for controlling Wyze Robot Vacuum

**Disclaimer: This repository is for non-destructive use only. WyzeLabs is a wonderful company providing excellent devices at a reasonable price. I ask that you do no harm and be civilized.**
Expand Down
8 changes: 5 additions & 3 deletions docs-src/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,25 @@ Whether you're building a custom app, or integrating into a third-party service
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Web Client | Send data to or query data from Wyze using a variety of device-specific sub-clients. | ``wyze_sdk.client`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Bulb Client | Control Wyze Bulb and Wyze Bulb Color | ``wyze_sdk.client.bulbs`` |
| Bulb Client | Control Wyze Bulb, Wyze Bulb Color, Wyze Bulb White, and Wyze Light Strip | ``wyze_sdk.client.bulbs`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Entry Sensor Client | Interact with Wyze Sense Entry Sensors | ``wyze_sdk.client.entry_sensors`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Camera Client | Control Wyze Cameras | ``wyze_sdk.client.cameras`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Event Client | Manage Wyze alarm events | ``wyze_sdk.client.events`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Lock Client | Control Wyze Lock | ``wyze_sdk.client.locks`` |
| Lock Client | Control Wyze Lock and Wyze Lock Keypad | ``wyze_sdk.client.locks`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Motion Sensor Client | Interact with Wyze Sense Motion Sensors | ``wyze_sdk.client.motion_sensors`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Plug Client | Control Wyze Plug and Wyze Plug Outdoor | ``wyze_sdk.client.plugs`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Scale Client | Control Wyze Scale | ``wyze_sdk.client.scales`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Thermostat Client | Control Wyze Thermostat | ``wyze_sdk.client.thermostats`` |
| Switch Client | Control Wyze Switch | ``wyze_sdk.client.switches`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Thermostat Client | Control Wyze Thermostat and Wyze Room Sensor | ``wyze_sdk.client.thermostats`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
| Vacuum Client | Control Wyze Robot Vacuum | ``wyze_sdk.client.vacuums`` |
+--------------------------------+-----------------------------------------------------------------------------------------------+------------------------------------+
Expand Down
8 changes: 8 additions & 0 deletions docs-src/wyze_sdk.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ Entry Sensors
:inherited-members:
:show-inheritance:

Switches
===========
.. autoclass:: wyze_sdk.api.devices.switches.SwitchesClient
:members:
:undoc-members:
:inherited-members:
:show-inheritance:

Thermostats
===========
.. autoclass:: wyze_sdk.api.devices.thermostats.ThermostatsClient
Expand Down
16 changes: 16 additions & 0 deletions docs-src/wyze_sdk.models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ wyze\_sdk.models.devices.cameras
:undoc-members:
:inherited-members:

wyze\_sdk.models.devices.lights
------------------------------

.. automodule:: wyze_sdk.models.devices.lights
:members:
:undoc-members:
:inherited-members:

wyze\_sdk.models.devices.locks
------------------------------

Expand Down Expand Up @@ -66,6 +74,14 @@ wyze\_sdk.models.devices.sensors
:undoc-members:
:inherited-members:

wyze\_sdk.models.devices.switches
------------------------------------

.. automodule:: wyze_sdk.models.devices.switches
:members:
:undoc-members:
:inherited-members:

wyze\_sdk.models.devices.thermostats
------------------------------------

Expand Down
8 changes: 8 additions & 0 deletions docs-src/wyze_sdk.service.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ wyze\_sdk.service.scale\_service module
:undoc-members:
:show-inheritance:

wyze\_sdk.service.sirius\_service module
---------------------------------------

.. automodule:: wyze_sdk.service.sirius_service
:members:
:undoc-members:
:show-inheritance:

wyze\_sdk.service.venus\_service module
---------------------------------------

Expand Down
7 changes: 5 additions & 2 deletions wyze_sdk/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from wyze_sdk.errors import WyzeClientConfigurationError
from wyze_sdk.service import (ApiServiceClient, EarthServiceClient,
GeneralApiServiceClient, PlatformServiceClient,
ScaleServiceClient, VenusServiceClient,
WyzeResponse)
ScaleServiceClient, SiriusServiceClient,
VenusServiceClient, WyzeResponse)


class BaseClient(object, metaclass=ABCMeta):
Expand Down Expand Up @@ -63,6 +63,9 @@ def _general_api_client(self) -> GeneralApiServiceClient:
def _scale_client(self) -> ScaleServiceClient:
return BaseClient._service_client(ScaleServiceClient, token=self._token, base_url=self._base_url)

def _sirius_client(self) -> EarthServiceClient:
return BaseClient._service_client(SiriusServiceClient, token=self._token, base_url=self._base_url)

def _venus_client(self) -> VenusServiceClient:
return BaseClient._service_client(VenusServiceClient, token=self._token, base_url=self._base_url)

Expand Down
6 changes: 5 additions & 1 deletion wyze_sdk/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
ContactSensorsClient, LocksClient,
MotionSensorsClient, PlugsClient,
ScalesClient, ThermostatsClient,
VacuumsClient)
SwitchesClient, VacuumsClient)
from wyze_sdk.api.events import EventsClient
from wyze_sdk.errors import WyzeClientConfigurationError
from wyze_sdk.models.devices import Device, DeviceParser
Expand Down Expand Up @@ -94,6 +94,10 @@ def locks(self) -> LocksClient:
def scales(self) -> ScalesClient:
return ScalesClient(token=self._token, user_id=self._user_id, base_url=self._base_url)

@property
def switches(self) -> SwitchesClient:
return SwitchesClient(token=self._token, base_url=self._base_url)

@property
def events(self) -> EventsClient:
return EventsClient(token=self._token, base_url=self._base_url)
Expand Down
1 change: 1 addition & 0 deletions wyze_sdk/api/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
from .plugs import PlugsClient # noqa
from .scales import ScalesClient # noqa
from .sensors import ContactSensorsClient, MotionSensorsClient # noqa
from .switches import SwitchesClient # noqa
from .thermostats import ThermostatsClient # noqa
from .vacuums import VacuumsClient # noqa
104 changes: 104 additions & 0 deletions wyze_sdk/api/devices/switches.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from datetime import datetime, timedelta
from typing import Optional, Sequence, Tuple, Union

from wyze_sdk.api.base import BaseClient
from wyze_sdk.models import JsonObject
from wyze_sdk.models.devices import DeviceModels, DeviceProp
from wyze_sdk.models.devices.switches import (Switch, SwitchProps, SwitchTimerAction, SwitchTimerActionType)
from wyze_sdk.service import WyzeResponse


class SwitchesClient(BaseClient):
"""A Client that services Wyze switches.
"""

def list(self, **kwargs) -> Sequence[Switch]:
"""Lists all switches available to a Wyze account.
:rtype: Sequence[Switch]
"""
return [Switch(**device) for device in self._list_switches()]

def _list_switches(self, **kwargs) -> Sequence[dict]:
return [device for device in super()._list_devices() if device["product_model"] in DeviceModels.SWITCH]

def info(self, *, device_mac: str, **kwargs) -> Optional[Switch]:
"""Retrieves details of a switch.
:param str device_mac: The device mac. e.g. ``LD_SS1_ABCDEF1234567890``
:rtype: Optional[Switch]
"""
switches = [_switch for _switch in self._list_switches() if _switch['mac'] == device_mac]
if len(switches) == 0:
return None

switch = switches[0]

iot_prop = super()._sirius_client().get_iot_prop(did=device_mac, keys=[prop_def.pid for prop_def in Switch.props().values()])
if "data" in iot_prop.data and "props" in iot_prop.data["data"]:
switch.update(iot_prop.data["data"]["props"])

return Switch(**switch)

def turn_on(self, *, device_mac: str, device_model: str, after: Optional[timedelta] = None, **kwargs) -> WyzeResponse:
"""Turns on a switch.
:param str device_mac: The device mac. e.g. ``LD_SS1_ABCDEF1234567890``
:param str device_model: The device model. e.g. ``LD_SS1``
:param Optional[timedelta] after: The delay before performing the action.
:rtype: WyzeResponse
"""
if after is None:
return self._set_switch_properties(device_mac, device_model, DeviceProp(definition=SwitchProps.switch_power(), value=True))

return self._set_switch_properties(device_mac, device_model, DeviceProp(definition=SwitchProps.timer_action(), value=[SwitchTimerAction(action=SwitchTimerActionType.TURN_ON, time=datetime.now() + after)]))

def turn_off(self, *, device_mac: str, device_model: str, after: Optional[timedelta] = None, **kwargs) -> WyzeResponse:
"""Turns off a switch.
:param str device_mac: The device mac. e.g. ``LD_SS1_ABCDEF1234567890``
:param str device_model: The device model. e.g. ``LD_SS1``
:param Optional[timedelta] after: The delay before performing the action.
:rtype: WyzeResponse
"""
if after is None:
return self._set_switch_properties(device_mac, device_model, DeviceProp(definition=SwitchProps.switch_power(), value=False))

return self._set_switch_properties(device_mac, device_model, DeviceProp(definition=SwitchProps.timer_action(), value=[SwitchTimerAction(action=SwitchTimerActionType.TURN_OFF, time=datetime.now() + after)]))

def clear_timer(self, *, device_mac: str, device_model: str, **kwargs) -> WyzeResponse:
"""Clears any existing power state timer on the switch.
:param str device_mac: The device mac. e.g. ``LD_SS1_ABCDEF1234567890``
:param str device_model: The device model. e.g. ``LD_SS1``
"""
return self._set_switch_properties(device_mac, device_model, DeviceProp(definition=SwitchProps.timer_action(), value=[]))

def set_away_mode(self, *, device_mac: str, device_model: str, away_mode: bool = True, **kwargs) -> WyzeResponse:
"""Sets away/vacation mode for a switch.
:param str device_mac: The device mac. e.g. ``LD_SS1_ABCDEF1234567890``
:param str device_model: The device model. e.g. ``LD_SS1``
:param bool away_mode: The new away mode. e.g. ``True``
"""
return self._set_switch_property(device_mac, device_model, DeviceProp(definition=SwitchProps.away_mode(), value=away_mode))

def _set_switch_property(self, device_mac: str, device_model: str, prop: DeviceProp) -> WyzeResponse:
return super()._sirius_client().set_iot_prop(did=device_mac, model=device_model, key=prop.definition.pid, value=str(prop.api_value))

def _set_switch_properties(self, device_mac: str, device_model: str, props: Union[DeviceProp, Sequence[DeviceProp]]) -> WyzeResponse:
if not isinstance(props, (list, Tuple)):
props = [props]
the_props = {}
for prop in props:
if prop.definition.type == JsonObject:
the_props[prop.definition.pid] = prop.api_value.to_json()
elif prop.definition.type == Sequence:
the_props[prop.definition.pid] = [_prop.to_json() for _prop in prop.api_value if isinstance(_prop, JsonObject)]
else:
the_props[prop.definition.pid] = str(prop.api_value)
return super()._sirius_client().set_iot_prop_by_topic(
did=device_mac, model=device_model, props=the_props)
73 changes: 38 additions & 35 deletions wyze_sdk/models/devices/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from .plugs import OutdoorPlug, Plug, PlugProps # noqa
from .scales import Scale, ScaleProps, ScaleRecord, UserGoalWeight # noqa
from .sensors import ContactSensor, MotionSensor, Sensor, SensorProps # noqa
from .switches import Switch, SwitchProps # noqa
from .thermostats import (Thermostat, ThermostatFanMode, # noqa
ThermostatProps, ThermostatScenarioType,
ThermostatSystemMode)
Expand All @@ -31,40 +32,42 @@ class DeviceParser(object):
def parse(cls, device: Union[dict, "Device"]) -> Optional["Device"]:
if device is None:
return None
elif isinstance(device, Device):
if isinstance(device, Device):
return device
else:
if "product_type" in device:
type = device["product_type"]
if type == BaseStation.type:
return BaseStation(**device)
elif type == Bulb.type:
return Bulb(**device)
elif type == Camera.type:
return Camera(**device)
elif type == ContactSensor.type:
return ContactSensor(**device)
elif type == Lock.type:
return Lock(**device)
elif type == LockGateway.type:
return LockGateway(**device)
elif type == MeshBulb.type:
return MeshBulb(**device)
elif type == MotionSensor.type:
return MotionSensor(**device)
elif type == OutdoorPlug.type:
return OutdoorPlug(**device)
elif type == Plug.type:
return Plug(**device)
elif type == Scale.type or type in DeviceModels.SCALE:
return Scale(**device)
elif type == Thermostat.type or type in DeviceModels.THERMOSTAT:
return Thermostat(**device)
elif type == Vacuum.type or type in DeviceModels.VACUUM:
return Vacuum(**device)
else:
cls._logger.warning(f"Unknown device type detected ({device})")
return Device(**device)
if "product_type" in device:
type = device["product_type"]
if type == BaseStation.type:
return BaseStation(**device)
elif type == Bulb.type:
return Bulb(**device)
elif type == Camera.type:
return Camera(**device)
elif type == ContactSensor.type:
return ContactSensor(**device)
elif type == Lock.type:
return Lock(**device)
elif type == LockGateway.type:
return LockGateway(**device)
elif type == MeshBulb.type:
return MeshBulb(**device)
elif type == MotionSensor.type:
return MotionSensor(**device)
elif type == OutdoorPlug.type:
return OutdoorPlug(**device)
elif type == Plug.type:
return Plug(**device)
elif type == Scale.type or type in DeviceModels.SCALE:
return Scale(**device)
elif type == Thermostat.type or type in DeviceModels.THERMOSTAT:
return Thermostat(**device)
elif type == Vacuum.type or type in DeviceModels.VACUUM:
return Vacuum(**device)
if "product_model" in device:
model = device["product_model"]
if model in DeviceModels.SWITCH:
return Switch(**device)
else:
cls._logger.warning(f"Unknown device detected and skipped ({device})")
return None
cls._logger.warning(f"Unknown device type detected ({device})")
return Device(**device)
cls._logger.warning(f"Unknown device detected and skipped ({device})")
return None
2 changes: 2 additions & 0 deletions wyze_sdk/models/devices/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ class DeviceModels(object):
OUTDOOR_PLUG = ['WLPPO', 'WLPPO-SUB']
PLUG = ['WLPP1', 'WLPP1CFH'] + OUTDOOR_PLUG

SWITCH = ['LD_SS1']


class Product(object):
"""
Expand Down
Loading

0 comments on commit f4a7231

Please sign in to comment.