commit a950a0769de38e03fcc69e1cc1c7cb052475f4b3 Author: Tanner Collin Date: Thu Sep 14 22:35:56 2017 -0600 Initial commit diff --git a/index.html.h b/index.html.h new file mode 100644 index 0000000..5a3db87 --- /dev/null +++ b/index.html.h @@ -0,0 +1,90 @@ +const char index_html[] = R"=====( + + + + + + + WS2812FX Ctrl + + + + + + +

WS2812FX Control

+ +
+ + + + + +
+ + +)====="; diff --git a/main.js.h b/main.js.h new file mode 100644 index 0000000..58d35f1 --- /dev/null +++ b/main.js.h @@ -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); +} +)====="; diff --git a/neopixel-wifi-control.ino b/neopixel-wifi-control.ino new file mode 100644 index 0000000..fa98bf6 --- /dev/null +++ b/neopixel-wifi-control.ino @@ -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 +#include +#include +#include +#include +#include + +#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
  • string for all modes. + */ +void modes_setup() { + modes = ""; + for(uint8_t i=0; i < ws2812fx.getModeCount(); i++) { + modes += "
  • "; + modes += ws2812fx.getModeName(i); + modes += "
  • "; + } +} + +/* ##################################################### +# 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"); +}