commit
a950a0769d
3 changed files with 389 additions and 0 deletions
@ -0,0 +1,90 @@ |
|||||||
|
const char index_html[] = R"=====( |
||||||
|
<!DOCTYPE html> |
||||||
|
<html lang='en'> |
||||||
|
<head> |
||||||
|
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' /> |
||||||
|
<meta name='viewport' content='width=device-width' /> |
||||||
|
|
||||||
|
<title>WS2812FX Ctrl</title> |
||||||
|
|
||||||
|
<script type='text/javascript' src='main.js'></script> |
||||||
|
|
||||||
|
<style> |
||||||
|
* { |
||||||
|
font-family:sans-serif; |
||||||
|
margin:0; |
||||||
|
padding:0; |
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
width:100%; |
||||||
|
max-width:675px; |
||||||
|
background-color:#202020; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
width:65%; |
||||||
|
margin:25px 0 25px 25%; |
||||||
|
color:#454545; |
||||||
|
text-align:center; |
||||||
|
} |
||||||
|
|
||||||
|
#colorbar { |
||||||
|
float:left; |
||||||
|
} |
||||||
|
|
||||||
|
#controls { |
||||||
|
width:65%; |
||||||
|
display:inline-block; |
||||||
|
padding-left:5px; |
||||||
|
} |
||||||
|
|
||||||
|
ul { |
||||||
|
text-align:center; |
||||||
|
} |
||||||
|
|
||||||
|
ul#mode li { |
||||||
|
display:block; |
||||||
|
} |
||||||
|
|
||||||
|
ul#brightness li, ul#speed li { |
||||||
|
display:inline-block; |
||||||
|
width:30%; |
||||||
|
} |
||||||
|
|
||||||
|
ul li a { |
||||||
|
display:block; |
||||||
|
margin:3px; |
||||||
|
padding:10px 5px; |
||||||
|
border:2px solid #454545; |
||||||
|
border-radius:5px; |
||||||
|
color:#454545; |
||||||
|
font-weight:bold; |
||||||
|
text-decoration:none; |
||||||
|
} |
||||||
|
|
||||||
|
ul li a.active { |
||||||
|
border:2px solid #909090; |
||||||
|
color:#909090; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<h1>WS2812FX Control</h1> |
||||||
|
<canvas id='colorbar' width='75' height='1080'></canvas> |
||||||
|
<div id='controls'> |
||||||
|
<ul id='mode'></ul> |
||||||
|
|
||||||
|
<ul id='brightness'> |
||||||
|
<li><a href='#' class='b' id='-'>☼</a></li> |
||||||
|
<li><a href='#' class='b' id='+'>☀</a></li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<ul id='speed'> |
||||||
|
<li><a href='#' class='s' id='-'>−</a></li> |
||||||
|
<li><a href='#' class='s' id='+'>+</a></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
||||||
|
)====="; |
@ -0,0 +1,119 @@ |
|||||||
|
const char main_js[] = R"=====( |
||||||
|
window.addEventListener('load', setup); |
||||||
|
window.addEventListener('resize', drawColorbar); |
||||||
|
|
||||||
|
function handle_M_B_S(e) { |
||||||
|
e.preventDefault(); |
||||||
|
var name = e.target.className; |
||||||
|
var val = e.target.id; |
||||||
|
if(e.target.className.indexOf('m') > -1) { |
||||||
|
elems = document.querySelectorAll('#mode li a'); |
||||||
|
[].forEach.call(elems, function(el) { |
||||||
|
el.classList.remove('active'); |
||||||
|
name = e.target.className; |
||||||
|
}); |
||||||
|
e.target.classList.add('active'); |
||||||
|
} |
||||||
|
submitVal(name, val); |
||||||
|
} |
||||||
|
|
||||||
|
function submitVal(name, val) { |
||||||
|
var xhttp = new XMLHttpRequest(); |
||||||
|
xhttp.open('GET', 'set?' + name + '=' + val, true); |
||||||
|
xhttp.send(); |
||||||
|
} |
||||||
|
|
||||||
|
function compToHex(c) { |
||||||
|
hex = c.toString(16); |
||||||
|
return hex.length == 1 ? '0' + hex : hex; |
||||||
|
} |
||||||
|
|
||||||
|
function getMousePos(can, evt) { |
||||||
|
r = can.getBoundingClientRect(); |
||||||
|
return { |
||||||
|
x: evt.clientX - r.left, |
||||||
|
y: evt.clientY - r.top |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function Touch(e) { |
||||||
|
e.preventDefault(); |
||||||
|
pos = { |
||||||
|
x: Math.round(e.targetTouches[0].pageX), |
||||||
|
y: Math.round(e.targetTouches[0].pageY) |
||||||
|
}; |
||||||
|
rgb = ctx.getImageData(pos.x, pos.y, 1, 1).data; |
||||||
|
drawColorbar(rgb); |
||||||
|
submitVal('c', compToHex(rgb[0]) + compToHex(rgb[1]) + compToHex(rgb[2])); |
||||||
|
} |
||||||
|
|
||||||
|
function Click(e) { |
||||||
|
pos = getMousePos(can, e); |
||||||
|
rgb = ctx.getImageData(pos.x, pos.y, 1, 1).data; |
||||||
|
drawColorbar(rgb); |
||||||
|
submitVal('c', compToHex(rgb[0]) + compToHex(rgb[1]) + compToHex(rgb[2])); |
||||||
|
} |
||||||
|
|
||||||
|
// Thanks to the backup at http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
||||||
|
function rgbToHsl(r, g, b){ |
||||||
|
r = r / 255; |
||||||
|
g = g / 255; |
||||||
|
b = b / 255; |
||||||
|
var max = Math.max(r, g, b); |
||||||
|
var min = Math.min(r, g, b); |
||||||
|
var h, s, l = (max + min) / 2; |
||||||
|
if(max == min) { |
||||||
|
h = s = 0; |
||||||
|
} else { |
||||||
|
var d = max - min; |
||||||
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min); |
||||||
|
switch(max) { |
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break; |
||||||
|
case g: h = (b - r) / d + 2; break; |
||||||
|
case b: h = (r - g) / d + 4; break; |
||||||
|
} |
||||||
|
h = h / 6; |
||||||
|
} |
||||||
|
return [h, s, l]; |
||||||
|
} |
||||||
|
|
||||||
|
function drawColorbar(rgb = [0, 0, 0]) { |
||||||
|
can = document.getElementById('colorbar'); |
||||||
|
ctx = can.getContext('2d'); |
||||||
|
can.width = document.body.clientWidth * 0.25; |
||||||
|
var h = can.height / 360; |
||||||
|
|
||||||
|
var hsl = rgbToHsl(rgb[0], rgb[1], rgb[2]); |
||||||
|
|
||||||
|
for(var i=0; i<=360; i++) { |
||||||
|
ctx.fillStyle = 'hsl('+i+', 100%, 50%)'; |
||||||
|
ctx.fillRect(0, i * h, can.width/2, h); |
||||||
|
ctx.fillStyle = 'hsl(' + hsl[0] * 360 + ', 100%, ' + i * (100/360) + '%)'; |
||||||
|
ctx.fillRect(can.width/2, i * h, can.width/2, h); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function setup(){ |
||||||
|
var xhttp = new XMLHttpRequest(); |
||||||
|
xhttp.onreadystatechange = function() { |
||||||
|
if (xhttp.readyState == 4 && xhttp.status == 200) { |
||||||
|
document.getElementById('mode').innerHTML = xhttp.responseText; |
||||||
|
elems = document.querySelectorAll('ul li a'); // adds listener also to existing s and b buttons
|
||||||
|
[].forEach.call(elems, function(el) { |
||||||
|
el.addEventListener('touchstart', handle_M_B_S, false); |
||||||
|
el.addEventListener('click', handle_M_B_S, false); |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
xhttp.open('GET', 'modes', true); |
||||||
|
xhttp.send(); |
||||||
|
|
||||||
|
var can = document.getElementById('colorbar'); |
||||||
|
var ctx = can.getContext('2d'); |
||||||
|
|
||||||
|
drawColorbar(); |
||||||
|
|
||||||
|
can.addEventListener('touchstart', Touch, false); |
||||||
|
can.addEventListener('click', Click, false); |
||||||
|
} |
||||||
|
)====="; |
@ -0,0 +1,180 @@ |
|||||||
|
/*
|
||||||
|
WS2812FX Webinterface. |
||||||
|
|
||||||
|
Harm Aldick - 2016 |
||||||
|
www.aldick.org |
||||||
|
|
||||||
|
|
||||||
|
FEATURES |
||||||
|
* Webinterface with mode, color, speed and brightness selectors |
||||||
|
|
||||||
|
LICENSE: The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2016 Harm Aldick
|
||||||
|
|
||||||
|
CHANGELOG |
||||||
|
2016-11-26 initial version |
||||||
|
|
||||||
|
Tanner Collin: |
||||||
|
2017-09-14 Create Wifi AP to anyone can configure the LEDs. |
||||||
|
Set up captive portal so web interface auto opens. |
||||||
|
Clean up. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
#include <ESP8266WiFi.h> |
||||||
|
#include <ESP8266WebServer.h> |
||||||
|
#include <WS2812FX.h> |
||||||
|
#include <WiFiClient.h> |
||||||
|
#include <DNSServer.h> |
||||||
|
#include <ESP8266mDNS.h> |
||||||
|
|
||||||
|
#include "index.html.h" |
||||||
|
#include "main.js.h" |
||||||
|
|
||||||
|
const char *ssid = "ADD SSID HERE"; |
||||||
|
const char *myHostname = "ws2812"; |
||||||
|
|
||||||
|
#define HTTP_PORT 80 |
||||||
|
|
||||||
|
#define DNS_PORT 53 |
||||||
|
|
||||||
|
// QUICKFIX...See https://github.com/esp8266/Arduino/issues/263
|
||||||
|
#define min(a,b) ((a)<(b)?(a):(b)) |
||||||
|
#define max(a,b) ((a)>(b)?(a):(b)) |
||||||
|
|
||||||
|
#define LED_PIN D7 // 0 = GPIO0, 2=GPIO2
|
||||||
|
#define LED_COUNT 80 |
||||||
|
|
||||||
|
#define DEFAULT_COLOR 0xFF5900 |
||||||
|
#define DEFAULT_BRIGHTNESS 255 |
||||||
|
#define DEFAULT_SPEED 200 |
||||||
|
#define DEFAULT_MODE FX_MODE_STATIC |
||||||
|
|
||||||
|
#define BRIGHTNESS_STEP 15 // in/decrease brightness by this amount per click
|
||||||
|
#define SPEED_STEP 10 // in/decrease brightness by this amount per click
|
||||||
|
|
||||||
|
String modes = ""; |
||||||
|
|
||||||
|
WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); |
||||||
|
ESP8266WebServer server(HTTP_PORT); |
||||||
|
DNSServer dnsServer; |
||||||
|
|
||||||
|
IPAddress apIP(192, 168, 4, 1); |
||||||
|
IPAddress netMsk(255, 255, 255, 0); |
||||||
|
|
||||||
|
void setup(){ |
||||||
|
Serial.begin(115200); |
||||||
|
Serial.println(); |
||||||
|
Serial.println(); |
||||||
|
Serial.println("Starting..."); |
||||||
|
|
||||||
|
modes.reserve(5000); |
||||||
|
modes_setup(); |
||||||
|
|
||||||
|
Serial.println("WS2812FX setup"); |
||||||
|
ws2812fx.init(); |
||||||
|
ws2812fx.setMode(DEFAULT_MODE); |
||||||
|
ws2812fx.setColor(DEFAULT_COLOR); |
||||||
|
ws2812fx.setSpeed(DEFAULT_SPEED); |
||||||
|
ws2812fx.setBrightness(DEFAULT_BRIGHTNESS); |
||||||
|
ws2812fx.start(); |
||||||
|
|
||||||
|
Serial.println("Wifi setup"); |
||||||
|
WiFi.softAPConfig(apIP, apIP, netMsk); |
||||||
|
WiFi.softAP(ssid); |
||||||
|
delay(500); |
||||||
|
IPAddress myIP = WiFi.softAPIP(); |
||||||
|
Serial.print("AP IP address: "); |
||||||
|
Serial.println(myIP); |
||||||
|
|
||||||
|
dnsServer.setErrorReplyCode(DNSReplyCode::NoError); |
||||||
|
dnsServer.start(DNS_PORT, "*", apIP); |
||||||
|
|
||||||
|
Serial.println("HTTP server setup"); |
||||||
|
server.on("/", srv_handle_index_html); |
||||||
|
server.on("/main.js", srv_handle_main_js); |
||||||
|
server.on("/modes", srv_handle_modes); |
||||||
|
server.on("/set", srv_handle_set); |
||||||
|
server.on("/generate_204", srv_handle_index_html); //Android captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||||
|
server.on("/fwlink", srv_handle_index_html); //Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||||
|
server.onNotFound(srv_handle_not_found); |
||||||
|
server.begin(); |
||||||
|
Serial.println("HTTP server started."); |
||||||
|
|
||||||
|
Serial.println("ready!"); |
||||||
|
} |
||||||
|
|
||||||
|
void loop() { |
||||||
|
dnsServer.processNextRequest(); |
||||||
|
server.handleClient(); |
||||||
|
ws2812fx.service(); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Build <li> string for all modes. |
||||||
|
*/ |
||||||
|
void modes_setup() { |
||||||
|
modes = ""; |
||||||
|
for(uint8_t i=0; i < ws2812fx.getModeCount(); i++) { |
||||||
|
modes += "<li><a href='#' class='m' id='"; |
||||||
|
modes += i; |
||||||
|
modes += "'>"; |
||||||
|
modes += ws2812fx.getModeName(i); |
||||||
|
modes += "</a></li>"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/* #####################################################
|
||||||
|
# Webserver Functions |
||||||
|
##################################################### */ |
||||||
|
|
||||||
|
void srv_handle_not_found() { |
||||||
|
server.send(404, "text/plain", "File Not Found"); |
||||||
|
} |
||||||
|
|
||||||
|
void srv_handle_index_html() { |
||||||
|
server.send_P(200,"text/html", index_html); |
||||||
|
} |
||||||
|
|
||||||
|
void srv_handle_main_js() { |
||||||
|
server.send_P(200,"application/javascript", main_js); |
||||||
|
} |
||||||
|
|
||||||
|
void srv_handle_modes() { |
||||||
|
server.send(200,"text/plain", modes); |
||||||
|
} |
||||||
|
|
||||||
|
void srv_handle_set() { |
||||||
|
for (uint8_t i=0; i < server.args(); i++){ |
||||||
|
if(server.argName(i) == "c") { |
||||||
|
uint32_t tmp = (uint32_t) strtol(&server.arg(i)[0], NULL, 16); |
||||||
|
if(tmp >= 0x000000 && tmp <= 0xFFFFFF) { |
||||||
|
ws2812fx.setColor(tmp); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(server.argName(i) == "m") { |
||||||
|
uint8_t tmp = (uint8_t) strtol(&server.arg(i)[0], NULL, 10); |
||||||
|
ws2812fx.setMode(tmp % ws2812fx.getModeCount()); |
||||||
|
} |
||||||
|
|
||||||
|
if(server.argName(i) == "b") { |
||||||
|
if(server.arg(i)[0] == '-') { |
||||||
|
ws2812fx.decreaseBrightness(BRIGHTNESS_STEP); |
||||||
|
} else { |
||||||
|
ws2812fx.increaseBrightness(BRIGHTNESS_STEP); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if(server.argName(i) == "s") { |
||||||
|
if(server.arg(i)[0] == '-') { |
||||||
|
ws2812fx.decreaseSpeed(SPEED_STEP); |
||||||
|
} else { |
||||||
|
ws2812fx.increaseSpeed(SPEED_STEP); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
server.send(200, "text/plain", "OK"); |
||||||
|
} |
Loading…
Reference in new issue