├── 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 | 
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 | )
--------------------------------------------------------------------------------