Skip to content

Commit 92d0c66

Browse files
committed
new example script for using WsgiServer
Includes an simple example of a wsgi complient application class
1 parent c429455 commit 92d0c66

File tree

2 files changed

+188
-83
lines changed

2 files changed

+188
-83
lines changed

examples/esp32spi_server.py

Lines changed: 0 additions & 83 deletions
This file was deleted.

examples/esp32spi_wsgiserver.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import board
2+
import busio
3+
import os
4+
from digitalio import DigitalInOut
5+
6+
from adafruit_esp32spi import adafruit_esp32spi
7+
import adafruit_esp32spi.adafruit_esp32spi_wifimanager as wifimanager
8+
import adafruit_esp32spi.adafruit_esp32spi_wsgiserver as server
9+
10+
# This example depends on the 'static' folder in the examples folder
11+
# being copied to the root of the circuitpython filesystem.
12+
# This is where our static assets like html, js, and css live.
13+
14+
# Get wifi details and more from a secrets.py file
15+
try:
16+
from secrets import secrets
17+
except ImportError:
18+
print("WiFi secrets are kept in secrets.py, please add them there!")
19+
raise
20+
21+
try:
22+
import json as json_module
23+
except ImportError:
24+
import ujson as json_module
25+
26+
"""Use below for Most Boards"""
27+
import neopixel
28+
status_light = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2) # Uncomment for Most Boards
29+
"""Uncomment below for ItsyBitsy M4"""
30+
# import adafruit_dotstar as dotstar
31+
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=1)
32+
33+
print("ESP32 SPI simple web server test!")
34+
35+
esp32_cs = DigitalInOut(board.D10)
36+
esp32_ready = DigitalInOut(board.D9)
37+
esp32_reset = DigitalInOut(board.D7)
38+
esp32_gpio0 = DigitalInOut(board.D12)
39+
40+
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
41+
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset, gpio0_pin=esp32_gpio0, debug=False)
42+
43+
## Connect to wifi with secrets
44+
wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light, debug=True)
45+
wifi.connect()
46+
47+
class SimpleWSGIApplication:
48+
"""
49+
An example of a simple WSGI Application that supports
50+
basic route handling and static asset file serving for common file types
51+
"""
52+
53+
INDEX = "/index.html"
54+
CHUNK_SIZE = 8912 # max number of bytes to read at once when reading files
55+
56+
def __init__(self, static_dir=None, debug=False):
57+
self._debug = debug
58+
self._listeners = {}
59+
self._start_response = None
60+
self._static = static_dir
61+
if self._static:
62+
self._static_files = ["/" + file for file in os.listdir(self._static)]
63+
64+
def __call__(self, environ, start_response):
65+
"""
66+
Called whenever the server gets a request.
67+
The environ dict has details about the request per wsgi specification.
68+
Call start_response with the response status string and headers as a list of tuples.
69+
Return a single item list with the item being your response data string.
70+
"""
71+
if self._debug:
72+
self._log_environ(environ)
73+
74+
self._start_response = start_response
75+
status = ""
76+
headers = []
77+
resp_data = []
78+
79+
key = self._get_listener_key(environ["REQUEST_METHOD"].lower(), environ["PATH_INFO"])
80+
if key in self._listeners:
81+
status, headers, resp_data = self._listeners[key](environ)
82+
if environ["REQUEST_METHOD"].lower() == "get" and self._static:
83+
path = environ["PATH_INFO"]
84+
if path in self._static_files:
85+
status, headers, resp_data = self.serve_file(path, directory=self._static)
86+
elif path == "/" and self.INDEX in self._static_files:
87+
status, headers, resp_data = self.serve_file(self.INDEX, directory=self._static)
88+
89+
self._start_response(status, headers)
90+
return resp_data
91+
92+
def on(self, method, path, request_handler):
93+
"""
94+
Register a Request Handler for a particular HTTP method and path.
95+
request_handler will be called whenever a matching HTTP request is received.
96+
97+
request_handler should accept the following args:
98+
(Dict environ)
99+
request_handler should return a tuple in the shape of:
100+
(status, header_list, data_iterable)
101+
102+
:param str method: the method of the HTTP request
103+
:param str path: the path of the HTTP request
104+
:param func request_handler: the function to call
105+
"""
106+
self._listeners[self._get_listener_key(method, path)] = request_handler
107+
108+
def serve_file(self, file_path, directory=None):
109+
status = "200 OK"
110+
headers = [("Content-Type", self._get_content_type(file_path))]
111+
112+
full_path = file_path if not directory else directory + file_path
113+
def resp_iter():
114+
with open(full_path, 'rb') as file:
115+
while True:
116+
chunk = file.read(self.CHUNK_SIZE)
117+
if chunk:
118+
yield chunk
119+
else:
120+
break
121+
122+
return (status, headers, resp_iter())
123+
124+
def _log_environ(self, environ): # pylint: disable=no-self-use
125+
print("environ map:")
126+
for name, value in environ.items():
127+
print(name, value)
128+
129+
def _get_listener_key(self, method, path): # pylint: disable=no-self-use
130+
return "{0}|{1}".format(method.lower(), path)
131+
132+
def _get_content_type(self, file): # pylint: disable=no-self-use
133+
ext = file.split('.')[-1]
134+
if ext in ("html", "htm"):
135+
return "text/html"
136+
if ext == "js":
137+
return "application/javascript"
138+
if ext == "css":
139+
return "text/css"
140+
if ext in ("jpg", "jpeg"):
141+
return "image/jpeg"
142+
if ext == "png":
143+
return "image/png"
144+
return "text/plain"
145+
146+
# Our HTTP Request handlers
147+
def led_on(environ): # pylint: disable=unused-argument
148+
print("led on!")
149+
status_light.fill(0, 0, 100)
150+
return web_app.serve_file("static/index.html")
151+
152+
def led_off(environ): # pylint: disable=unused-argument
153+
print("led off!")
154+
status_light.fill(0)
155+
return web_app.serve_file("static/index.html")
156+
157+
def led_color(environ): # pylint: disable=unused-argument
158+
json = json_module.loads(environ["wsgi.input"].getvalue())
159+
print(json)
160+
rgb_tuple = (json.get("r"), json.get("g"), json.get("b"))
161+
status_light.fill(rgb_tuple)
162+
return ("200 OK", [], [])
163+
164+
# Here we create our application, setting the static directory location
165+
# and registering the above request_handlers for specific HTTP requests
166+
# we want to listen and respond to.
167+
web_app = SimpleWSGIApplication(static_dir="/static")
168+
web_app.on("GET", "/led_on", led_on)
169+
web_app.on("GET", "/led_off", led_off)
170+
web_app.on("POST", "/ajax/ledcolor", led_color)
171+
172+
# Here we setup our server, passing in our web_app as the application
173+
server.set_interface(esp)
174+
wsgiServer = server.WSGIServer(80, application=web_app)
175+
176+
print("open this IP in your browser: ", esp.pretty_ip(esp.ip_address))
177+
178+
# Start the server
179+
wsgiServer.start()
180+
while True:
181+
# Our main loop where we have the server poll for incoming requests
182+
try:
183+
wsgiServer.update_poll()
184+
# Could do any other background tasks here, like reading sensors
185+
except (ValueError, RuntimeError) as e:
186+
print("Failed to update server, restarting ESP32\n", e)
187+
wifi.reset()
188+
continue

0 commit comments

Comments
 (0)