1
+ import usocket as socket
2
+ import json
3
+
4
+
5
+ class YeeLightException (Exception ):
6
+ pass
7
+
8
+
9
+ class EFFECT :
10
+ SMOOTH = "smooth"
11
+ SUDDEN = "sudden"
12
+
13
+
14
+ class MODE :
15
+ NORMAL = 0
16
+ CT_MODE = 1
17
+ RGB_MODE = 2
18
+ HSV_MODE = 3
19
+ COLOR_FLOW_MODE = 4
20
+ NIGHT_LIGHT_MODE = 5
21
+
22
+
23
+ class ACTION :
24
+ LED_RECOVER_STATE = 0
25
+ LED_STAY = 1
26
+ LED_TURN_OFF = 2
27
+
28
+
29
+ class SCENE_CLASS :
30
+ COLOR = "color"
31
+ HSV = "hsv"
32
+ CT = "ct"
33
+ AUTO_DELAY_OFF = "auto_delay_off"
34
+
35
+
36
+ class SET_ADJUST_ACTION :
37
+ INCREASE = "increase"
38
+ DECREASE = "decrease"
39
+ CIRCLE = "circle"
40
+
41
+
42
+ class SET_ADJUST_PROP :
43
+ BRIGHT = "bright"
44
+ CT = "ct"
45
+ COLOR = "color"
46
+
47
+
48
+ """ API DOCS: https://www.yeelight.com/download/Yeelight_Inter-Operation_Spec.pdf """
49
+
50
+
51
+ class Bulb ():
52
+ def __init__ (self , ip , port = 55443 , debug = False ):
53
+ self .cmd_id = 0
54
+ self ._ip = ip
55
+ self ._port = port
56
+ self .debug = debug
57
+
58
+ @property
59
+ def get_ip (self ):
60
+ return self ._ip
61
+
62
+ @property
63
+ def get_port (self ):
64
+ return self ._port
65
+
66
+ def turn_on (self , effect = EFFECT .SUDDEN , duration = 30 , mode = MODE .NORMAL ):
67
+ return self ._handle_response (self ._send_message ("set_power" ,
68
+ ["on" , effect , duration , mode ]))
69
+
70
+ def turn_off (self , effect = EFFECT .SUDDEN , duration = 30 , mode = MODE .NORMAL ):
71
+ return self ._handle_response (self ._send_message ("set_power" ,
72
+ ["off" , effect , duration , mode ]))
73
+
74
+ def toggle (self ):
75
+ return self ._handle_response (self ._send_message ("toggle" ))
76
+
77
+ @property
78
+ def is_on (self ):
79
+ result = self ._handle_response (self ._send_message ("get_prop" , ["power" ]))
80
+ return result [0 ] == "on"
81
+
82
+ def change_color_temperature (self , color_temp_val , effect = EFFECT .SUDDEN , duration = 30 ):
83
+ """
84
+ :param color_temp_val: between 1700 and 6500K
85
+ """
86
+ return self ._handle_response (self ._send_message ("set_ct_abx" ,
87
+ [color_temp_val , effect , duration ]))
88
+
89
+ def set_rgb (self , r , g , b , effect = EFFECT .SUDDEN , duration = 30 ):
90
+ """
91
+ :param r: red
92
+ :param g: green
93
+ :param b: blue
94
+ """
95
+ rgb = (r * 65536 ) + (g * 256 ) + b
96
+ return self ._handle_response (self ._send_message ("set_rgb" ,
97
+ [rgb , effect , duration ]))
98
+
99
+ def set_hsv (self , hue , sat , effect = EFFECT .SUDDEN , duration = 30 ):
100
+ """
101
+ :param hue: ranges from 0 to 359
102
+ :param sat: ranges from 0 to 100
103
+ """
104
+ return self ._handle_response (self ._send_message ("set_hsv" ,
105
+ [hue , sat , effect , duration ]))
106
+
107
+ def set_brightness (self , brightness , effect = EFFECT .SUDDEN , duration = 30 ):
108
+ """
109
+ :param brightness: between 1 and 100
110
+ """
111
+ return self ._handle_response (self ._send_message ("set_bright" ,
112
+ [brightness , effect , duration ]))
113
+
114
+ def save_current_state (self ):
115
+ return self ._handle_response (self ._send_message ("set_default" ))
116
+
117
+ def start_color_flow (self , count , flow_expression , action = ACTION .LED_RECOVER_STATE ):
118
+ """
119
+ :param count: is the total number of visible state changing before color flow
120
+ stopped. 0 means infinite loop on the state changing.
121
+ :param flow_expression: is the expression of the state changing series (see API docs)
122
+ :param action: is the action taken after the flow is stopped.
123
+ 0 means smart LED recover to the state before the color flow started.
124
+ 1 means smart LED stay at the state when the flow is stopped.
125
+ 2 means turn off the smart LED after the flow is stopped.
126
+ """
127
+ return self ._handle_response (self ._send_message ("start_cf" ,
128
+ [count , action , flow_expression ]))
129
+
130
+ def stop_color_flow (self ):
131
+ return self ._handle_response (self ._send_message ("stop_cf" ))
132
+
133
+ def set_scene (self , val1 , val2 , val3 , opt = SCENE_CLASS .COLOR ):
134
+ """
135
+ :param val1: :param val2: :param val3: are class specific. (see API docs)
136
+ :param opt: can be "color", "hsv", "ct", "cf", "auto_delay_off".
137
+ "color" means change the smart LED to specified color and brightness.
138
+ "hsv" means change the smart LED to specified color and brightness.
139
+ "ct" means change the smart LED to specified ct and brightness.
140
+ "cf" means start a color flow in specified fashion.
141
+ "auto_delay_off" means turn on the smart LED to specified
142
+ brightness and start a sleep timer to turn off the light after the specified minutes.
143
+ """
144
+ return self ._handle_response (self ._send_message ("set_scene" ,
145
+ [opt , val1 , val2 , val3 ]))
146
+
147
+ def sleep_timer (self , time_minutes , type = 0 ):
148
+ return self ._handle_response (self ._send_message ("cron_add" ,
149
+ [type , time_minutes ]))
150
+
151
+ def get_background_job (self , type = 0 ):
152
+ return self ._handle_response (self ._send_message ("cron_get" ,
153
+ [type ]))
154
+
155
+ def delete_background_job (self , type = 0 ):
156
+ return self ._handle_response (self ._send_message ("cron_del" ,
157
+ [type ]))
158
+
159
+ def set_adjust (self , action = SET_ADJUST_ACTION .INCREASE , prop = SET_ADJUST_PROP .BRIGHT ):
160
+ """
161
+ :param action: the direction of the adjustment. The valid value can be:
162
+ "increase": increase the specified property
163
+ "decrease": decrease the specified property
164
+ "circle": increase the specified property, after it reaches the max
165
+ value, go back to minimum value.
166
+ :param prop: the property to adjust. The valid value can be:
167
+ "bright": adjust brightness.
168
+ "ct": adjust color temperature.
169
+ "color": adjust color. (When “prop" is “color", the “action" can only
170
+ be “circle", otherwise, it will be deemed as invalid request.)
171
+ """
172
+ return self ._handle_response (self ._send_message ("set_adjust" ,
173
+ [action , prop ]))
174
+
175
+ def adjust_brightness (self , percentage , duration = 30 ):
176
+ """
177
+ :param percentage: the percentage to be adjusted. The range is: -100 ~ 100
178
+ """
179
+ return self ._handle_response (self ._send_message ("adjust_bright" ,
180
+ [percentage , duration ]))
181
+
182
+ def adjust_color_temperature (self , percentage , duration = 30 ):
183
+ """
184
+ :param percentage: the percentage to be adjusted. The range is: -100 ~ 100
185
+ """
186
+ return self ._handle_response (self ._send_message ("adjust_ct" ,
187
+ [percentage , duration ]))
188
+
189
+ def adjust_color (self , percentage , duration = 30 ):
190
+ """
191
+ :param percentage: the percentage to be adjusted. The range is: -100 ~ 100
192
+ """
193
+ return self ._handle_response (self ._send_message ("adjust_color" ,
194
+ [percentage , duration ]))
195
+
196
+ def set_music (self , host , port , enable = True ):
197
+ """
198
+ :param host: the IP address of the music server.
199
+ :param port: the TCP port music application is listening on.
200
+ :param enable: 0: turn off music mode.
201
+ 1: turn on music mode.
202
+ """
203
+ return self ._handle_response (self ._send_message ("set_music" ,
204
+ [1 if enable else 0 , host , port ]))
205
+
206
+ def set_name (self , name ):
207
+ """
208
+ :param name: the name of the device
209
+ """
210
+ return self ._handle_response (self ._send_message ("set_name" ,
211
+ [name ]))
212
+
213
+ def _send_message (self , method , params = None ):
214
+ if params is None :
215
+ params = []
216
+
217
+ self .cmd_id += 1
218
+
219
+ message = '{{"id": {id}, "method": "{method}", "params": {params}}}\r \n ' . \
220
+ format (id = self .cmd_id , method = method , params = json .dumps (params ))
221
+
222
+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
223
+
224
+ try :
225
+ sock .connect ((self .get_ip , self .get_port ))
226
+ sock .send (message .encode ())
227
+ recv_data = sock .recv (1024 )
228
+ except socket .timeout :
229
+ return ""
230
+ finally :
231
+ sock .close ()
232
+
233
+ return recv_data
234
+
235
+ def _handle_response (self , response ):
236
+ response = json .loads (response .decode ('utf-8' ))
237
+
238
+ if self .debug :
239
+ print (response )
240
+
241
+ if "params" in response :
242
+ return response ["params" ]
243
+ elif "id" in response and not "error" in response :
244
+ return response ["result" ]
245
+ elif "error" in response :
246
+ raise YeeLightException (response ["error" ])
247
+ else :
248
+ raise YeeLightException ("Unknown Exception occurred." )
0 commit comments