/ adafruit_hue.py
adafruit_hue.py
1 # The MIT License (MIT) 2 # 3 # Copyright (c) 2019 Brent Rubell for Adafruit Industries 4 # 5 # Permission is hereby granted, free of charge, to any person obtaining a copy 6 # of this software and associated documentation files (the "Software"), to deal 7 # in the Software without restriction, including without limitation the rights 8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 # copies of the Software, and to permit persons to whom the Software is 10 # furnished to do so, subject to the following conditions: 11 # 12 # The above copyright notice and this permission notice shall be included in 13 # all copies or substantial portions of the Software. 14 # 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 # THE SOFTWARE. 22 """ 23 `adafruit_hue` 24 ================================================================================ 25 26 CircuitPython helper library for the Philips Hue 27 28 * Author(s): Brent Rubell 29 30 Implementation Notes 31 -------------------- 32 33 **Software and Dependencies:** 34 35 * Adafruit CircuitPython firmware for the supported boards: 36 https://github.com/adafruit/circuitpython/releases 37 38 * Adafruit ESP32SPI or ESP_ATcontrol library: 39 https://github.com/adafruit/Adafruit_CircuitPython_ESP32SPI 40 https://github.com/adafruit/Adafruit_CircuitPython_ESP_ATcontrol 41 42 * SimpleIO library: 43 https://github.com/adafruit/Adafruit_CircuitPython_SimpleIO 44 """ 45 import time 46 from random import randint 47 from simpleio import map_range 48 49 __version__ = "0.0.0-auto.0" 50 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Hue.git" 51 52 53 class Bridge: 54 """ 55 HTTP Interface for interacting with a Philips Hue Bridge. 56 """ 57 58 def __init__(self, wifi_manager, bridge_ip=None, username=None): 59 """ 60 Creates an instance of the Philips Hue Bridge Interface. 61 :param wifi_manager wifi_manager: WiFiManager from ESPSPI_WiFiManager/ESPAT_WiFiManager 62 """ 63 wifi_type = str(type(wifi_manager)) 64 if "ESPSPI_WiFiManager" in wifi_type or "ESPAT_WiFiManager" in wifi_type: 65 self._wifi = wifi_manager 66 else: 67 raise TypeError("This library requires a WiFiManager object.") 68 self._ip = bridge_ip 69 self._username = username 70 if bridge_ip and username is not None: 71 self._bridge_url = "http://{}/api".format(self._ip) 72 self._username_url = self._bridge_url + "/" + self._username 73 74 @staticmethod 75 def rgb_to_hsb(rgb): 76 """Returns RGB values as a HSL tuple. 77 :param list rgb: RGB Values 78 """ 79 r = rgb[0] / 255 80 g = rgb[1] / 255 81 b = rgb[2] / 255 82 c_max = max(r, g, b) 83 c_min = min(r, g, b) 84 delta = c_max - c_min 85 light = (c_max + c_min) / 2 86 if delta == 0.0: 87 hue = 0 88 sat = 0 89 else: 90 if light < 0.5: 91 sat = (c_max - c_min) / (c_max + c_min) 92 else: 93 sat = (c_max - c_min) / (2.0 - c_max - c_min) 94 if c_max == r: 95 hue = (g - b) / (c_max - c_min) 96 elif c_max == g: 97 hue = 2.0 + (b - r) / (c_max - c_min) 98 else: 99 hue = 4.0 + (r - g) / (c_max - c_min) 100 hue *= 60 101 if hue < 0: 102 hue += 360 103 hue = map_range(hue, 0, 360, 0, 65535) 104 sat = map_range(sat * 100, 0, 100, 0, 254) 105 light = map_range(light * 100, 0, 100, 0, 254) 106 return round(hue), round(sat, 3), round(light, 2) 107 108 # Hue Core API 109 def discover_bridge(self): 110 """Discovers Philips Hue Bridge IP from the hosted broker discovery service. 111 Returns the bridge's IP address. 112 """ 113 try: 114 resp = self._wifi.get("https://discovery.meethue.com") 115 json_data = resp.json() 116 bridge_ip = json_data[0]["internalipaddress"] 117 resp.close() 118 except: 119 raise TypeError( 120 "Ensure the Philips Bridge and CircuitPython device\ 121 are both on the same WiFi network." 122 ) 123 self._ip = bridge_ip 124 # set up hue bridge address path 125 self._bridge_url = "http://{}/api".format(self._ip) 126 return self._ip 127 128 def register_username(self): 129 """Attempts to register a Hue application username for use with your bridge. 130 Provides a 30 second delay to press the link button on the bridge. 131 Returns username or None. 132 """ 133 self._bridge_url = "http://{}/api".format(self._ip) 134 data = {"devicetype": "CircuitPython#pyportal{0}".format(randint(0, 100))} 135 resp = self._wifi.post(self._bridge_url, json=data) 136 connection_attempts = 1 137 username = None 138 while username is None and connection_attempts > 0: 139 resp = self._wifi.post(self._bridge_url, json=data) 140 json = resp.json()[0] 141 if json.get("success"): 142 username = str(json["success"]["username"]) 143 self._username_url = self._bridge_url + "/" + username 144 connection_attempts -= 1 145 time.sleep(1) 146 resp.close() 147 return username 148 149 # Lights API 150 def show_light_info(self, light_id): 151 """Gets the attributes and state of a given light. 152 :param int light_id: Light identifier. 153 """ 154 resp = self._get("{0}/lights/{1}".format(self._username_url, light_id)) 155 return resp 156 157 def set_light(self, light_id, **kwargs): 158 """Allows the user to turn the light on and off, modify the hue and effects. 159 You can pass the following as valid kwargs into this method: 160 :param bool on: On/Off state of the light 161 :param int bri: Brightness value of the light, 0-100% (1 to 254) 162 :param int hue: Hue value to set the light, in degrees (0 to 360) (0 to 65535) 163 :param int sat: Saturation of the light, 0-100% (0 to 254) 164 (more settings at: 165 https://developers.meethue.com/develop/hue-api/lights-api/#set-light-state ) 166 """ 167 resp = self._put( 168 "{0}/lights/{1}/state".format(self._username_url, light_id), kwargs 169 ) 170 return resp 171 172 def toggle_light(self, light_id): 173 """Gets and toggles the current state of a specified light. 174 :param int light_id: Light identifier. 175 """ 176 light_state = self.get_light(light_id) 177 light_state = not light_state["state"]["on"] 178 resp = self.set_light(light_id, on=light_state) 179 return resp 180 181 def get_light(self, light_id): 182 """Gets the attributes and state of a provided light. 183 :param int light_id: Light identifier. 184 """ 185 resp = self._get("{0}/lights/{1}".format(self._username_url, light_id)) 186 return resp 187 188 def get_lights(self): 189 """Returns all the light resources available for a bridge. 190 """ 191 resp = self._get(self._username_url + "/lights") 192 return resp 193 194 # Groups API 195 def create_group(self, lights, group_id): 196 """Creates a new group containing the lights specified and optional name. 197 :param list lights: List of light identifiers. 198 :param str group_id: Optional group name. 199 """ 200 data = {"lights": lights, "name": group_id, "type": group_id} 201 resp = self._post(self._username_url + "/groups", data) 202 return resp 203 204 def set_group(self, group_id, **kwargs): 205 """Allows the user to turn the light on and off, modify the hue and effects. 206 :param int group_id: Group identifier. 207 You can pass the following as (optional) valid kwargs into this method: 208 :param bool on: On/Off state of the light 209 :param int bri: Brightness value of the light (1 to 254) 210 :param int hue: Hue value to set the light to (0 to 65535) 211 :param int sat: Saturation of the light (0 to 254) 212 (more settings at 213 https://developers.meethue.com/develop/hue-api/lights-api/#set-light-state ) 214 """ 215 resp = self._put( 216 "{0}/groups/{1}/action".format(self._username_url, group_id), kwargs 217 ) 218 return resp 219 220 def get_groups(self): 221 """Returns all the light groups available for a bridge. 222 """ 223 resp = self._get(self._username_url + "/groups") 224 return resp 225 226 # Scene API 227 def set_scene(self, group_id, scene_id): 228 """Sets a group scene. 229 :param str scene: The scene identifier 230 """ 231 # To recall an existing scene, use the Groups API. 232 self.set_group(group_id, scene=scene_id) 233 234 def get_scenes(self): 235 """Returns a list of all scenes currently stored in the bridge. 236 """ 237 resp = self._get(self._username_url + "/scenes") 238 return resp 239 240 # HTTP Helpers for the Hue API 241 def _post(self, path, data): 242 """POST data 243 :param str path: Formatted Hue API URL 244 :param json data: JSON data to POST to the Hue API. 245 """ 246 resp = self._wifi.post(path, json=data) 247 resp_json = resp.json() 248 resp.close() 249 return resp_json 250 251 def _put(self, path, data): 252 """PUT data 253 :param str path: Formatted Hue API URL 254 :param json data: JSON data to PUT to the Hue API. 255 """ 256 resp = self._wifi.put(path, json=data) 257 resp_json = resp.json() 258 resp.close() 259 return resp_json 260 261 def _get(self, path, data=None): 262 """GET data 263 :param str path: Formatted Hue API URL 264 :param json data: JSON data to GET from the Hue API. 265 """ 266 resp = self._wifi.get(path, json=data) 267 resp_json = resp.json() 268 resp.close() 269 return resp_json