wyzeapy 0.5.28__py3-none-any.whl → 0.5.30__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ import logging
2
+ from enum import Enum
3
+ from typing import Any, Dict, List
4
+
5
+ from .base_service import BaseService
6
+ from ..types import Device, IrrigationProps, DeviceTypes
7
+
8
+ _LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ class CropType(Enum):
12
+ COOL_SEASON_GRASS = "cool_season_grass"
13
+ WARM_SEASON_GRASS = "warm_season_grass"
14
+ SHRUBS = "shrubs"
15
+ TREES = "trees"
16
+ ANNUALS = "annuals"
17
+ PERENNIALS = "perennials"
18
+ XERISCAPE = "xeriscape"
19
+ GARDEN = "garden"
20
+
21
+
22
+ class ExposureType(Enum):
23
+ LOTS_OF_SUN = "lots_of_sun"
24
+ SOME_SHADE = "some_shade"
25
+
26
+
27
+ class NozzleType(Enum):
28
+ FIXED_SPRAY_HEAD = "fixed_spray_head"
29
+ ROTOR_HEAD = "rotor_head"
30
+ ROTARY_NOZZLE = "rotary_nozzle"
31
+ MISTER = "mister"
32
+ BUBBLER = "bubbler"
33
+ EMITTER = "emitter"
34
+ DRIP_LINE = "drip_line"
35
+
36
+
37
+ class SlopeType(Enum):
38
+ FLAT = "flat"
39
+ SLIGHT = "slight"
40
+ MODERATE = "moderate"
41
+ STEEP = "steep"
42
+
43
+
44
+ class SoilType(Enum):
45
+ CLAY_LOAM = 'clay_loam'
46
+ CLAY = 'clay'
47
+ SILTY_CLAY = 'silty_clay'
48
+ LOAM = 'loam'
49
+ SANDY_LOAM = 'sandy_loam'
50
+ LOAMY_SAND = 'loamy_sand'
51
+ SAND = 'sand'
52
+
53
+
54
+ class Zone:
55
+ """Represents a single irrigation zone."""
56
+ def __init__(self, dictionary: Dict[Any, Any]):
57
+ self.zone_number: int = dictionary.get('zone_number', 1)
58
+ self.name: str = dictionary.get('name', 'Zone 1')
59
+ self.enabled: bool = dictionary.get('enabled', True)
60
+ self.zone_id: str = dictionary.get('zone_id', 'zone_id')
61
+ self.smart_duration: int = dictionary.get('smart_duration', 600)
62
+
63
+ # this quickrun duration is used only for running a zone manually
64
+ # the wyze api has no such value, but takes a duration as part of the api call
65
+ # the default value grabs the wyze smart_duration but all further updates
66
+ # are managed through the home assistant state
67
+ self.quickrun_duration: int = dictionary.get('smart_duration', 600)
68
+
69
+ class Irrigation(Device):
70
+ def __init__(self, dictionary: Dict[Any, Any]):
71
+ super().__init__(dictionary)
72
+
73
+ # the below comes from the get_iot_prop call
74
+ self.RSSI: int = 0
75
+ self.IP: str = "192.168.1.100"
76
+ self.sn: str = "SN123456789"
77
+ self.available: bool = False
78
+ self.ssid: str = "ssid"
79
+ # the below comes from the device_info call
80
+ self.zones: List[Zone] = []
81
+
82
+
83
+ class IrrigationService(BaseService):
84
+ async def update(self, irrigation: Irrigation) -> Irrigation:
85
+ """Update the irrigation device with latest data from Wyze API."""
86
+ # Get IoT properties
87
+ properties = (await self.get_iot_prop(irrigation))['data']['props']
88
+
89
+ # Update device properties
90
+ irrigation.RSSI = properties.get('RSSI', -65)
91
+ irrigation.IP = properties.get('IP', '192.168.1.100')
92
+ irrigation.sn = properties.get('sn', 'SN123456789')
93
+ irrigation.ssid = properties.get('ssid', 'ssid')
94
+ irrigation.available = (properties.get(IrrigationProps.IOT_STATE.value) == "connected")
95
+
96
+ # Get zones
97
+ zones = (await self.get_zone_by_device(irrigation))['data']['zones']
98
+
99
+ # Update zones
100
+ irrigation.zones = []
101
+ for zone in zones:
102
+ irrigation.zones.append(Zone(zone))
103
+
104
+ return irrigation
105
+
106
+ async def update_device_props(self, irrigation: Irrigation) -> Irrigation:
107
+ """Update the irrigation device with latest data from Wyze API."""
108
+ # Get IoT properties
109
+ properties = (await self.get_iot_prop(irrigation))['data']['props']
110
+
111
+ # Update device properties
112
+ irrigation.RSSI = properties.get('RSSI')
113
+ irrigation.IP = properties.get('IP')
114
+ irrigation.sn = properties.get('sn')
115
+ irrigation.ssid = properties.get('ssid')
116
+ irrigation.available = (properties.get(IrrigationProps.IOT_STATE.value) == 'connected')
117
+
118
+ return irrigation
119
+
120
+ async def get_irrigations(self) -> List[Irrigation]:
121
+ if self._devices is None:
122
+ self._devices = await self.get_object_list()
123
+
124
+ irrigations = [device for device in self._devices if device.type == DeviceTypes.IRRIGATION and "BS_WK1" in device.product_model]
125
+
126
+ return [Irrigation(irrigation.raw_dict) for irrigation in irrigations]
127
+
128
+ async def start_zone(self, irrigation: Device, zone_number: int, quickrun_duration: int) -> Dict[Any, Any]:
129
+ """Start a zone with the specified duration.
130
+
131
+ Args:
132
+ irrigation: The irrigation device
133
+ zone_number: The zone number to start
134
+ quickrun_duration: Duration in seconds to run the zone
135
+
136
+ Returns:
137
+ Dict containing the API response
138
+ """
139
+ url = "https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/quickrun"
140
+ return await self._start_zone(url, irrigation, zone_number, quickrun_duration)
141
+
142
+ async def stop_running_schedule(self, device: Device) -> Dict[Any, Any]:
143
+ """Stop any currently running irrigation schedule.
144
+
145
+ Args:
146
+ device: The irrigation device
147
+
148
+ Returns:
149
+ Dict containing the API response
150
+ """
151
+ url = "https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/runningschedule"
152
+ action = "STOP"
153
+ return await self._stop_running_schedule(url, device, action)
154
+
155
+ async def set_zone_quickrun_duration(self, irrigation: Irrigation, zone_number: int, duration: int) -> Irrigation:
156
+ """Set the quickrun duration for a specific zone.
157
+
158
+ Args:
159
+ irrigation: The irrigation device
160
+ zone_number: The zone number to configure
161
+ duration: Duration in seconds for quickrun
162
+ """
163
+ for zone in irrigation.zones:
164
+ if zone.zone_number == zone_number:
165
+ zone.quickrun_duration = duration
166
+ break
167
+
168
+ return irrigation
169
+
170
+ # Private implementation methods
171
+ async def get_iot_prop(self, device: Device) -> Dict[Any, Any]:
172
+ """Get IoT properties for a device."""
173
+ url = "https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/get_iot_prop"
174
+ keys = 'zone_state,iot_state,iot_state_update_time,app_version,RSSI,' \
175
+ 'wifi_mac,sn,device_model,ssid,IP'
176
+ return await self._get_iot_prop(url, device, keys)
177
+
178
+ async def get_device_info(self, device: Device) -> Dict[Any, Any]:
179
+ """Get device info from Wyze API."""
180
+ url = "https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/device_info"
181
+ keys = 'wiring,sensor,enable_schedules,notification_enable,notification_watering_begins,' \
182
+ 'notification_watering_ends,notification_watering_is_skipped,skip_low_temp,skip_wind,' \
183
+ 'skip_rain,skip_saturation'
184
+ return await self._irrigation_device_info(url, device, keys)
185
+
186
+ async def get_zone_by_device(self, device: Device) -> List[Dict[Any, Any]]:
187
+ """Get zones for a device."""
188
+ url = "https://wyze-lockwood-service.wyzecam.com/plugin/irrigation/zone"
189
+ return await self._get_zone_by_device(url, device)
@@ -27,7 +27,9 @@ class LockService(BaseService):
27
27
  ble_token_info = await self._get_lock_ble_token(lock)
28
28
  lock.raw_dict["token"] = ble_token_info["token"]
29
29
  lock.ble_id = ble_token_info["token"]["id"]
30
- lock.ble_token = wyze_decrypt_cbc(FORD_APP_SECRET[:16], ble_token_info["token"]["token"])
30
+ lock.ble_token = wyze_decrypt_cbc(
31
+ FORD_APP_SECRET[:16], ble_token_info["token"]["token"]
32
+ )
31
33
 
32
34
  lock.available = lock.raw_dict.get("onoff_line") == 1
33
35
  lock.door_open = lock.raw_dict.get("door_open_status") == 1
@@ -37,13 +39,13 @@ class LockService(BaseService):
37
39
  locker_status = lock.raw_dict.get("locker_status")
38
40
  # Check if the door is locked
39
41
  lock.unlocked = locker_status.get("hardlock") == 2
40
-
42
+
41
43
  # Reset unlocking and locking if needed
42
44
  if lock.unlocked and lock.unlocking:
43
45
  lock.unlocking = False
44
46
  if not lock.unlocked and lock.locking:
45
47
  lock.locking = False
46
-
48
+
47
49
  return lock
48
50
 
49
51
  async def get_locks(self):
@@ -31,9 +31,9 @@ class SensorService(BaseService):
31
31
  sensor.device_params = await self.get_updated_params(sensor.mac)
32
32
  properties = await self._get_device_info(sensor)
33
33
 
34
- for property in properties['data']['property_list']:
35
- pid = property['pid']
36
- value = property['value']
34
+ for property in properties["data"]["property_list"]:
35
+ pid = property["pid"]
36
+ value = property["value"]
37
37
 
38
38
  try:
39
39
  if PropertyIDs(pid) == PropertyIDs.CONTACT_STATE:
@@ -45,26 +45,44 @@ class SensorService(BaseService):
45
45
 
46
46
  return sensor
47
47
 
48
- async def register_for_updates(self, sensor: Sensor, callback: Callable[[Sensor], None]):
48
+ async def register_for_updates(
49
+ self, sensor: Sensor, callback: Callable[[Sensor], None]
50
+ ):
49
51
  _LOGGER.debug(f"Registering sensor: {sensor.nickname} for updates")
50
52
  loop = asyncio.get_event_loop()
51
53
  if not self._updater_thread:
52
- self._updater_thread = Thread(target=self.update_worker, args=[loop, ], daemon=True)
54
+ self._updater_thread = Thread(
55
+ target=self.update_worker,
56
+ args=[
57
+ loop,
58
+ ],
59
+ daemon=True,
60
+ )
53
61
  self._updater_thread.start()
54
62
 
55
63
  self._subscribers.append((sensor, callback))
56
64
 
57
65
  async def deregister_for_updates(self, sensor: Sensor):
58
- self._subscribers = [(sense, callback) for sense, callback in self._subscribers if sense.mac != sensor.mac]
66
+ self._subscribers = [
67
+ (sense, callback)
68
+ for sense, callback in self._subscribers
69
+ if sense.mac != sensor.mac
70
+ ]
59
71
 
60
72
  def update_worker(self, loop):
61
73
  while True:
62
74
  for sensor, callback in self._subscribers:
63
75
  _LOGGER.debug(f"Providing update for {sensor.nickname}")
64
76
  try:
65
- callback(asyncio.run_coroutine_threadsafe(self.update(sensor), loop).result())
77
+ callback(
78
+ asyncio.run_coroutine_threadsafe(
79
+ self.update(sensor), loop
80
+ ).result()
81
+ )
66
82
  except UnknownApiError as e:
67
- _LOGGER.warning(f"The update method detected an UnknownApiError: {e}")
83
+ _LOGGER.warning(
84
+ f"The update method detected an UnknownApiError: {e}"
85
+ )
68
86
  except ClientOSError as e:
69
87
  _LOGGER.error(f"A network error was detected: {e}")
70
88
  except ContentTypeError as e:
@@ -74,7 +92,10 @@ class SensorService(BaseService):
74
92
  if self._devices is None:
75
93
  self._devices = await self.get_object_list()
76
94
 
77
- sensors = [Sensor(device.raw_dict) for device in self._devices if
78
- device.type is DeviceTypes.MOTION_SENSOR or
79
- device.type is DeviceTypes.CONTACT_SENSOR]
95
+ sensors = [
96
+ Sensor(device.raw_dict)
97
+ for device in self._devices
98
+ if device.type is DeviceTypes.MOTION_SENSOR
99
+ or device.type is DeviceTypes.CONTACT_SENSOR
100
+ ]
80
101
  return [Sensor(sensor.raw_dict) for sensor in sensors]
@@ -36,8 +36,12 @@ class SwitchService(BaseService):
36
36
  if self._devices is None:
37
37
  self._devices = await self.get_object_list()
38
38
 
39
- devices = [device for device in self._devices if device.type is DeviceTypes.PLUG or
40
- device.type is DeviceTypes.OUTDOOR_PLUG]
39
+ devices = [
40
+ device
41
+ for device in self._devices
42
+ if device.type is DeviceTypes.PLUG
43
+ or device.type is DeviceTypes.OUTDOOR_PLUG
44
+ ]
41
45
  return [Switch(switch.raw_dict) for switch in devices]
42
46
 
43
47
  async def turn_on(self, switch: Switch):
@@ -37,9 +37,9 @@ class Preset(Enum):
37
37
 
38
38
 
39
39
  class HVACState(Enum):
40
- COOLING = 'cooling'
41
- HEATING = 'heating'
42
- IDLE = 'idle'
40
+ COOLING = "cooling"
41
+ HEATING = "heating"
42
+ IDLE = "idle"
43
43
 
44
44
 
45
45
  class Thermostat(Device):
@@ -60,7 +60,7 @@ class Thermostat(Device):
60
60
 
61
61
  class ThermostatService(BaseService):
62
62
  async def update(self, thermostat: Thermostat) -> Thermostat:
63
- properties = (await self._thermostat_get_iot_prop(thermostat))['data']['props']
63
+ properties = (await self._thermostat_get_iot_prop(thermostat))["data"]["props"]
64
64
 
65
65
  device_props = []
66
66
  for property in properties:
@@ -87,7 +87,7 @@ class ThermostatService(BaseService):
87
87
  elif prop == ThermostatProps.TEMPERATURE:
88
88
  thermostat.temperature = float(value)
89
89
  elif prop == ThermostatProps.IOT_STATE:
90
- thermostat.available = value == 'connected'
90
+ thermostat.available = value == "connected"
91
91
  elif prop == ThermostatProps.HUMIDITY:
92
92
  thermostat.humidity = int(value)
93
93
  elif prop == ThermostatProps.WORKING_STATE:
@@ -99,7 +99,9 @@ class ThermostatService(BaseService):
99
99
  if self._devices is None:
100
100
  self._devices = await self.get_object_list()
101
101
 
102
- thermostats = [device for device in self._devices if device.type is DeviceTypes.THERMOSTAT]
102
+ thermostats = [
103
+ device for device in self._devices if device.type is DeviceTypes.THERMOSTAT
104
+ ]
103
105
 
104
106
  return [Thermostat(thermostat.raw_dict) for thermostat in thermostats]
105
107
 
@@ -110,22 +112,34 @@ class ThermostatService(BaseService):
110
112
  await self._thermostat_set_iot_prop(thermostat, ThermostatProps.HEAT_SP, temp)
111
113
 
112
114
  async def set_hvac_mode(self, thermostat: Device, hvac_mode: HVACMode):
113
- await self._thermostat_set_iot_prop(thermostat, ThermostatProps.MODE_SYS, hvac_mode.value)
115
+ await self._thermostat_set_iot_prop(
116
+ thermostat, ThermostatProps.MODE_SYS, hvac_mode.value
117
+ )
114
118
 
115
119
  async def set_fan_mode(self, thermostat: Device, fan_mode: FanMode):
116
- await self._thermostat_set_iot_prop(thermostat, ThermostatProps.FAN_MODE, fan_mode.value)
120
+ await self._thermostat_set_iot_prop(
121
+ thermostat, ThermostatProps.FAN_MODE, fan_mode.value
122
+ )
117
123
 
118
124
  async def set_preset(self, thermostat: Thermostat, preset: Preset):
119
- await self._thermostat_set_iot_prop(thermostat, ThermostatProps.CURRENT_SCENARIO, preset.value)
125
+ await self._thermostat_set_iot_prop(
126
+ thermostat, ThermostatProps.CURRENT_SCENARIO, preset.value
127
+ )
120
128
 
121
129
  async def _thermostat_get_iot_prop(self, device: Device) -> Dict[Any, Any]:
122
130
  url = "https://wyze-earth-service.wyzecam.com/plugin/earth/get_iot_prop"
123
- keys = 'trigger_off_val,emheat,temperature,humidity,time2temp_val,protect_time,mode_sys,heat_sp,cool_sp,' \
124
- 'current_scenario,config_scenario,temp_unit,fan_mode,iot_state,w_city_id,w_lat,w_lon,working_state,' \
125
- 'dev_hold,dev_holdtime,asw_hold,app_version,setup_state,wiring_logic_id,save_comfort_balance,' \
126
- 'kid_lock,calibrate_humidity,calibrate_temperature,fancirc_time,query_schedule'
131
+ keys = (
132
+ "trigger_off_val,emheat,temperature,humidity,time2temp_val,protect_time,mode_sys,heat_sp,cool_sp,"
133
+ "current_scenario,config_scenario,temp_unit,fan_mode,iot_state,w_city_id,w_lat,w_lon,working_state,"
134
+ "dev_hold,dev_holdtime,asw_hold,app_version,setup_state,wiring_logic_id,save_comfort_balance,"
135
+ "kid_lock,calibrate_humidity,calibrate_temperature,fancirc_time,query_schedule"
136
+ )
127
137
  return await self._get_iot_prop(url, device, keys)
128
138
 
129
- async def _thermostat_set_iot_prop(self, device: Device, prop: ThermostatProps, value: Any) -> None:
130
- url = "https://wyze-earth-service.wyzecam.com/plugin/earth/set_iot_prop_by_topic"
139
+ async def _thermostat_set_iot_prop(
140
+ self, device: Device, prop: ThermostatProps, value: Any
141
+ ) -> None:
142
+ url = (
143
+ "https://wyze-earth-service.wyzecam.com/plugin/earth/set_iot_prop_by_topic"
144
+ )
131
145
  return await self._set_iot_prop(url, device, prop.value, value)
@@ -7,17 +7,34 @@ from ..types import Device
7
7
  import logging
8
8
  import threading
9
9
 
10
+ """
11
+ Asynchronous device update scheduling and management.
12
+
13
+ This module provides classes to schedule and execute periodic updates of
14
+ Wyze devices, ensuring rate limits and fair distribution of update calls.
15
+ """
16
+
10
17
  _LOGGER = logging.getLogger(__name__)
11
18
 
12
19
  INTERVAL = 300
13
20
  MAX_SLOTS = 225
14
21
 
22
+
15
23
  @dataclass(order=True)
16
24
  class DeviceUpdater(object):
17
- device: Device=field(compare=False) # The device that will be updated
18
- service: Any=field(compare=False)
19
- update_in: int # A countdown to zero that will tell the priority queue that it is time to update this device
20
- updates_per_interval: int=field(compare=False) # The number of updates that should happen every 5 minutes
25
+ """Represents a scheduled update task for a single device.
26
+
27
+ Attributes:
28
+ service: The service instance responsible for updating the device.
29
+ device: The Device object to be updated.
30
+ update_in: Countdown ticks until the next update is due.
31
+ updates_per_interval: Number of updates allowed per INTERVAL.
32
+ """
33
+
34
+ device: Device = field(compare=False)
35
+ service: Any = field(compare=False)
36
+ update_in: int # Countdown ticks until this device should be updated
37
+ updates_per_interval: int = field(compare=False)
21
38
 
22
39
  def __init__(self, service, device: Device, update_interval: int):
23
40
  """
@@ -42,7 +59,7 @@ class DeviceUpdater(object):
42
59
  self.device = await self.service.update(self.device)
43
60
  # Callback to provide the updated info to the subscriber
44
61
  self.device.callback_function(self.device)
45
- except:
62
+ except Exception:
46
63
  _LOGGER.exception("Unknown error happened during updating device info")
47
64
  finally:
48
65
  # Release the mutex after the async call
@@ -63,11 +80,17 @@ class DeviceUpdater(object):
63
80
  if self.updates_per_interval > 1:
64
81
  self.updates_per_interval -= 1
65
82
 
83
+
66
84
  class UpdateManager:
67
- # Holds all the logic for when to update the devices
85
+ """Manager for scheduling and executing periodic device updates.
86
+
87
+ Maintains a priority queue of DeviceUpdater instances and enforces rate
88
+ limits and fair distribution of update calls across devices.
89
+ """
90
+
68
91
  updaters = []
69
92
  removed_updaters = []
70
- mutex = threading.Lock() # Create a lock object as a class variable
93
+ mutex = threading.Lock()
71
94
 
72
95
  def check_if_removed(self, updater: DeviceUpdater):
73
96
  for item in self.removed_updaters:
@@ -78,7 +101,7 @@ class UpdateManager:
78
101
  # This function should be called once every second
79
102
  async def update_next(self):
80
103
  # If there are no updaters in the queue we don't need to do anything
81
- if (len(self.updaters) == 0):
104
+ if len(self.updaters) == 0:
82
105
  _LOGGER.debug("No devices to update in queue")
83
106
  return
84
107
  while True:
@@ -92,12 +115,13 @@ class UpdateManager:
92
115
  # We then reduce the counter for all the other updaters
93
116
  self.tick_tock()
94
117
  # Then we update the target device
95
- await updater.update(self.mutex) # It will only update if it is time for it to update. Otherwise it just reduces its update_in counter.
118
+ await updater.update(
119
+ self.mutex
120
+ ) # It will only update if it is time for it to update. Otherwise it just reduces its update_in counter.
96
121
  # Then we put it back at the end of the queue. Or the front again if it wasn't ready to update
97
122
  heappush(self.updaters, updater)
98
123
  await sleep(1)
99
124
 
100
-
101
125
  def filled_slots(self):
102
126
  # This just returns the number of available slots
103
127
  current_slots = 0
@@ -123,7 +147,10 @@ class UpdateManager:
123
147
 
124
148
  # When we add a new updater it has to fit within the max slots or we will not add it
125
149
  while (self.filled_slots() + updater.updates_per_interval) > MAX_SLOTS:
126
- _LOGGER.debug("Reducing updates per interval to fit new device as slots are full: %s", self.filled_slots())
150
+ _LOGGER.debug(
151
+ "Reducing updates per interval to fit new device as slots are full: %s",
152
+ self.filled_slots(),
153
+ )
127
154
  # If we are overflowing the available slots we will reduce the frequency of updates evenly for all devices until we can fit in one more.
128
155
  self.decrease_updates_per_interval()
129
156
  updater.delay()
@@ -41,7 +41,7 @@ class WallSwitch(Device):
41
41
 
42
42
  class WallSwitchService(BaseService):
43
43
  async def update(self, switch: WallSwitch) -> WallSwitch:
44
- properties = (await self._wall_switch_get_iot_prop(switch))['data']['props']
44
+ properties = (await self._wall_switch_get_iot_prop(switch))["data"]["props"]
45
45
 
46
46
  device_props = []
47
47
  for prop_key, prop_value in properties.items():
@@ -67,9 +67,11 @@ class WallSwitchService(BaseService):
67
67
  if self._devices is None:
68
68
  self._devices = await self.get_object_list()
69
69
 
70
- switches = [device for device in self._devices
71
- if device.type is DeviceTypes.COMMON
72
- and device.product_model == "LD_SS1"]
70
+ switches = [
71
+ device
72
+ for device in self._devices
73
+ if device.type is DeviceTypes.COMMON and device.product_model == "LD_SS1"
74
+ ]
73
75
 
74
76
  return [WallSwitch(switch.raw_dict) for switch in switches]
75
77
 
@@ -89,7 +91,9 @@ class WallSwitchService(BaseService):
89
91
  await self._wall_switch_set_iot_prop(switch, WallSwitchProps.SWITCH_POWER, True)
90
92
 
91
93
  async def power_off(self, switch: WallSwitch):
92
- await self._wall_switch_set_iot_prop(switch, WallSwitchProps.SWITCH_POWER, False)
94
+ await self._wall_switch_set_iot_prop(
95
+ switch, WallSwitchProps.SWITCH_POWER, False
96
+ )
93
97
 
94
98
  async def iot_on(self, switch: WallSwitch):
95
99
  await self._wall_switch_set_iot_prop(switch, WallSwitchProps.SWITCH_IOT, True)
@@ -97,14 +101,20 @@ class WallSwitchService(BaseService):
97
101
  async def iot_off(self, switch: WallSwitch):
98
102
  await self._wall_switch_set_iot_prop(switch, WallSwitchProps.SWITCH_IOT, False)
99
103
 
100
- async def set_single_press_type(self, switch: WallSwitch, single_press_type: SinglePressType):
101
- await self._wall_switch_set_iot_prop(switch, WallSwitchProps.SINGLE_PRESS_TYPE, single_press_type.value)
104
+ async def set_single_press_type(
105
+ self, switch: WallSwitch, single_press_type: SinglePressType
106
+ ):
107
+ await self._wall_switch_set_iot_prop(
108
+ switch, WallSwitchProps.SINGLE_PRESS_TYPE, single_press_type.value
109
+ )
102
110
 
103
111
  async def _wall_switch_get_iot_prop(self, device: Device) -> Dict[Any, Any]:
104
112
  url = "https://wyze-sirius-service.wyzecam.com//plugin/sirius/get_iot_prop"
105
113
  keys = "iot_state,switch-power,switch-iot,single_press_type"
106
114
  return await self._get_iot_prop(url, device, keys)
107
115
 
108
- async def _wall_switch_set_iot_prop(self, device: Device, prop: WallSwitchProps, value: Any) -> None:
116
+ async def _wall_switch_set_iot_prop(
117
+ self, device: Device, prop: WallSwitchProps, value: Any
118
+ ) -> None:
109
119
  url = "https://wyze-sirius-service.wyzecam.com//plugin/sirius/set_iot_prop_by_topic"
110
120
  return await self._set_iot_prop(url, device, prop.value, value)