-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |