Skip to content

Move esp32spi_wsgi server code to here #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
add static files example
  • Loading branch information
FoamyGuy committed Jun 26, 2023
commit 7e8391de08584bbcab6058c49fad5292c3e008a5
16 changes: 16 additions & 0 deletions examples/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries

SPDX-License-Identifier: MIT
-->

<!DOCTYPE html>
<html>
<head>
<script async src="led_color_picker_example.js"></script>
</head>
<body>
<h1>LED color picker demo!</h1>
<canvas id="colorPicker" height="300px" width="300px"></canvas>
</body>
</html>
129 changes: 129 additions & 0 deletions examples/static/led_color_picker_example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
//
// SPDX-License-Identifier: MIT

let canvas = document.getElementById('colorPicker');
let ctx = canvas.getContext("2d");
ctx.width = 300;
ctx.height = 300;

function drawColorPicker() {
/**
* Color picker inspired by:
* https://medium.com/@bantic/hand-coding-a-color-wheel-with-canvas-78256c9d7d43
*/
let radius = 150;
let image = ctx.createImageData(2*radius, 2*radius);
let data = image.data;

for (let x = -radius; x < radius; x++) {
for (let y = -radius; y < radius; y++) {

let [r, phi] = xy2polar(x, y);

if (r > radius) {
// skip all (x,y) coordinates that are outside of the circle
continue;
}

let deg = rad2deg(phi);

// Figure out the starting index of this pixel in the image data array.
let rowLength = 2*radius;
let adjustedX = x + radius; // convert x from [-50, 50] to [0, 100] (the coordinates of the image data array)
let adjustedY = y + radius; // convert y from [-50, 50] to [0, 100] (the coordinates of the image data array)
let pixelWidth = 4; // each pixel requires 4 slots in the data array
let index = (adjustedX + (adjustedY * rowLength)) * pixelWidth;

let hue = deg;
let saturation = r / radius;
let value = 1.0;

let [red, green, blue] = hsv2rgb(hue, saturation, value);
let alpha = 255;

data[index] = red;
data[index+1] = green;
data[index+2] = blue;
data[index+3] = alpha;
}
}

ctx.putImageData(image, 0, 0);
}

function xy2polar(x, y) {
let r = Math.sqrt(x*x + y*y);
let phi = Math.atan2(y, x);
return [r, phi];
}

// rad in [-π, π] range
// return degree in [0, 360] range
function rad2deg(rad) {
return ((rad + Math.PI) / (2 * Math.PI)) * 360;
}

// hue in range [0, 360]
// saturation, value in range [0,1]
// return [r,g,b] each in range [0,255]
// See: https://en.wikipedia.org/wiki/HSL_and_HSV#From_HSV
function hsv2rgb(hue, saturation, value) {
let chroma = value * saturation;
let hue1 = hue / 60;
let x = chroma * (1- Math.abs((hue1 % 2) - 1));
let r1, g1, b1;
if (hue1 >= 0 && hue1 <= 1) {
([r1, g1, b1] = [chroma, x, 0]);
} else if (hue1 >= 1 && hue1 <= 2) {
([r1, g1, b1] = [x, chroma, 0]);
} else if (hue1 >= 2 && hue1 <= 3) {
([r1, g1, b1] = [0, chroma, x]);
} else if (hue1 >= 3 && hue1 <= 4) {
([r1, g1, b1] = [0, x, chroma]);
} else if (hue1 >= 4 && hue1 <= 5) {
([r1, g1, b1] = [x, 0, chroma]);
} else if (hue1 >= 5 && hue1 <= 6) {
([r1, g1, b1] = [chroma, 0, x]);
}

let m = value - chroma;
let [r,g,b] = [r1+m, g1+m, b1+m];

// Change r,g,b values from [0,1] to [0,255]
return [255*r,255*g,255*b];
}

function onColorPick(event) {
coords = getCursorPosition(canvas, event)
imageData = ctx.getImageData(coords[0],coords[1],1,1)
rgbObject = {
r: imageData.data[0],
g: imageData.data[1],
b: imageData.data[2]
}
console.log(`r: ${rgbObject.r} g: ${rgbObject.g} b: ${rgbObject.b}`);
data = JSON.stringify(rgbObject);
window.fetch("/ajax/ledcolor", {
method: "POST",
body: data,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
}).then(response => {
console.log("sucess!: " + response)
}, error => {
console.log("error!: " + error)
})
}

function getCursorPosition(canvas, event) {
const rect = canvas.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
console.log("x: " + x + " y: " + y)
return [x,y]
}

drawColorPicker();
canvas.addEventListener('mousedown', onColorPick);
246 changes: 246 additions & 0 deletions examples/wsgi_static_files_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
# SPDX-FileCopyrightText: 2019 ladyada for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import os
import board
import busio
from digitalio import DigitalInOut
import neopixel

from adafruit_esp32spi import adafruit_esp32spi
import adafruit_esp32spi.adafruit_esp32spi_wifimanager as wifimanager
import adafruit_wsgi.esp32spi_wsgiserver as server

# This example depends on the 'static' folder in the examples folder
# being copied to the root of the circuitpython filesystem.
# This is where our static assets like html, js, and css live.

# Get wifi details and more from a secrets.py file
try:
from secrets import secrets
except ImportError:
print("WiFi secrets are kept in secrets.py, please add them there!")
raise

try:
import json as json_module
except ImportError:
import ujson as json_module

print("ESP32 SPI simple web server test!")

# If you are using a board with pre-defined ESP32 Pins:
esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)

# If you have an externally connected ESP32:
# esp32_cs = DigitalInOut(board.D9)
# esp32_ready = DigitalInOut(board.D10)
# esp32_reset = DigitalInOut(board.D5)

spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
esp = adafruit_esp32spi.ESP_SPIcontrol(
spi, esp32_cs, esp32_ready, esp32_reset
) # pylint: disable=line-too-long

print("MAC addr:", [hex(i) for i in esp.MAC_address])
print("MAC addr actual:", [hex(i) for i in esp.MAC_address_actual])

# Use below for Most Boards
status_light = neopixel.NeoPixel(
board.NEOPIXEL, 1, brightness=0.2
) # Uncomment for Most Boards
# Uncomment below for ItsyBitsy M4
# import adafruit_dotstar as dotstar
# status_light = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=1)

## If you want to connect to wifi with secrets:
wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
wifi.connect()

## If you want to create a WIFI hotspot to connect to with secrets:
# secrets = {"ssid": "My ESP32 AP!", "password": "supersecret"}
# wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
# wifi.create_ap()

## To you want to create an un-protected WIFI hotspot to connect to with secrets:"
# secrets = {"ssid": "My ESP32 AP!"}
# wifi = wifimanager.ESPSPI_WiFiManager(esp, secrets, status_light)
# wifi.create_ap()


class SimpleWSGIApplication:
"""
An example of a simple WSGI Application that supports
basic route handling and static asset file serving for common file types
"""

INDEX = "/index.html"
CHUNK_SIZE = 8912 # max number of bytes to read at once when reading files

def __init__(self, static_dir=None, debug=False):
self._debug = debug
self._listeners = {}
self._start_response = None
self._static = static_dir
if self._static:
self._static_files = ["/" + file for file in os.listdir(self._static)]

def __call__(self, environ, start_response):
"""
Called whenever the server gets a request.
The environ dict has details about the request per wsgi specification.
Call start_response with the response status string and headers as a list of tuples.
Return a single item list with the item being your response data string.
"""
if self._debug:
self._log_environ(environ)

self._start_response = start_response
status = ""
headers = []
resp_data = []

key = self._get_listener_key(
environ["REQUEST_METHOD"].lower(), environ["PATH_INFO"]
)
if key in self._listeners:
status, headers, resp_data = self._listeners[key](environ)
if environ["REQUEST_METHOD"].lower() == "get" and self._static:
path = environ["PATH_INFO"]
if path in self._static_files:
status, headers, resp_data = self.serve_file(
path, directory=self._static
)
elif path == "/" and self.INDEX in self._static_files:
status, headers, resp_data = self.serve_file(
self.INDEX, directory=self._static
)

self._start_response(status, headers)
return resp_data

def on(self, method, path, request_handler):
"""
Register a Request Handler for a particular HTTP method and path.
request_handler will be called whenever a matching HTTP request is received.

request_handler should accept the following args:
(Dict environ)
request_handler should return a tuple in the shape of:
(status, header_list, data_iterable)

:param str method: the method of the HTTP request
:param str path: the path of the HTTP request
:param func request_handler: the function to call
"""
self._listeners[self._get_listener_key(method, path)] = request_handler

def serve_file(self, file_path, directory=None):
status = "200 OK"
headers = [("Content-Type", self._get_content_type(file_path))]

full_path = file_path if not directory else directory + file_path

def resp_iter():
with open(full_path, "rb") as file:
while True:
chunk = file.read(self.CHUNK_SIZE)
if chunk:
yield chunk
else:
break

return (status, headers, resp_iter())

def _log_environ(self, environ): # pylint: disable=no-self-use
print("environ map:")
for name, value in environ.items():
print(name, value)

def _get_listener_key(self, method, path): # pylint: disable=no-self-use
return "{0}|{1}".format(method.lower(), path)

def _get_content_type(self, file): # pylint: disable=no-self-use
ext = file.split(".")[-1]
if ext in ("html", "htm"):
return "text/html"
if ext == "js":
return "application/javascript"
if ext == "css":
return "text/css"
if ext in ("jpg", "jpeg"):
return "image/jpeg"
if ext == "png":
return "image/png"
return "text/plain"


# Our HTTP Request handlers
def led_on(environ): # pylint: disable=unused-argument
print("led on!")
status_light.fill((0, 0, 100))
return web_app.serve_file("static/index.html")


def led_off(environ): # pylint: disable=unused-argument
print("led off!")
status_light.fill(0)
return web_app.serve_file("static/index.html")


def led_color(environ): # pylint: disable=unused-argument
json = json_module.loads(environ["wsgi.input"].getvalue())
print(json)
rgb_tuple = (json.get("r"), json.get("g"), json.get("b"))
status_light.fill(rgb_tuple)
return ("200 OK", [], [])


# Here we create our application, setting the static directory location
# and registering the above request_handlers for specific HTTP requests
# we want to listen and respond to.
static = "/static"
try:
static_files = os.listdir(static)
if "index.html" not in static_files:
raise RuntimeError(
"""
This example depends on an index.html, but it isn't present.
Please add it to the {0} directory""".format(
static
)
)
except OSError as e:
raise RuntimeError(
"""
This example depends on a static asset directory.
Please create one named {0} in the root of the device filesystem.""".format(
static
)
) from e

web_app = SimpleWSGIApplication(static_dir=static)
web_app.on("GET", "/led_on", led_on)
web_app.on("GET", "/led_off", led_off)
web_app.on("POST", "/ajax/ledcolor", led_color)

# Here we setup our server, passing in our web_app as the application
server.set_interface(esp)
wsgiServer = server.WSGIServer(80, application=web_app)

print("open this IP in your browser: ", esp.pretty_ip(esp.ip_address))

# Start the server
wsgiServer.start()
while True:
# Our main loop where we have the server poll for incoming requests
try:
wsgiServer.update_poll()
# Could do any other background tasks here, like reading sensors
except OSError as e:
print("Failed to update server, restarting ESP32\n", e)
wifi.reset()
continue