Skip to content

Commit

Permalink
add static files example
Browse files Browse the repository at this point in the history
  • Loading branch information
FoamyGuy committed Jun 26, 2023
1 parent 381c84c commit 7e8391d
Show file tree
Hide file tree
Showing 3 changed files with 391 additions and 0 deletions.
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

0 comments on commit 7e8391d

Please sign in to comment.