1
1
"""
2
2
Code to serve up a webserver that controls LEDs and toggles modes. Relies on some environment variables set in the settings.toml.
3
3
"""
4
+ import alarm
5
+ from analogio import AnalogIn
6
+ import digitalio
4
7
import os
5
8
import time
6
9
import board
16
19
SSID_NAME = os .getenv ("SSID_NAME" )
17
20
SSID_PASS = os .getenv ("WLAN_PASS" )
18
21
NUM_PIXELS = 10
19
- LED_MODE = "rainbow"
22
+ MODE_FORM_KEY = "mode"
23
+ MODE_RAINBOW = "rainbow_mode"
24
+ MODE_SLEEP = "sleep"
25
+ MODE_COLOR_PULSE = "color_pulse_mode"
26
+ RESET_VARS_FORM_KEY = "reset_variables"
27
+ SETTINGS_URL = "/settings"
28
+ RAINBOW_SETTINGS_URL = "/settings/rainbow"
29
+ COLOR_PULSE_SETTINGS_URL = "/settings/color_pulse"
30
+
31
+ TARGET_LOOP_RATE = 60 # hz
32
+ TARGET_LOOP_SECONDS = 1 / TARGET_LOOP_RATE
33
+ LAST_LOOP_TIME = time .time ()
34
+ LED_MODE = MODE_RAINBOW
20
35
BRIGHTNESS = 0.5
21
- BRIGHTNESS_LOWER_BOUND = 0.2
36
+ BRIGHTNESS_LOWER_BOUND = 0.0
22
37
BRIGHTNESS_UPPER_BOUND = 1.0
23
- BRIGHTNESS_INCREMENT = 0.1
38
+ BRIGHTNESS_FORM_KEY = "brightness"
39
+
24
40
RAINBOW_SPEED = 0
25
41
RAINBOW_MIN_SPEED = 0
26
42
RAINBOW_MAX_SPEED = 0.1
27
- RAINBOW_SPEED_INCREMENT = 0.002
43
+ RAINBOW_SPEED_FORM_KEY = "rainbow_speed"
28
44
COLOR_R = 255
45
+ COLOR_R_FORM_KEY = "r"
29
46
COLOR_G = 0
47
+ COLOR_G_FORM_KEY = "g"
30
48
COLOR_B = 255
49
+ COLOR_B_FORM_KEY = "b"
31
50
COLOR_UPPER_BOUND = 255
32
51
COLOR_LOWER_BOUND = 0
33
- COLOR_INCREMENT = 10
34
52
# font for HTML
35
53
FONT_FAMILY = "monospace"
36
54
37
- # set static IP address
38
- ipv4 = ipaddress .IPv4Address ("192.168.1.42" )
39
- netmask = ipaddress .IPv4Address ("255.255.255.0" )
40
- gateway = ipaddress .IPv4Address ("192.168.1.1" )
41
- wifi .radio .set_ipv4_address (ipv4 = ipv4 ,netmask = netmask ,gateway = gateway )
55
+ BATTERY_LEVEL = 3.7
56
+
42
57
wifi .radio .start_ap (ssid = SSID_NAME , password = SSID_PASS )
43
58
print ("started network" )
44
59
pool = socketpool .SocketPool (wifi .radio )
47
62
# Update this to match the number of NeoPixel LEDs connected to your board.
48
63
ORDER = neopixel .RGB
49
64
PIXEL_PIN = board .D5
65
+ BUTTON_PIN = board .BUTTON
66
+ INBUILT_NEOPIXEL_PIN = board .NEOPIXEL_I2C_POWER
67
+ INBUILT_NEOPIXEL = digitalio .DigitalInOut (INBUILT_NEOPIXEL_PIN )
68
+ INBUILT_NEOPIXEL .direction = digitalio .Direction .OUTPUT
69
+ INBUILT_NEOPIXEL .value = False
50
70
51
71
pixels = neopixel .NeoPixel (
52
72
PIXEL_PIN , NUM_PIXELS , brightness = BRIGHTNESS , auto_write = False , pixel_order = ORDER )
53
73
74
+ BATTERY_PIN = board .VOLTAGE_MONITOR
75
+ BATTERY_IN = AnalogIn (BATTERY_PIN )
76
+ PIN_ALARM = alarm .pin .PinAlarm (pin = BUTTON_PIN , value = False )
77
+
78
+ def update_battery ():
79
+ global BATTERY_IN , BATTERY_LEVEL
80
+ measured_voltage = BATTERY_IN .value
81
+ # docs claim below, but it seems this isn't accurate
82
+ # measured_voltage *= 2
83
+ measured_voltage /= 1000
84
+ BATTERY_LEVEL = measured_voltage
85
+
86
+
54
87
def web_page ():
55
88
html = f"""<!DOCTYPE html>
56
89
<html lang="en">
@@ -75,78 +108,199 @@ def web_page():
75
108
<h1>Crystal Ball Web Server</h1>
76
109
<div class="paragraph">
77
110
<p class="dotted">This is a webserver to control the lights in the crystal ball.</p>
78
- </div>
111
+ </div>
112
+ <h1>Current Settings</h1>
113
+ <div class="paragraph">
114
+ <p class="dotted">Mode: <span style="color: deeppink;">{ LED_MODE } </span></p>
115
+ </div>
116
+ <div class="paragraph">
117
+ <p class="dotted">Battery: <span style="color: deeppink;">{ BATTERY_LEVEL :.2f} </span></p>
118
+ </div>
119
+ <div class="paragraph">
120
+ <p class="dotted">Brightness: <span style="color: deeppink;">{ BRIGHTNESS :.2f} </span></p>
121
+ </div>
122
+ <div class="paragraph">
123
+ <p class="dotted">Rainbow Speed: <span style="color: deeppink;">{ RAINBOW_SPEED :.3f} </span></p>
124
+ </div>
125
+ <div class="paragraph">
126
+ <p class="dotted">Red value: <span style="color: deeppink;">{ COLOR_R } </span></p>
127
+ </div>
128
+ <div class="paragraph">
129
+ <p class="dotted">Green value: <span style="color: deeppink;">{ COLOR_G } </span></p>
130
+ </div>
79
131
<div class="paragraph">
80
- <p class="dotted">The current mode is <span style="color: deeppink;">{ LED_MODE } </span></p>
132
+ <p class="dotted">Blue value: <span style="color: deeppink;">{ COLOR_B } </span></p>
81
133
</div>
82
- <h1>Lights Control</h1>
83
- <form accept-charset="utf-8" method="POST">
84
- <button class="button" name="Set Rainbow Mode" value="rainbow_mode" type="submit">Set Rainbow</button></a></p></form>
85
- <p><form accept-charset="utf-8" method="POST">
86
- <button class="button" name="Set Color Pulse Mode" value="color_pulse_mode" type="submit">Set Color Pulse</button></a></p></form>
87
- <p><form accept-charset="utf-8" method="POST">
88
- <button class="button" name="Increment Brightness" value="increment_brightness" type="submit">Increment Brightness ({ BRIGHTNESS_LOWER_BOUND } to { BRIGHTNESS_UPPER_BOUND } , increment: { BRIGHTNESS_INCREMENT } , currently: { BRIGHTNESS } )</button></a></p></form>
89
- <p><form accept-charset="utf-8" method="POST">
90
- <button class="button" name="Increment Rainbow Speed" value="increment_rainbow_speed" type="submit">Increment Rainbow Speed ({ RAINBOW_MIN_SPEED } to { RAINBOW_MAX_SPEED } , increment: { RAINBOW_SPEED_INCREMENT } , currently: { RAINBOW_SPEED } )</button></a></p></form>
91
- <p><form accept-charset="utf-8" method="POST">
92
- <button class="button" name="Color Pulse: Red" value="red" type="submit">Increment Color Pulse Red ({ COLOR_LOWER_BOUND } to { COLOR_UPPER_BOUND } , increment: { COLOR_INCREMENT } , currently: { COLOR_R } )</button></a></p></form>
93
- <p><form accept-charset="utf-8" method="POST">
94
- <button class="button" name="Color Pulse: Green" value="green" type="submit">Increment Color Pulse Green ({ COLOR_LOWER_BOUND } to { COLOR_UPPER_BOUND } , increment: { COLOR_INCREMENT } , currently: { COLOR_G } )</button></a></p></form>
95
- <p><form accept-charset="utf-8" method="POST">
96
- <button class="button" name="Color Pulse: Blue" value="blue" type="submit">Increment Color Pulse Blue ({ COLOR_LOWER_BOUND } to { COLOR_UPPER_BOUND } , increment: { COLOR_INCREMENT } , currently: { COLOR_B } )</button></a></p></form>
97
- <p><form accept-charset="utf-8" method="POST">
98
- <button class="button" name="Reset Variables" value="reset_variables" type="submit">Reset All Variables to Defaults</button></a></p></form>
134
+
135
+ <h1>Controls</h1>
136
+ <h2>General Controls</h2>
137
+ <p>
138
+ <form action="{ SETTINGS_URL } " method="post" enctype="text/plain">
139
+ <button class="button" name="{ MODE_FORM_KEY } " value="{ MODE_RAINBOW } " type="submit">Set Rainbow Mode</button>
140
+ </form>
141
+ </p>
142
+ <p>
143
+ <form action="{ SETTINGS_URL } " method="post" enctype="text/plain">
144
+ <button class="button" name="{ MODE_FORM_KEY } " value="{ MODE_COLOR_PULSE } " type="submit">Set Color Pulse Mode</button>
145
+ </form>
146
+ </p>
147
+ <p>
148
+ <form action="{ SETTINGS_URL } " method="post" enctype="text/plain">
149
+ <button class="button" name="{ MODE_FORM_KEY } " value="{ MODE_SLEEP } " type="submit">Set Deep Sleep</button>
150
+ </form>
151
+ </p>
152
+ <p>
153
+ <form action="{ SETTINGS_URL } " method="post" enctype="text/plain">
154
+ <input type="text" name="{ BRIGHTNESS_FORM_KEY } " placeholder="Set brightness, { BRIGHTNESS_LOWER_BOUND } to { BRIGHTNESS_UPPER_BOUND } .">
155
+ <input type="submit" value="Submit">
156
+ </form>
157
+ </p>
158
+ <p>
159
+ <form action="{ SETTINGS_URL } " method="post" enctype="text/plain">
160
+ <button class="button" name="{ RESET_VARS_FORM_KEY } " value="yes" type="submit">Reset Variables</button>
161
+ </form>
162
+ </p>
163
+ <h2>Rainbow Controls</h2>
164
+ <p>
165
+ <form action="{ RAINBOW_SETTINGS_URL } " method="post" enctype="text/plain">
166
+ <input type="text" name="{ RAINBOW_SPEED_FORM_KEY } " placeholder="Set rainbow speed, { RAINBOW_MIN_SPEED } to { RAINBOW_MAX_SPEED } .">
167
+ <input type="submit" value="Submit">
168
+ </form>
169
+ </p>
170
+ <h2>Color Pulse Controls</h3>
171
+ <p>
172
+ <form action="{ COLOR_PULSE_SETTINGS_URL } " method="post" enctype="text/plain">
173
+ <input type="text" name="{ COLOR_R_FORM_KEY } " placeholder="Set red value, { COLOR_LOWER_BOUND } to { COLOR_UPPER_BOUND } .">
174
+ <input type="submit" value="Submit">
175
+ </form>
176
+ </p>
177
+ <p>
178
+ <form action="{ COLOR_PULSE_SETTINGS_URL } " method="post" enctype="text/plain">
179
+ <input type="text" name="{ COLOR_G_FORM_KEY } " placeholder="Set green value, { COLOR_LOWER_BOUND } to { COLOR_UPPER_BOUND } .">
180
+ <input type="submit" value="Submit">
181
+ </form>
182
+ </p>
183
+ <p>
184
+ <form action="{ COLOR_PULSE_SETTINGS_URL } " method="post" enctype="text/plain">
185
+ <input type="text" name="{ COLOR_B_FORM_KEY } " placeholder="Set blue value, { COLOR_LOWER_BOUND } to { COLOR_UPPER_BOUND } .">
186
+ <input type="submit" value="Submit">
187
+ </form>
188
+ </p>
99
189
</body></html>
100
190
"""
101
191
return html
102
192
103
193
104
- # route default static IP
105
- @server .route ("/" )
106
- def base (request : Request ): # pylint: disable=unused-argument
107
- # serve the HTML f string
108
- # with content type text/html
109
- return Response (request , f"{ web_page ()} " , content_type = 'text/html' )
110
-
111
194
def bound_number (cur , min_bound , max_bound , increment ):
112
195
new_val = cur + increment
113
- if new_val >= max_bound :
196
+ if new_val > max_bound :
197
+ new_val = max_bound
198
+ if new_val < min_bound :
114
199
new_val = min_bound
115
200
return new_val
116
201
117
- # if a button is pressed on the site
118
- @server .route ("/" , POST )
119
- def buttonpress (request : Request ):
120
- global LED_MODE , BRIGHTNESS , RAINBOW_SPEED , COLOR_R , COLOR_G , COLOR_B
121
- # get the raw text
122
- raw_text = request .raw_request .decode ("utf8" )
123
- print (raw_text )
124
- if "rainbow_mode" in raw_text :
125
- LED_MODE = "rainbow"
126
- elif "color_pulse_mode" in raw_text :
127
- LED_MODE = "color_pulse"
128
- elif "increment_brightness" in raw_text :
129
- BRIGHTNESS = bound_number (BRIGHTNESS , BRIGHTNESS_LOWER_BOUND , BRIGHTNESS_UPPER_BOUND , BRIGHTNESS_INCREMENT )
202
+ @server .route (SETTINGS_URL , [POST ])
203
+ def settings (request : Request ):
204
+
205
+ brightness_req = request .form_data .get (BRIGHTNESS_FORM_KEY , None )
206
+ if not brightness_req is None :
207
+ global BRIGHTNESS
208
+ brightness = BRIGHTNESS
209
+ try :
210
+ brightness = float (brightness_req .strip ())
211
+ except ValueError :
212
+ pass
213
+ BRIGHTNESS = bound_number (brightness , BRIGHTNESS_LOWER_BOUND , BRIGHTNESS_UPPER_BOUND , 0 )
130
214
pixels .brightness = BRIGHTNESS
131
- elif "increment_rainbow_speed" in raw_text :
132
- RAINBOW_SPEED = bound_number (RAINBOW_SPEED , RAINBOW_MIN_SPEED , RAINBOW_MAX_SPEED , RAINBOW_SPEED_INCREMENT )
133
- elif "red" in raw_text :
134
- COLOR_R = bound_number (COLOR_R , COLOR_LOWER_BOUND , COLOR_UPPER_BOUND , COLOR_INCREMENT )
135
- elif "green" in raw_text :
136
- COLOR_G = bound_number (COLOR_G , COLOR_LOWER_BOUND , COLOR_UPPER_BOUND , COLOR_INCREMENT )
137
- elif "blue" in raw_text :
138
- COLOR_B = bound_number (COLOR_B , COLOR_LOWER_BOUND , COLOR_UPPER_BOUND , COLOR_INCREMENT )
139
- elif "reset_variables" in raw_text :
140
- LED_MODE = "rainbow"
215
+
216
+ mode = request .form_data .get (MODE_FORM_KEY , None )
217
+ if not mode is None :
218
+ mode = mode .strip ()
219
+ global LED_MODE
220
+ if mode == MODE_RAINBOW :
221
+ LED_MODE = MODE_RAINBOW
222
+ elif mode == MODE_COLOR_PULSE :
223
+ LED_MODE = MODE_COLOR_PULSE
224
+ elif mode == MODE_SLEEP :
225
+ global server
226
+ server .stop ()
227
+ wifi .radio .stop_ap ()
228
+ pixels .fill ((0 , 0 , 0 ))
229
+ pixels .show ()
230
+ alarm .exit_and_deep_sleep_until_alarms (PIN_ALARM )
231
+ else :
232
+ pass
233
+
234
+ reset_vars = request .form_data .get (RESET_VARS_FORM_KEY , None )
235
+ if not reset_vars is None :
236
+ global LED_MODE , BRIGHTNESS , RAINBOW_SPEED , COLOR_R , COLOR_B , COLOR_G
237
+ LED_MODE = MODE_RAINBOW
141
238
BRIGHTNESS = 0.5
142
239
RAINBOW_SPEED = 0
143
240
COLOR_R = 255
144
241
COLOR_G = 0
145
242
COLOR_B = 255
146
- else :
147
- pass
148
243
return Response (request , f"{ web_page ()} " , content_type = 'text/html' )
149
244
245
+
246
+ @server .route (RAINBOW_SETTINGS_URL , [POST ])
247
+ def settings_rainbow (request : Request ):
248
+
249
+ rainbow_speed = request .form_data .get (RAINBOW_SPEED_FORM_KEY , None )
250
+ if not rainbow_speed is None :
251
+ global RAINBOW_SPEED
252
+ speed = RAINBOW_SPEED
253
+ try :
254
+ speed = float (rainbow_speed .strip ())
255
+ except ValueError :
256
+ pass
257
+ RAINBOW_SPEED = bound_number (speed , RAINBOW_MIN_SPEED , RAINBOW_MAX_SPEED , 0 )
258
+ return Response (request , f"{ web_page ()} " , content_type = 'text/html' )
259
+
260
+
261
+ @server .route (COLOR_PULSE_SETTINGS_URL , [POST ])
262
+ def settings_color_pulse (request : Request ):
263
+
264
+ r_val = request .form_data .get (COLOR_R_FORM_KEY , None )
265
+ if not r_val is None :
266
+ global COLOR_R
267
+ r = COLOR_R
268
+ try :
269
+ r = int (r_val .strip ())
270
+ except ValueError :
271
+ pass
272
+ COLOR_R = bound_number (r , COLOR_LOWER_BOUND , COLOR_UPPER_BOUND , 0 )
273
+
274
+ g_val = request .form_data .get (COLOR_G_FORM_KEY , None )
275
+ if not g_val is None :
276
+ global COLOR_G
277
+ g = COLOR_G
278
+ try :
279
+ g = int (g_val .strip ())
280
+ except ValueError :
281
+ pass
282
+ COLOR_G = bound_number (g , COLOR_LOWER_BOUND , COLOR_UPPER_BOUND , 0 )
283
+
284
+ b_val = request .form_data .get (COLOR_B_FORM_KEY , None )
285
+ if not b_val is None :
286
+ global COLOR_B
287
+ b = COLOR_B
288
+ try :
289
+ b = int (b_val .strip ())
290
+ except ValueError :
291
+ pass
292
+ COLOR_B = bound_number (b , COLOR_LOWER_BOUND , COLOR_UPPER_BOUND , 0 )
293
+
294
+ return Response (request , f"{ web_page ()} " , content_type = 'text/html' )
295
+
296
+ # route default static IP
297
+ @server .route ("/" )
298
+ def base (request : Request ): # pylint: disable=unused-argument
299
+ # serve the HTML f string
300
+ # with content type text/html
301
+ return Response (request , f"{ web_page ()} " , content_type = 'text/html' )
302
+
303
+
150
304
def rainbow (speed ):
151
305
for j in range (255 ):
152
306
for i in range (NUM_PIXELS ):
@@ -162,8 +316,6 @@ def color_pulse():
162
316
163
317
164
318
def main ():
165
-
166
-
167
319
print ("starting server.." )
168
320
# startup the server
169
321
try :
@@ -174,13 +326,19 @@ def main():
174
326
time .sleep (5 )
175
327
print ("restarting.." )
176
328
microcontroller .reset ()
177
-
329
+ global LAST_LOOP_TIME , TARGET_LOOP_SECONDS
178
330
while True :
179
- if LED_MODE == "rainbow" :
331
+ seconds_before_loop = (time .time () - LAST_LOOP_TIME ) - TARGET_LOOP_SECONDS
332
+ if seconds_before_loop < 0 :
333
+ time .sleep (abs (seconds_before_loop ))
334
+ LAST_LOOP_TIME = time .time ()
335
+
336
+ if LED_MODE == MODE_RAINBOW :
180
337
rainbow (RAINBOW_SPEED )
181
- elif LED_MODE == "color_pulse" :
338
+ elif LED_MODE == MODE_COLOR_PULSE :
182
339
color_pulse ()
183
340
# poll the server for incoming/outgoing requests
184
341
server .poll ()
342
+ update_battery ()
185
343
186
344
main ()
0 commit comments