wyzeapy.services.bulb_service
1# Copyright (c) 2021. Mulliken, LLC - All Rights Reserved 2# You may use, distribute and modify this code under the terms 3# of the attached license. You should have received a copy of 4# the license with this file. If not, please write to: 5# katie@mulliken.net to receive a copy 6import logging 7import re 8from typing import Any, Dict, Optional, List 9 10from .base_service import BaseService 11from ..types import Device, PropertyIDs, DeviceTypes 12from ..utils import create_pid_pair 13 14_LOGGER = logging.getLogger(__name__) 15 16 17class Bulb(Device): 18 _brightness: int = 0 19 _color_temp: int = 1800 20 _color: Optional[str] 21 enr: str 22 23 on: bool = False 24 cloud_fallback = False 25 26 def __init__(self, dictionary: Dict[Any, Any]): 27 super().__init__(dictionary) 28 29 self.ip = self.device_params["ip"] 30 31 if ( 32 self.type is DeviceTypes.MESH_LIGHT 33 or self.type is DeviceTypes.LIGHTSTRIP 34 ): 35 self._color = "000000" 36 37 @property 38 def brightness(self) -> int: 39 return self._brightness 40 41 @brightness.setter 42 def brightness(self, value: int) -> None: 43 assert value <= 100 44 assert value >= 0 45 self._brightness = value 46 47 @property 48 def color_temp(self) -> int: 49 return self._color_temp 50 51 @color_temp.setter 52 def color_temp(self, value: int) -> None: 53 self._color_temp = value 54 55 @property 56 def color(self) -> Optional[str]: 57 return self._color 58 59 @color.setter 60 def color(self, value) -> None: 61 assert re.match(r"^([A-Fa-f\d]{6}|[A-Fa-f\d]{3})$", value) is not None 62 self._color = value 63 64 65class BulbService(BaseService): 66 async def update(self, bulb: Bulb) -> Bulb: 67 # Get updated device_params 68 async with BaseService._update_lock: 69 bulb.device_params = await self.get_updated_params(bulb.mac) 70 71 device_info = await self._get_property_list(bulb) 72 for property_id, value in device_info: 73 if property_id == PropertyIDs.BRIGHTNESS: 74 bulb.brightness = int(float(value)) 75 elif property_id == PropertyIDs.COLOR_TEMP: 76 try: 77 bulb.color_temp = int(value) 78 except ValueError: 79 bulb.color_temp = 2700 80 elif property_id == PropertyIDs.ON: 81 bulb.on = value == "1" 82 elif property_id == PropertyIDs.AVAILABLE: 83 bulb.available = value == "1" 84 elif ( 85 property_id == PropertyIDs.COLOR 86 and bulb.type in [DeviceTypes.LIGHTSTRIP, DeviceTypes.MESH_LIGHT] 87 ): 88 bulb.color = value 89 elif property_id == PropertyIDs.COLOR_MODE: 90 bulb.color_mode = value 91 elif property_id == PropertyIDs.SUN_MATCH: 92 bulb.sun_match = value == "1" 93 elif property_id == PropertyIDs.LIGHTSTRIP_EFFECTS: 94 bulb.effects = value 95 elif property_id == PropertyIDs.LIGHTSTRIP_MUSIC_MODE: 96 bulb.music_mode = value == "1" 97 98 return bulb 99 100 async def get_bulbs(self) -> List[Bulb]: 101 if self._devices is None: 102 self._devices = await self.get_object_list() 103 104 bulbs = [ 105 device 106 for device in self._devices 107 if device.type in [DeviceTypes.LIGHT, 108 DeviceTypes.MESH_LIGHT, 109 DeviceTypes.LIGHTSTRIP] 110 ] 111 112 return [Bulb(bulb.raw_dict) for bulb in bulbs] 113 114 async def turn_on(self, bulb: Bulb, local_control, options=None): 115 plist = [ 116 create_pid_pair(PropertyIDs.ON, "1") 117 ] 118 119 if options is not None: 120 plist.extend(options) 121 122 if bulb.type is DeviceTypes.LIGHT: 123 await self._set_property_list(bulb, plist) 124 125 elif ( 126 bulb.type in [DeviceTypes.MESH_LIGHT, DeviceTypes.LIGHTSTRIP] 127 ): 128 # Local Control 129 if local_control and not bulb.cloud_fallback: 130 await self._local_bulb_command(bulb, plist) 131 132 # Cloud Control 133 elif bulb.type is DeviceTypes.MESH_LIGHT: # Sun match for mesh bulbs needs to be set on a different endpoint for some reason 134 for item in plist: 135 if item["pid"] == PropertyIDs.SUN_MATCH.value: 136 await self._set_property_list(bulb, [item]) 137 plist.remove(item) 138 await self._run_action_list(bulb, plist) 139 else: 140 await self._run_action_list(bulb, plist) # Lightstrips 141 142 async def turn_off(self, bulb: Bulb, local_control): 143 plist = [ 144 create_pid_pair(PropertyIDs.ON, "0") 145 ] 146 147 if bulb.type in [ 148 DeviceTypes.LIGHT 149 ]: 150 await self._set_property_list(bulb, plist) 151 elif ( 152 bulb.type in [DeviceTypes.MESH_LIGHT, DeviceTypes.LIGHTSTRIP] 153 ): 154 if local_control and not bulb.cloud_fallback: 155 await self._local_bulb_command(bulb, plist) 156 else: 157 await self._run_action_list(bulb, plist) 158 159 async def set_color_temp(self, bulb: Bulb, color_temp: int): 160 plist = [ 161 create_pid_pair(PropertyIDs.COLOR_TEMP, str(color_temp)) 162 ] 163 164 if bulb.type in [ 165 DeviceTypes.LIGHT 166 ]: 167 await self._set_property_list(bulb, plist) 168 elif bulb.type in [ 169 DeviceTypes.MESH_LIGHT 170 ]: 171 await self._local_bulb_command(bulb, plist) 172 173 async def set_color(self, bulb: Bulb, color: str, local_control): 174 plist = [ 175 create_pid_pair(PropertyIDs.COLOR, str(color)) 176 ] 177 if bulb.type in [ 178 DeviceTypes.MESH_LIGHT 179 ]: 180 if local_control and not bulb.cloud_fallback: 181 await self._local_bulb_command(bulb, plist) 182 else: 183 await self._run_action_list(bulb, plist) 184 185 async def set_brightness(self, bulb: Device, brightness: int): 186 plist = [ 187 create_pid_pair(PropertyIDs.BRIGHTNESS, str(brightness)) 188 ] 189 190 if bulb.type in [ 191 DeviceTypes.LIGHT 192 ]: 193 await self._set_property_list(bulb, plist) 194 if bulb.type in [ 195 DeviceTypes.MESH_LIGHT 196 ]: 197 await self._local_bulb_command(bulb, plist) 198 199 async def music_mode_on(self, bulb: Device): 200 plist = [ 201 create_pid_pair(PropertyIDs.LIGHTSTRIP_MUSIC_MODE, "1") 202 ] 203 204 await self._run_action_list(bulb, plist) 205 206 async def music_mode_off(self, bulb: Device): 207 plist = [ 208 create_pid_pair(PropertyIDs.LIGHTSTRIP_MUSIC_MODE, "0") 209 ] 210 211 await self._run_action_list(bulb, plist)
18class Bulb(Device): 19 _brightness: int = 0 20 _color_temp: int = 1800 21 _color: Optional[str] 22 enr: str 23 24 on: bool = False 25 cloud_fallback = False 26 27 def __init__(self, dictionary: Dict[Any, Any]): 28 super().__init__(dictionary) 29 30 self.ip = self.device_params["ip"] 31 32 if ( 33 self.type is DeviceTypes.MESH_LIGHT 34 or self.type is DeviceTypes.LIGHTSTRIP 35 ): 36 self._color = "000000" 37 38 @property 39 def brightness(self) -> int: 40 return self._brightness 41 42 @brightness.setter 43 def brightness(self, value: int) -> None: 44 assert value <= 100 45 assert value >= 0 46 self._brightness = value 47 48 @property 49 def color_temp(self) -> int: 50 return self._color_temp 51 52 @color_temp.setter 53 def color_temp(self, value: int) -> None: 54 self._color_temp = value 55 56 @property 57 def color(self) -> Optional[str]: 58 return self._color 59 60 @color.setter 61 def color(self, value) -> None: 62 assert re.match(r"^([A-Fa-f\d]{6}|[A-Fa-f\d]{3})$", value) is not None 63 self._color = value
Inherited Members
66class BulbService(BaseService): 67 async def update(self, bulb: Bulb) -> Bulb: 68 # Get updated device_params 69 async with BaseService._update_lock: 70 bulb.device_params = await self.get_updated_params(bulb.mac) 71 72 device_info = await self._get_property_list(bulb) 73 for property_id, value in device_info: 74 if property_id == PropertyIDs.BRIGHTNESS: 75 bulb.brightness = int(float(value)) 76 elif property_id == PropertyIDs.COLOR_TEMP: 77 try: 78 bulb.color_temp = int(value) 79 except ValueError: 80 bulb.color_temp = 2700 81 elif property_id == PropertyIDs.ON: 82 bulb.on = value == "1" 83 elif property_id == PropertyIDs.AVAILABLE: 84 bulb.available = value == "1" 85 elif ( 86 property_id == PropertyIDs.COLOR 87 and bulb.type in [DeviceTypes.LIGHTSTRIP, DeviceTypes.MESH_LIGHT] 88 ): 89 bulb.color = value 90 elif property_id == PropertyIDs.COLOR_MODE: 91 bulb.color_mode = value 92 elif property_id == PropertyIDs.SUN_MATCH: 93 bulb.sun_match = value == "1" 94 elif property_id == PropertyIDs.LIGHTSTRIP_EFFECTS: 95 bulb.effects = value 96 elif property_id == PropertyIDs.LIGHTSTRIP_MUSIC_MODE: 97 bulb.music_mode = value == "1" 98 99 return bulb 100 101 async def get_bulbs(self) -> List[Bulb]: 102 if self._devices is None: 103 self._devices = await self.get_object_list() 104 105 bulbs = [ 106 device 107 for device in self._devices 108 if device.type in [DeviceTypes.LIGHT, 109 DeviceTypes.MESH_LIGHT, 110 DeviceTypes.LIGHTSTRIP] 111 ] 112 113 return [Bulb(bulb.raw_dict) for bulb in bulbs] 114 115 async def turn_on(self, bulb: Bulb, local_control, options=None): 116 plist = [ 117 create_pid_pair(PropertyIDs.ON, "1") 118 ] 119 120 if options is not None: 121 plist.extend(options) 122 123 if bulb.type is DeviceTypes.LIGHT: 124 await self._set_property_list(bulb, plist) 125 126 elif ( 127 bulb.type in [DeviceTypes.MESH_LIGHT, DeviceTypes.LIGHTSTRIP] 128 ): 129 # Local Control 130 if local_control and not bulb.cloud_fallback: 131 await self._local_bulb_command(bulb, plist) 132 133 # Cloud Control 134 elif bulb.type is DeviceTypes.MESH_LIGHT: # Sun match for mesh bulbs needs to be set on a different endpoint for some reason 135 for item in plist: 136 if item["pid"] == PropertyIDs.SUN_MATCH.value: 137 await self._set_property_list(bulb, [item]) 138 plist.remove(item) 139 await self._run_action_list(bulb, plist) 140 else: 141 await self._run_action_list(bulb, plist) # Lightstrips 142 143 async def turn_off(self, bulb: Bulb, local_control): 144 plist = [ 145 create_pid_pair(PropertyIDs.ON, "0") 146 ] 147 148 if bulb.type in [ 149 DeviceTypes.LIGHT 150 ]: 151 await self._set_property_list(bulb, plist) 152 elif ( 153 bulb.type in [DeviceTypes.MESH_LIGHT, DeviceTypes.LIGHTSTRIP] 154 ): 155 if local_control and not bulb.cloud_fallback: 156 await self._local_bulb_command(bulb, plist) 157 else: 158 await self._run_action_list(bulb, plist) 159 160 async def set_color_temp(self, bulb: Bulb, color_temp: int): 161 plist = [ 162 create_pid_pair(PropertyIDs.COLOR_TEMP, str(color_temp)) 163 ] 164 165 if bulb.type in [ 166 DeviceTypes.LIGHT 167 ]: 168 await self._set_property_list(bulb, plist) 169 elif bulb.type in [ 170 DeviceTypes.MESH_LIGHT 171 ]: 172 await self._local_bulb_command(bulb, plist) 173 174 async def set_color(self, bulb: Bulb, color: str, local_control): 175 plist = [ 176 create_pid_pair(PropertyIDs.COLOR, str(color)) 177 ] 178 if bulb.type in [ 179 DeviceTypes.MESH_LIGHT 180 ]: 181 if local_control and not bulb.cloud_fallback: 182 await self._local_bulb_command(bulb, plist) 183 else: 184 await self._run_action_list(bulb, plist) 185 186 async def set_brightness(self, bulb: Device, brightness: int): 187 plist = [ 188 create_pid_pair(PropertyIDs.BRIGHTNESS, str(brightness)) 189 ] 190 191 if bulb.type in [ 192 DeviceTypes.LIGHT 193 ]: 194 await self._set_property_list(bulb, plist) 195 if bulb.type in [ 196 DeviceTypes.MESH_LIGHT 197 ]: 198 await self._local_bulb_command(bulb, plist) 199 200 async def music_mode_on(self, bulb: Device): 201 plist = [ 202 create_pid_pair(PropertyIDs.LIGHTSTRIP_MUSIC_MODE, "1") 203 ] 204 205 await self._run_action_list(bulb, plist) 206 207 async def music_mode_off(self, bulb: Device): 208 plist = [ 209 create_pid_pair(PropertyIDs.LIGHTSTRIP_MUSIC_MODE, "0") 210 ] 211 212 await self._run_action_list(bulb, plist)
Base service class for interacting with Wyze devices.
67 async def update(self, bulb: Bulb) -> Bulb: 68 # Get updated device_params 69 async with BaseService._update_lock: 70 bulb.device_params = await self.get_updated_params(bulb.mac) 71 72 device_info = await self._get_property_list(bulb) 73 for property_id, value in device_info: 74 if property_id == PropertyIDs.BRIGHTNESS: 75 bulb.brightness = int(float(value)) 76 elif property_id == PropertyIDs.COLOR_TEMP: 77 try: 78 bulb.color_temp = int(value) 79 except ValueError: 80 bulb.color_temp = 2700 81 elif property_id == PropertyIDs.ON: 82 bulb.on = value == "1" 83 elif property_id == PropertyIDs.AVAILABLE: 84 bulb.available = value == "1" 85 elif ( 86 property_id == PropertyIDs.COLOR 87 and bulb.type in [DeviceTypes.LIGHTSTRIP, DeviceTypes.MESH_LIGHT] 88 ): 89 bulb.color = value 90 elif property_id == PropertyIDs.COLOR_MODE: 91 bulb.color_mode = value 92 elif property_id == PropertyIDs.SUN_MATCH: 93 bulb.sun_match = value == "1" 94 elif property_id == PropertyIDs.LIGHTSTRIP_EFFECTS: 95 bulb.effects = value 96 elif property_id == PropertyIDs.LIGHTSTRIP_MUSIC_MODE: 97 bulb.music_mode = value == "1" 98 99 return bulb
101 async def get_bulbs(self) -> List[Bulb]: 102 if self._devices is None: 103 self._devices = await self.get_object_list() 104 105 bulbs = [ 106 device 107 for device in self._devices 108 if device.type in [DeviceTypes.LIGHT, 109 DeviceTypes.MESH_LIGHT, 110 DeviceTypes.LIGHTSTRIP] 111 ] 112 113 return [Bulb(bulb.raw_dict) for bulb in bulbs]
115 async def turn_on(self, bulb: Bulb, local_control, options=None): 116 plist = [ 117 create_pid_pair(PropertyIDs.ON, "1") 118 ] 119 120 if options is not None: 121 plist.extend(options) 122 123 if bulb.type is DeviceTypes.LIGHT: 124 await self._set_property_list(bulb, plist) 125 126 elif ( 127 bulb.type in [DeviceTypes.MESH_LIGHT, DeviceTypes.LIGHTSTRIP] 128 ): 129 # Local Control 130 if local_control and not bulb.cloud_fallback: 131 await self._local_bulb_command(bulb, plist) 132 133 # Cloud Control 134 elif bulb.type is DeviceTypes.MESH_LIGHT: # Sun match for mesh bulbs needs to be set on a different endpoint for some reason 135 for item in plist: 136 if item["pid"] == PropertyIDs.SUN_MATCH.value: 137 await self._set_property_list(bulb, [item]) 138 plist.remove(item) 139 await self._run_action_list(bulb, plist) 140 else: 141 await self._run_action_list(bulb, plist) # Lightstrips
143 async def turn_off(self, bulb: Bulb, local_control): 144 plist = [ 145 create_pid_pair(PropertyIDs.ON, "0") 146 ] 147 148 if bulb.type in [ 149 DeviceTypes.LIGHT 150 ]: 151 await self._set_property_list(bulb, plist) 152 elif ( 153 bulb.type in [DeviceTypes.MESH_LIGHT, DeviceTypes.LIGHTSTRIP] 154 ): 155 if local_control and not bulb.cloud_fallback: 156 await self._local_bulb_command(bulb, plist) 157 else: 158 await self._run_action_list(bulb, plist)
160 async def set_color_temp(self, bulb: Bulb, color_temp: int): 161 plist = [ 162 create_pid_pair(PropertyIDs.COLOR_TEMP, str(color_temp)) 163 ] 164 165 if bulb.type in [ 166 DeviceTypes.LIGHT 167 ]: 168 await self._set_property_list(bulb, plist) 169 elif bulb.type in [ 170 DeviceTypes.MESH_LIGHT 171 ]: 172 await self._local_bulb_command(bulb, plist)
174 async def set_color(self, bulb: Bulb, color: str, local_control): 175 plist = [ 176 create_pid_pair(PropertyIDs.COLOR, str(color)) 177 ] 178 if bulb.type in [ 179 DeviceTypes.MESH_LIGHT 180 ]: 181 if local_control and not bulb.cloud_fallback: 182 await self._local_bulb_command(bulb, plist) 183 else: 184 await self._run_action_list(bulb, plist)
186 async def set_brightness(self, bulb: Device, brightness: int): 187 plist = [ 188 create_pid_pair(PropertyIDs.BRIGHTNESS, str(brightness)) 189 ] 190 191 if bulb.type in [ 192 DeviceTypes.LIGHT 193 ]: 194 await self._set_property_list(bulb, plist) 195 if bulb.type in [ 196 DeviceTypes.MESH_LIGHT 197 ]: 198 await self._local_bulb_command(bulb, plist)