├── LICENSE.txt ├── MANIFEST ├── README.md ├── pyyeelight ├── __init__.py ├── tests │ └── __init__.py ├── yeelightAPICall.py └── yeelightMessage.py ├── setup.cfg └── setup.py /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Hydreliox 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | setup.cfg 3 | setup.py 4 | pyyeelight/__init__.py 5 | pyyeelight/yeelightAPICall.py 6 | pyyeelight/yeelightMessage.py 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PyYeelight 2 | =================== 3 | 4 | ![YeelightBulb](http://www.yeelight.com/yeelight2016/i/image/newindex/topic2.png) 5 | 6 | ### Library 7 | 8 | **PyYeelight** :bulb: is a simple python3 library for the Yeelight wifi bulb. 9 | 10 | - This library is under development but still usable on your local network only 11 | - This library has been developed using this [documentation](http://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf) available on the official [Yeelight Website](http://www.yeelight.com/en_US/) 12 | - The tests are only made with the [YLDP03YL](http://www.yeelight.com/en_US/product/wifi-led-c) model which is the color wifi model. If you have any other bulb that can match with this library please feel free to PR. 13 | - Thanks to [Marc Pabst](https://github.com/mxtra) for his work on Yeelight. His work helped me a lot ! 14 | 15 | ### Tests 16 | 17 | Tests are only made with a YLDP03YL model. Because it's the only hardware model I own. If you have bugs with another kind of model, you could open an issue and propose some code to make the library available on all models 18 | 19 | ### TODO 20 | 21 | - [ ] Add test coverage 22 | - [x] Add the library to Pypi 23 | - [ ] Add sleep timer API call (using cron) 24 | - [ ] Add color flow API call (using start_cf) 25 | - [ ] Add music mode 26 | - [ ] Add scene API call 27 | - [ ] Correct some bugs (see TODO in code) 28 | - [ ] Handle Notifications send by bulb (to discover bulbs and adjust properties 29 | - [ ] .... and lots of things ! 30 | 31 | ### How-To 32 | 33 | 1. You have to setup your bulb using Yeelight app. ( [Android](https://play.google.com/store/apps/details?id=com.yeelight.cherry&hl=fr), [IOS](https://itunes.apple.com/us/app/yeelight/id977125608?mt=8) ). 34 | 2. In the bulb property, you have to enable "Developer Mode" 35 | 3. Determine your bulb ip (using router, software, ping and so on) 36 | 4. Open your favorite python3 console 37 | ``` 38 | >>> import pyyeelight 39 | >>> bulb = pyyeelight.YeelightBulb("192.168.1.25") 40 | >>> bulb.set_rgb_color(255,0,0) 41 | ``` 42 | 43 | ### What can I add ? 44 | 45 | - PR are welcome 46 | - Advices on library structure are welcome too, this is one of my first python library and I'm still a noob on Python code 47 | - If you want to contact me : @hydreliox on Twitter -------------------------------------------------------------------------------- /pyyeelight/__init__.py: -------------------------------------------------------------------------------- 1 | from .yeelightAPICall import YeelightAPICall 2 | from voluptuous import Schema, All, Any, Range 3 | 4 | 5 | class YeelightBulb: 6 | """ 7 | TODO : check every method with bulb off (software off) to see if the command is allowed 8 | 9 | """ 10 | 11 | POWER_OFF = "off" 12 | POWER_ON = "on" 13 | 14 | EFFECT_SUDDEN = "sudden" 15 | EFFECT_SMOOTH = "smooth" 16 | 17 | MIN_TRANSITION_TIME = 30 18 | 19 | PROPERTY_NAME_POWER = "power" 20 | PROPERTY_NAME_BRIGHTNESS = "bright" 21 | PROPERTY_NAME_COLOR_TEMPERATURE = "ct" 22 | PROPERTY_NAME_RGB_COLOR = "rgb" 23 | PROPERTY_NAME_HUE = "hue" 24 | PROPERTY_NAME_SATURATION = "sat" 25 | PROPERTY_NAME_COLOR_MODE = "color_mode" 26 | PROPERTY_NAME_FLOW = "flowing" 27 | PROPERTY_NAME_SLEEP_REMAINING = "delayoff" 28 | PROPERTY_NAME_FLOW_PARAMETERS = "flow_params" 29 | PROPERTY_NAME_MUSIC_IS_ON = "music_on" 30 | PROPERTY_NAME_BULB_NAME = "name" 31 | 32 | ADJUST_ACTION_INCREASE = "increase" 33 | ADJUST_ACTION_DECREASE = "decrease" 34 | ADJUST_ACTION_CIRCLE = "circle" 35 | 36 | ADJUST_PROPERTY_BRIGHTNESS = "bright" 37 | ADJUST_PROPERTY_COLOR_TEMPERATURE = "ct" 38 | ADJUST_PROPERTY_COLOR = "color" 39 | 40 | def __init__(self, ip, port=55443): 41 | self.api_call = YeelightAPICall(ip, port) 42 | self.property = dict.fromkeys([self.PROPERTY_NAME_POWER, self.PROPERTY_NAME_BRIGHTNESS, 43 | self.PROPERTY_NAME_COLOR_TEMPERATURE, self.PROPERTY_NAME_RGB_COLOR, 44 | self.PROPERTY_NAME_HUE, self.PROPERTY_NAME_SATURATION, 45 | self.PROPERTY_NAME_COLOR_MODE, self.PROPERTY_NAME_FLOW, 46 | self.PROPERTY_NAME_SLEEP_REMAINING, self.PROPERTY_NAME_FLOW_PARAMETERS, 47 | self.PROPERTY_NAME_MUSIC_IS_ON, self.PROPERTY_NAME_BULB_NAME]) 48 | self.refresh_property() 49 | 50 | def is_on(self): 51 | return self.property[self.PROPERTY_NAME_POWER] == self.POWER_ON 52 | 53 | def is_off(self): 54 | return self.property[self.PROPERTY_NAME_POWER] == self.POWER_OFF 55 | 56 | def get_property(self, property_name): 57 | try: 58 | return self.property[property_name] 59 | except KeyError: 60 | print("This property '{}' is not available".format(property_name)) 61 | return None 62 | 63 | def get_all_properties(self): 64 | return self.property 65 | 66 | def refresh_property(self): 67 | # Generate a list of str where each str is a property name 68 | prop_list = [] 69 | for prop in self.property.keys(): 70 | prop_list.append(prop) 71 | # Send command to the bulb 72 | self.api_call.operate_on_bulb("get_prop", prop_list) 73 | # Affect each result to the right property dict keys 74 | for i in range(len(prop_list)): 75 | self.property[prop_list[i]] = self.api_call.get_response()[i] 76 | 77 | def set_color_temperature(self, temperature, effect=EFFECT_SUDDEN, transition_time=MIN_TRANSITION_TIME): 78 | """ 79 | Set the white color temperature. The bulb must be switched on. 80 | 81 | :param temperature: color temperature to set. It can be between 1700 and 6500 K 82 | :param effect: if the change is made suddenly or smoothly 83 | :param transition_time: in case the change is made smoothly, time in ms that change last 84 | 85 | :type temperature: int 86 | :type effect: str 87 | :type transition_time : int 88 | """ 89 | # Check bulb state 90 | if self.is_off(): 91 | raise Exception("set_color_temperature can't be used if the bulb is off. Turn it on first") 92 | # Input validation 93 | schema = Schema({'temperature': All(int, Range(min=1700, max=6500)), 94 | 'effect': Any(self.EFFECT_SUDDEN, self.EFFECT_SMOOTH), 95 | 'transition_time': All(int, Range(min=30))}) 96 | schema({'temperature': temperature, 'effect': effect, 'transition_time': transition_time}) 97 | # Send command 98 | params = [temperature, effect, transition_time] 99 | self.api_call.operate_on_bulb("set_ct_abx", params) 100 | # Update property 101 | self.property[self.PROPERTY_NAME_COLOR_TEMPERATURE] = temperature 102 | 103 | def set_rgb_color(self, red, green, blue, effect=EFFECT_SUDDEN, transition_time=MIN_TRANSITION_TIME): 104 | """ 105 | Set the color of the bulb using rgb code. The bulb must be switched on. 106 | 107 | :param red: Red component of the color between 0 and 255 108 | :param green: Green component of the color between 0 and 255 109 | :param blue: Blue component of the color between 0 and 255 110 | :param effect: if the change is made suddenly or smoothly 111 | :param transition_time: in case the change is made smoothly, time in ms that change last 112 | 113 | :type red: int 114 | :type green: int 115 | :type blue: int 116 | :type effect: str 117 | :type transition_time : int 118 | """ 119 | # Check bulb state 120 | if self.is_off(): 121 | raise Exception("set_rgb_color can't be used if the bulb is off. Turn it on first") 122 | # Input validation 123 | schema = Schema({'red': All(int, Range(min=0, max=255)), 124 | 'green': All(int, Range(min=0, max=255)), 125 | 'blue': All(int, Range(min=0, max=255)), 126 | 'effect': Any(self.EFFECT_SUDDEN, self.EFFECT_SMOOTH), 127 | 'transition_time': All(int, Range(min=30))}) 128 | schema({'red': red, 'green': green, 'blue': blue, 'effect': effect, 'transition_time': transition_time}) 129 | # Send command 130 | rgb = (red*65536) + (green*256) + blue 131 | params = [rgb, effect, transition_time] 132 | self.api_call.operate_on_bulb("set_rgb", params) 133 | # Update property 134 | self.property[self.PROPERTY_NAME_RGB_COLOR] = rgb 135 | 136 | def set_hsv_color(self, hue, saturation, effect=EFFECT_SUDDEN, transition_time=MIN_TRANSITION_TIME): 137 | """ 138 | Set the color of the bulb using hsv code. The bulb must be switched on. 139 | TODO : Resolve bug found trying to set hue to 100 and sat to 100 (General error) 140 | 141 | :param hue: Hue component of the color between 0 and 359 142 | :param saturation: Saturation component of the color between 0 and 100 143 | :param effect: if the change is made suddenly or smoothly 144 | :param transition_time: in case the change is made smoothly, time in ms that change last 145 | 146 | :type hue: int 147 | :type saturation: int 148 | :type effect: str 149 | :type transition_time : int 150 | """ 151 | # Check bulb state 152 | if self.is_off(): 153 | raise Exception("set_hsv_color can't be used if the bulb is off. Turn it on first") 154 | # Input validation 155 | schema = Schema({'hue': All(int, Range(min=0, max=359)), 156 | 'saturation': All(int, Range(min=0, max=100)), 157 | 'effect': Any(self.EFFECT_SUDDEN, self.EFFECT_SMOOTH), 158 | 'transition_time': All(int, Range(min=30))}) 159 | schema({'hue': hue, 'saturation': saturation, 'effect': effect, 'transition_time': transition_time}) 160 | # Send command 161 | params = [hue, saturation, effect, transition_time] 162 | self.api_call.operate_on_bulb("set_hsv", params) 163 | # Update property 164 | self.property[self.PROPERTY_NAME_HUE] = hue 165 | self.property[self.PROPERTY_NAME_SATURATION] = saturation 166 | 167 | def set_brightness(self, brightness, effect=EFFECT_SUDDEN, transition_time=MIN_TRANSITION_TIME): 168 | """ 169 | This method is used to change the brightness of a smart LED 170 | 171 | :param brightness: is the target brightness. The type is integer and ranges from 1 to 100. The 172 | brightness is a percentage instead of a absolute value. 100 means maximum brightness 173 | while 1 means the minimum brightness. 174 | :param effect: if the change is made suddenly or smoothly 175 | :param transition_time: in case the change is made smoothly, time in ms that change last 176 | 177 | :type brightness: int 178 | :type effect: str 179 | :type transition_time : int 180 | """ 181 | # Check bulb state 182 | if self.is_off(): 183 | raise Exception("set_brightness can't be used if the bulb is off. Turn it on first") 184 | # Input validation 185 | schema = Schema({'brightness': All(int, Range(min=1, max=100)), 186 | 'effect': Any(self.EFFECT_SUDDEN, self.EFFECT_SMOOTH), 187 | 'transition_time': All(int, Range(min=30))}) 188 | schema({'brightness': brightness, 'effect': effect, 'transition_time': transition_time}) 189 | # Send command 190 | params = [brightness, effect, transition_time] 191 | self.api_call.operate_on_bulb("set_bright", params) 192 | # Update property 193 | self.property[self.PROPERTY_NAME_BRIGHTNESS] = brightness 194 | 195 | def turn_on(self, effect=EFFECT_SUDDEN, transition_time=MIN_TRANSITION_TIME): 196 | """ 197 | This method is used to switch on the smart LED (software managed on). 198 | 199 | :param effect: if the change is made suddenly or smoothly 200 | :param transition_time: in case the change is made smoothly, time in ms that change last 201 | 202 | :type effect: str 203 | :type transition_time : int 204 | """ 205 | # Check bulb state 206 | if self.is_on(): 207 | return 208 | else: 209 | # Input validation 210 | schema = Schema( 211 | {'effect': Any(self.EFFECT_SUDDEN, self.EFFECT_SMOOTH), 'transition_time': All(int, Range(min=30))}) 212 | schema({'effect': effect, 'transition_time': transition_time}) 213 | # Send command 214 | params = ["on", effect, transition_time] 215 | self.api_call.operate_on_bulb("set_power", params) 216 | # Update property 217 | self.property[self.PROPERTY_NAME_POWER] = self.POWER_ON 218 | 219 | def turn_off(self, effect=EFFECT_SUDDEN, transition_time=MIN_TRANSITION_TIME): 220 | """ 221 | This method is used to switch off the smart LED (software managed off). 222 | 223 | :param effect: if the change is made suddenly or smoothly 224 | :param transition_time: in case the change is made smoothly, time in ms that change last 225 | 226 | :type effect: str 227 | :type transition_time : int 228 | """ 229 | # Check bulb state 230 | if self.is_off(): 231 | return 232 | else: 233 | # Input validation 234 | schema = Schema( 235 | {'effect': Any(self.EFFECT_SUDDEN, self.EFFECT_SMOOTH), 'transition_time': All(int, Range(min=30))}) 236 | schema({'effect': effect, 'transition_time': transition_time}) 237 | # Send command 238 | params = ["off", effect, transition_time] 239 | self.api_call.operate_on_bulb("set_power", params) 240 | # Update property 241 | self.property[self.PROPERTY_NAME_POWER] = self.POWER_OFF 242 | 243 | def toggle(self): 244 | """ 245 | This method is used to toggle the smart LED (software managed off). 246 | This method is defined because sometimes user may just want to flip the state without knowing the 247 | current state 248 | """ 249 | # Send command 250 | self.api_call.operate_on_bulb("toggle") 251 | # Update property 252 | if self.is_on(): 253 | self.property[self.PROPERTY_NAME_POWER] = self.POWER_OFF 254 | elif self.is_off(): 255 | self.property[self.PROPERTY_NAME_POWER] = self.POWER_ON 256 | 257 | def save_state(self): 258 | """ 259 | This method is used to save current state of smart LED in persistent memory. So if user powers off and 260 | then powers on the smart LED again (hard power reset), the smart LED will show last saved state. 261 | 262 | For example, if user likes the current color (red) and brightness (50%) and want to make this state as a 263 | default initial state (every time the smart LED is powered), then he can use save_state to do a snapshot. 264 | 265 | Only accepted if the smart LED is currently in "on" state. 266 | """ 267 | # Check bulb state 268 | if self.is_off(): 269 | raise Exception("save_state can't be used if the bulb is off. Turn it on first") 270 | # Send command 271 | self.api_call.operate_on_bulb("set_default") 272 | 273 | def adjust(self, action, prop): 274 | """ 275 | This method is used to change brightness, CT or color of a smart LED without knowing the current value, 276 | it's main used by controllers. 277 | 278 | :param action: The direction of the adjustment. The valid value can be: 279 | ADJUST_ACTION_INCREASE: increase the specified property 280 | ADJUST_ACTION_DECREASE : decrease the specified property 281 | ADJUST_ACTION_CIRCLE : increase the specified property, after it reaches the max value, 282 | go back to minimum value. 283 | :param prop: The property to adjust. The valid value can be: 284 | ADJUST_PROPERTY_BRIGHTNESS: adjust brightness. 285 | ADJUST_PROPERTY_COLOR_TEMPERATURE: adjust color temperature. 286 | ADJUST_PROPERTY_COLOR: adjust color. (When “prop" is “color", the “action" can only 287 | be “circle", otherwise, it will be deemed as invalid request.) 288 | 289 | :type action: str 290 | :type prop: str 291 | """ 292 | # Input validation 293 | schema = Schema( 294 | {'action': Any(self.ADJUST_ACTION_CIRCLE, self.ADJUST_ACTION_DECREASE, self.ADJUST_ACTION_INCREASE), 295 | 'prop': Any(self.ADJUST_PROPERTY_BRIGHTNESS, self.ADJUST_PROPERTY_COLOR, 296 | self.ADJUST_PROPERTY_COLOR_TEMPERATURE)}) 297 | schema({'action': action, 'prop': prop}) 298 | # Send command 299 | params = [action, prop] 300 | self.api_call.operate_on_bulb("set_adjust", params) 301 | # Update property 302 | self.refresh_property() # TODO : do more test to handle property change without sending another command 303 | 304 | # WARNING : This method is in the API documentation but after some test the method is not supported by Bulbs 305 | # def set_name(self, name): 306 | # """ 307 | # This method is used to name the device. The name will be stored on the device and reported in 308 | # discovering response. User can also read the name through “get_property” method. 309 | # 310 | # NOTE: When using Yeelight official App, the device name is stored on cloud. This method instead store 311 | # the name on persistent memory of the device, so the two names could be different. 312 | # 313 | # :param name: name stored in the device memory 314 | # 315 | # :type name: str 316 | # """ 317 | # # Input validation 318 | # schema = Schema({'name': str }) 319 | # schema({'name': name}) 320 | # # Send command 321 | # self.api_call.operate_on_bulb("set_name", [name]) 322 | # # Update property 323 | # #self.property[self.PROPERTY_NAME_BRIGHTNESS] = brightness 324 | -------------------------------------------------------------------------------- /pyyeelight/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HydrelioxGitHub/PyYeelight/74e1261f04192fffc56e92cb0262c9068835c926/pyyeelight/tests/__init__.py -------------------------------------------------------------------------------- /pyyeelight/yeelightAPICall.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import uuid 3 | from .yeelightMessage import YeelightCommand, YeelightResponse 4 | 5 | 6 | class YeelightAPICall: 7 | """ 8 | Class used to send and receive thought sockets command and response messages 9 | """ 10 | 11 | DEFAULT_PORT = 55443 12 | 13 | def __init__(self, ip, port=DEFAULT_PORT): 14 | """ 15 | Build the API Call 16 | 17 | :param ip: ip of the bulb you want to manipulate 18 | :param port: port used to send and receive messages (should be the default port) 19 | 20 | """ 21 | self.ip = ip 22 | self.port = port 23 | self.command_id = 0 24 | self.command = None 25 | self.response = None 26 | 27 | def get_response(self): 28 | return self.response.result 29 | 30 | def get_command(self): 31 | return self.command 32 | 33 | def next_cmd_id(self): 34 | """ 35 | Each command should be run with an unique id, uuid generate this id 36 | This id is converted to an 16 bit int that can be sent to the bulb 37 | Test with 32 and 64bits length int failed 38 | :return: Unique id 39 | :rtype: int 40 | """ 41 | self.command_id = uuid.uuid4().int & (1 << 16) - 1 42 | return self.command_id 43 | 44 | def operate_on_bulb(self, method, params=None): 45 | """ 46 | Build socket and send command to the bulb through it 47 | 48 | :param method: method you want to use 49 | :param params: parameters needed for this method (can be a string if ony one parameter is needed) 50 | 51 | :type method: str 52 | :type params: list of str 53 | """ 54 | # Get the message 55 | self.command = YeelightCommand(self.next_cmd_id(), method, params) 56 | # Send with socket 57 | tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 58 | tcp_socket.connect((self.ip, int(self.port))) 59 | tcp_socket.send(self.command.get_message().encode()) 60 | data = tcp_socket.recv(4096) 61 | tcp_socket.close() 62 | # Process the response 63 | self.response = YeelightResponse(data.decode(), self.command) 64 | -------------------------------------------------------------------------------- /pyyeelight/yeelightMessage.py: -------------------------------------------------------------------------------- 1 | class YeelightMessage: 2 | """ 3 | Generic class for all type of messages sent and received from a Yeelight Bulb 4 | """ 5 | MESSAGE_TYPE_COMMAND = "command" 6 | MESSAGE_TYPE_RESPONSE = "response" 7 | MESSAGE_TYPE_NOTIFICATION = "notification" 8 | 9 | COMMAND_BODY = '{{"id":{},"method":"{}","params":[{}]}}\r\n' 10 | 11 | RESPONSE_ID = "id" 12 | RESPONSE_RESULT = "result" 13 | RESPONSE_ERROR = "error" 14 | 15 | ERROR_MESSAGE = "message" 16 | ERROR_CODE = "code" 17 | 18 | def __init__(self): 19 | """ 20 | The generic class has no type 21 | """ 22 | self.type = None 23 | 24 | def get_type(self): 25 | return self.type 26 | 27 | 28 | class YeelightCommand(YeelightMessage): 29 | """ 30 | Class used to handle command message sent to a bulb 31 | """ 32 | 33 | def __init__(self, unique_id, method, params=None): 34 | """ 35 | For some methods (stop_cf, toggle ...), params are not necessary (see API doc) 36 | 37 | :param unique_id: id to make the command unique 38 | :param method: method name (see API doc) 39 | :param params: parameters used with the method (see API doc) 40 | 41 | :type unique_id : int 42 | :type method : str 43 | :type params : list 44 | """ 45 | super().__init__() 46 | self.type = self.MESSAGE_TYPE_COMMAND 47 | self.method = method 48 | self.params = params 49 | self.command_id = unique_id 50 | self.message = None 51 | self.build_message() 52 | 53 | def build_message(self): 54 | """ 55 | Make the one string message sent to the bulb 56 | """ 57 | if self.params is None: 58 | inline_params = "" 59 | else: 60 | # Put all params in one string 61 | inline_params = "" 62 | if type(self.params) is list: 63 | for x in self.params: 64 | if x != self.params[0]: 65 | inline_params += ", " 66 | if type(x) is int: 67 | inline_params += str(x) 68 | else: 69 | inline_params += '"{}"'.format(x) 70 | else: 71 | inline_params += '"{}"'.format(self.params) 72 | 73 | # Build message with that string 74 | self.message = self.COMMAND_BODY.format(str(self.command_id), self.method, inline_params) 75 | 76 | def get_message(self): 77 | """ 78 | Return the one string message 79 | :rtype: str 80 | """ 81 | return self.message 82 | 83 | def get_command_id(self): 84 | """ 85 | Get the unique id 86 | :rtype: int 87 | """ 88 | return self.command_id 89 | 90 | def __str__(self): 91 | """ 92 | Verbose description of the command 93 | :rtype: str 94 | """ 95 | return 'Command : "{}"\nParameters : "{}"\nId : {}'.format(self.method, self.params, self.command_id) 96 | 97 | 98 | class YeelightResponse(YeelightMessage): 99 | """ 100 | Class used to handle a response from the bulb to a command message 101 | """ 102 | 103 | def __init__(self, raw_response, command): 104 | """ 105 | Initiate the response 106 | :param raw_response: Not decoded one string response 107 | :param command : command used to generate the bulb response 108 | :type raw_response: str 109 | :type command: YeelightCommand 110 | """ 111 | super().__init__() 112 | self.type = self.MESSAGE_TYPE_RESPONSE 113 | self.command = command 114 | self.response_id = None 115 | self.result = None 116 | self.decode_response(raw_response) 117 | 118 | def decode_response(self, raw_response): 119 | """ 120 | Put in self.result the data from the response 121 | Can generate exception if the command and the response id does not match 122 | of if the response is an error 123 | :param raw_response: Not decoded one string response 124 | """ 125 | # Transform response into a dict 126 | import json 127 | data = json.loads(raw_response) 128 | # Retrieve the response id 129 | self.response_id = data[self.RESPONSE_ID] 130 | # Check if the response id match the command id 131 | self.check_id() 132 | # Get response data 133 | if self.RESPONSE_RESULT in data: 134 | self.result = data[self.RESPONSE_RESULT] 135 | elif self.RESPONSE_ERROR in data: 136 | # If the response is an error raise YeelightError Exception 137 | message = data[self.RESPONSE_ERROR][self.ERROR_MESSAGE] 138 | code = data[self.RESPONSE_ERROR][self.ERROR_CODE] 139 | raise YeelightError(message, code, self.command) 140 | 141 | def check_id(self): 142 | """ 143 | Raise an exception if the command and the response id does not match 144 | """ 145 | if self.response_id != self.command.get_command_id(): 146 | raise Exception( 147 | "Error decoding response : the response id {} doesn't match the command id {}".format(self.response_id, 148 | self.command.get_command_id())) 149 | 150 | 151 | class YeelightNotification(YeelightMessage): 152 | """ 153 | Class used to handle notification message generate from the bulb 154 | """ 155 | 156 | def __init__(self, ): 157 | super().__init__() 158 | self.type = self.MESSAGE_TYPE_NOTIFICATION 159 | 160 | 161 | class YeelightError(Exception): 162 | """ 163 | Exception for error message response from the bulb 164 | """ 165 | 166 | def __init__(self, error_message, error_code, command): 167 | """ 168 | This exception is mainly use to correctly generate the error message is the stack trace 169 | :param error_message: error message from the bulb 170 | :param error_code: error code generate by the bulb 171 | :param command: command that generate this response 172 | :type error_message: str 173 | :type error_code: int 174 | :type command: YeelightCommand 175 | """ 176 | message = "Sent to the Yeelight Bulb :\n{}\n".format(command.__str__()) 177 | message += "The Yeelight bulb returns the following error : {} (Code {})\n".format(error_message, error_code) 178 | Exception.__init__(self, message) 179 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | setup( 3 | name = 'pyyeelight', 4 | packages = ['pyyeelight'], # this must be the same as the name above 5 | install_requires = ['voluptuous'], 6 | version = '1.0-beta', 7 | description = 'a simple python3 library for Yeelight Wifi Bulbs', 8 | author = 'Hydreliox', 9 | author_email = 'hydreliox@gmail.com', 10 | url = 'https://github.com/HydrelioxGitHub/pyyeelight', # use the URL to the github repo 11 | download_url = 'https://github.com/HydrelioxGitHub/pyyeelight/tarball/1.0-beta', 12 | keywords = ['xiaomi', 'bulb', 'yeelight', 'API'], # arbitrary keywords 13 | classifiers = [], 14 | ) --------------------------------------------------------------------------------