Skip to content

Commit 8a66458

Browse files
committed
Steps towards a full UI
* Add /index.html, /index.js, /metadata.json endpoints * metadata.json gives each property and its range of values * add property2 to set properties on pycam.camera (yes it stinks) now I can start creating the amateur-hour web UI on top of this
1 parent 3bcc927 commit 8a66458

File tree

3 files changed

+146
-45
lines changed

3 files changed

+146
-45
lines changed

examples/ipcam2/code.py

Lines changed: 80 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import espcamera
1+
import json
22
import os
3+
import struct
34
import sys
45
import time
5-
import struct
6-
import board
6+
77
import adafruit_pycamera
8+
import bitmaptools
9+
import board
810
import displayio
11+
import espcamera
912
import gifio
10-
import ulab.numpy as np
11-
import bitmaptools
1213
import socketpool
14+
import ulab.numpy as np
1315
import wifi
14-
from adafruit_httpserver import Server, Request, Response, GET, POST
16+
from adafruit_httpserver import (BAD_REQUEST_400, GET, NOT_FOUND_404, POST, FileResponse,
17+
JSONResponse, Request, Response, Server)
1518

1619
pycam = adafruit_pycamera.PyCamera()
1720
pycam.autofocus_init()
@@ -27,52 +30,77 @@
2730
print(wifi.radio.ipv4_address)
2831

2932
pool = socketpool.SocketPool(wifi.radio)
30-
server = Server(pool, debug=True)
33+
server = Server(pool, debug=True, root_path='/htdocs')
34+
35+
@server.route("/metadata.json", [GET])
36+
def property(request: Request) -> Response:
37+
return FileResponse(request, "/metadata.json")
38+
39+
@server.route("/", [GET])
40+
def property(request: Request) -> Response:
41+
return FileResponse(request, "/index.html")
42+
43+
@server.route("/index.js", [GET])
44+
def property(request: Request) -> Response:
45+
return FileResponse(request, "/index.js")
46+
3147

3248
@server.route("/lcd", [GET, POST])
3349
def property(request: Request) -> Response:
3450
pycam.blit(pycam.continuous_capture())
3551
return Response(request, "")
3652

53+
3754
@server.route("/jpeg", [GET, POST])
3855
def property(request: Request) -> Response:
3956
pycam.camera.reconfigure(
4057
pixel_format=espcamera.PixelFormat.JPEG,
41-
frame_size=pycam.resolution_to_frame_size[pycam._resolution]
58+
frame_size=pycam.resolution_to_frame_size[pycam._resolution],
4259
)
4360
try:
4461
jpeg = pycam.camera.take(1)
4562
if jpeg is not None:
4663
return Response(request, bytes(jpeg), content_type="image/jpeg")
4764
else:
48-
return Response(request, "", content_type="text/plain", status=INTERNAL_SERVER_ERROR_500)
65+
return Response(
66+
request, "", content_type="text/plain", status=INTERNAL_SERVER_ERROR_500
67+
)
4968
finally:
5069
pycam.live_preview_mode()
5170

71+
@server.route("/focus", [GET])
72+
def focus(request: Request) -> Response:
73+
return JSONResponse(request, pycam.autofocus())
74+
5275
@server.route("/property", [GET, POST])
5376
def property(request: Request) -> Response:
54-
enctype = request.query_params.get("enctype", "text/plain")
77+
return property_common(pycam, request)
78+
79+
80+
@server.route("/property2", [GET, POST])
81+
def property2(request: Request) -> Response:
82+
return property_common(pycam.camera, request)
83+
5584

56-
key = request.form_data.get("k")
57-
value = request.form_data.get("v", None)
58-
85+
def property_common(obj, request):
5986
try:
60-
current_value = getattr(pycam, key, None)
87+
params = request.query_params or request.form_data
88+
key = params["k"]
89+
value = params.get("v", None)
90+
91+
if value is None:
92+
try:
93+
current_value = getattr(obj, key, None)
94+
return JSONResponse(request, current_value)
95+
except Exception as e:
96+
return Response(request, {'error': str(e)}, status=BAD_REQUEST_400)
97+
else:
98+
new_value = json.loads(value)
99+
setattr(obj, key, new_value)
100+
return JSONResponse(request, {'status': 'OK'})
61101
except Exception as e:
62-
return Response(request, str(e), status=NOT_FOUND_404)
102+
return JSONResponse(request, {'error': str(e)}, status=BAD_REQUEST_400)
63103

64-
if value is None:
65-
return Response(request, str(current_value))
66-
else:
67-
try:
68-
current_value_type = type(current_value)
69-
if current_value_type is bool:
70-
setattr(pycam, key, value not in ('False', '0', 'no', 'n', 'off', ''))
71-
else:
72-
setattr(pycam, key, current_value_type(value))
73-
return Response(request, "")
74-
except Exception as e:
75-
return Response(request, str(e), status=BAD_REQUEST_400)
76104

77105
server.serve_forever(str(wifi.radio.ipv4_address), port)
78106

@@ -82,18 +110,21 @@ def property(request: Request) -> Response:
82110
last_frame = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
83111
onionskin = displayio.Bitmap(pycam.camera.width, pycam.camera.height, 65535)
84112
while True:
85-
if (pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0):
113+
if pycam.mode_text == "STOP" and pycam.stop_motion_frame != 0:
86114
# alpha blend
87115
new_frame = pycam.continuous_capture()
88-
bitmaptools.alphablend(onionskin, last_frame, new_frame,
89-
displayio.Colorspace.RGB565_SWAPPED)
116+
bitmaptools.alphablend(
117+
onionskin, last_frame, new_frame, displayio.Colorspace.RGB565_SWAPPED
118+
)
90119
pycam.blit(onionskin)
91120
else:
92121
pycam.blit(pycam.continuous_capture())
93-
#print("\t\t", capture_time, blit_time)
122+
# print("\t\t", capture_time, blit_time)
94123

95124
pycam.keys_debounce()
96-
print(f"{pycam.shutter.released=} {pycam.shutter.long_press=} {pycam.shutter.short_count=}")
125+
print(
126+
f"{pycam.shutter.released=} {pycam.shutter.long_press=} {pycam.shutter.short_count=}"
127+
)
97128
# test shutter button
98129
if pycam.shutter.long_press:
99130
print("FOCUS")
@@ -118,26 +149,31 @@ def property(request: Request) -> Response:
118149

119150
if pycam.mode_text == "GIF":
120151
try:
121-
f = pycam.open_next_image("gif")
152+
f = pycam.open_next_image("gif")
122153
except RuntimeError as e:
123-
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
124-
time.sleep(0.5)
125-
continue
154+
pycam.display_message("Error\nNo SD Card", color=0xFF0000)
155+
time.sleep(0.5)
156+
continue
126157
i = 0
127158
ft = []
128159
pycam._mode_label.text = "RECORDING"
129160

130161
pycam.display.refresh()
131-
with gifio.GifWriter(f, pycam.camera.width, pycam.camera.height,
132-
displayio.Colorspace.RGB565_SWAPPED, dither=True) as g:
162+
with gifio.GifWriter(
163+
f,
164+
pycam.camera.width,
165+
pycam.camera.height,
166+
displayio.Colorspace.RGB565_SWAPPED,
167+
dither=True,
168+
) as g:
133169
t00 = t0 = time.monotonic()
134170
while (i < 15) or (pycam.shutter_button.value == False):
135171
i += 1
136172
_gifframe = pycam.continuous_capture()
137-
g.add_frame(_gifframe, .12)
173+
g.add_frame(_gifframe, 0.12)
138174
pycam.blit(_gifframe)
139175
t1 = time.monotonic()
140-
ft.append(1/(t1-t0))
176+
ft.append(1 / (t1 - t0))
141177
print(end=".")
142178
t0 = t1
143179
pycam._mode_label.text = "GIF"
@@ -195,18 +231,17 @@ def property(request: Request) -> Response:
195231
print("LF")
196232
curr_setting = (curr_setting + 1) % len(settings)
197233
print(settings[curr_setting])
198-
#new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)
199-
#pycam.set_resolution(pycam.resolutions[new_res])
234+
# new_res = min(len(pycam.resolutions)-1, pycam.get_resolution()+1)
235+
# pycam.set_resolution(pycam.resolutions[new_res])
200236
pycam.select_setting(settings[curr_setting])
201237
if pycam.right.fell:
202238
print("RT")
203239
curr_setting = (curr_setting - 1 + len(settings)) % len(settings)
204240
print(settings[curr_setting])
205241
pycam.select_setting(settings[curr_setting])
206-
#new_res = max(1, pycam.get_resolution()-1)
207-
#pycam.set_resolution(pycam.resolutions[new_res])
242+
# new_res = max(1, pycam.get_resolution()-1)
243+
# pycam.set_resolution(pycam.resolutions[new_res])
208244
if pycam.select.fell:
209245
print("SEL")
210246
if pycam.ok.fell:
211247
print("OK")
212-

examples/ipcam2/htdocs/metadata.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"property": {"effect": ["Normal", "Invert", "B&W", "Reddish", "Greenish", "Bluish", "Sepia", "Solarize"], "resolution": ["240x240", "320x240", "640x480", "800x600", "1024x768", "1280x720", "1280x1024", "1600x1200", "1920x1080", "2048x1536", "2560x1440", "2560x1600", "2560x1920"], "led_level": [0, 1, 2, 3, 4], "led_color": [0, 1, 2, 3, 4, 5, 6, 7]}, "property2": {"sensor_name": null, "contrast": [-2, -1, 0, 1, 2], "brightness": [-2, -1, 0, 1, 2], "saturation": [-2, -1, 0, 1, 2], "sharpness": [-2, -1, 0, 1, 2], "ae_level": [-2, -1, 0, 1, 2], "denoise": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "gain_ceiling": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "quality": [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35], "colorbar": [false, true], "whitebal": [false, true], "gain_ctrl": [false, true], "exposure_ctrl": [false, true], "hmirror": [false, true], "vflip": [false, true], "aec2": [false, true], "awb_gain": [false, true], "dcw": [false, true], "bpc": [false, true], "wpc": [false, true], "raw_gma": [false, true], "lenc": [false, true], "aec_gain": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29], "aec_value": [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1150], "wb_mode": [0, 1, 2, 3, 4]}}

examples/ipcam2/make_web_metadata.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import json
2+
3+
def list_range(*args): return list(range(*args))
4+
5+
metadata = {
6+
"property": {
7+
"effect": (
8+
"Normal",
9+
"Invert",
10+
"B&W",
11+
"Reddish",
12+
"Greenish",
13+
"Bluish",
14+
"Sepia",
15+
"Solarize",
16+
),
17+
"resolution": (
18+
"240x240",
19+
"320x240",
20+
"640x480",
21+
"800x600",
22+
"1024x768",
23+
"1280x720",
24+
"1280x1024",
25+
"1600x1200",
26+
"1920x1080",
27+
"2048x1536",
28+
"2560x1440",
29+
"2560x1600",
30+
"2560x1920",
31+
),
32+
"led_level": list_range(5),
33+
"led_color": list_range(8),
34+
},
35+
"property2": {
36+
"sensor_name": None,
37+
"contrast": list_range(-2, 3),
38+
"brightness": list_range(-2, 3),
39+
"saturation": list_range(-2, 3),
40+
"sharpness": list_range(-2, 3),
41+
"ae_level": list_range(-2, 3),
42+
"denoise": list_range(10),
43+
"gain_ceiling": list_range(10),
44+
"quality": list_range(8, 36),
45+
"colorbar": [False, True],
46+
"whitebal": [False, True],
47+
"gain_ctrl": [False, True],
48+
"exposure_ctrl": [False, True],
49+
"hmirror": [False, True],
50+
"vflip": [False, True],
51+
"aec2": [False, True],
52+
"awb_gain": [False, True],
53+
"dcw": [False, True],
54+
"bpc": [False, True],
55+
"wpc": [False, True],
56+
"raw_gma": [False, True],
57+
"lenc": [False, True],
58+
"aec_gain": list_range(30),
59+
"aec_value": list_range(0, 1200, 50),
60+
"wb_mode": list_range(0, 5),
61+
},
62+
}
63+
64+
with open("metadata.json", "w", encoding="utf-8") as f:
65+
json.dump(metadata, f)

0 commit comments

Comments
 (0)