diff --git a/esp8266_webinterface.ino b/esp8266_webinterface.ino new file mode 100644 index 0000000..8794e7c --- /dev/null +++ b/esp8266_webinterface.ino @@ -0,0 +1,321 @@ +/* + 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 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + + CHANGELOG + 2016-11-26 initial version + 2018-01-06 added custom effects list option and auto-cycle feature + +*/ +#ifdef ARDUINO_ARCH_ESP32 + #include + #include + #define WEB_SERVER WebServer + #define ESP_RESET ESP.restart() +#else + #include + #include + #define WEB_SERVER ESP8266WebServer + #define ESP_RESET ESP.reset() +#endif + +#include +#include +#include + +//IPAddress ip(192,168,1,200); +//IPAddress gateway(192,168,1,1); +//IPAddress subnet(255,255,255,0); + +extern const char index_html[]; +extern const char main_js[]; + +#define WIFI_SSID "XCHC2" +#define WIFI_PASSWORD "workspace" + + + + +// 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 2 // 0 = GPIO0, 2=GPIO2 +#define LED_COUNT 100 + +#define WIFI_TIMEOUT 30000 // checks WiFi every ...ms. Reset after this time, if WiFi cannot reconnect. +#define HTTP_PORT 80 + +unsigned long auto_last_change = 0; +unsigned long last_wifi_check_time = 0; +String modes = ""; +uint8_t myModes[] = {}; // *** optionally create a custom list of effect/mode numbers +bool auto_cycle = false; + +WiFiUDP UdpSend; +ArtnetWifi artnet; + +WS2812FX ws2812fx = WS2812FX(LED_COUNT, LED_PIN, NEO_BRG + NEO_KHZ800); +WEB_SERVER server(HTTP_PORT); + +void setup(){ + Serial.begin(115200); + delay(500); + Serial.println("\n\nStarting..."); + + modes.reserve(5000); + modes_setup(); + + Serial.println("WS2812FX setup"); + ws2812fx.init(); + ws2812fx.setMode(FX_MODE_STATIC); + ws2812fx.setColor(0xFF5900); + ws2812fx.setSpeed(1000); + ws2812fx.setBrightness(128); + ws2812fx.start(); + + Serial.println("Wifi setup"); + wifi_setup(); + + 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.onNotFound(srv_handle_not_found); + server.begin(); + Serial.println("HTTP server started."); + + // this will be called for each packet received + artnet.setArtDmxCallback(onDmxFrame); + artnet.begin(); + + Serial.println("ready!"); +} + + +void loop() { + unsigned long now = millis(); + + server.handleClient(); + ws2812fx.service(); + +// if(now - last_wifi_check_time > WIFI_TIMEOUT) { +// Serial.print("Checking WiFi... "); +// if(WiFi.status() != WL_CONNECTED) { +// Serial.println("WiFi connection lost. Reconnecting..."); +// wifi_setup(); +// } else { +// Serial.println("OK"); +// } +// last_wifi_check_time = now; +// } + + if(auto_cycle && (now - auto_last_change > 10000)) { // cycle effect mode every 10 seconds + uint8_t next_mode = (ws2812fx.getMode() + 1) % ws2812fx.getModeCount(); + if(sizeof(myModes) > 0) { // if custom list of modes exists + for(uint8_t i=0; i < sizeof(myModes); i++) { + if(myModes[i] == ws2812fx.getMode()) { + next_mode = ((i + 1) < sizeof(myModes)) ? myModes[i + 1] : myModes[0]; + break; + } + } + } + ws2812fx.setMode(next_mode); + Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode())); + auto_last_change = now; + } + //artnet.read(); +} + +void onDmxFrame(uint16_t universe, uint16_t length, uint8_t sequence, uint8_t* data) +{ + bool tail = false; + + Serial.print("DMX: Univ: "); + Serial.print(universe, DEC); + Serial.print(", Seq: "); + Serial.print(sequence, DEC); + Serial.print(", Data ("); + Serial.print(length, DEC); + Serial.print("): "); + + if (length > 16) { + length = 16; + tail = true; + } + // send out the buffer + for (int i = 0; i < length; i++) + { + Serial.print(data[i], HEX); + Serial.print(" "); + } + if (tail) { + Serial.print("..."); + } + Serial.println(); +} + + + +/* + * Connect to WiFi. If no connection is made within WIFI_TIMEOUT, ESP gets resettet. + */ +void wifi_setup() { + char APssid[50] = "LED_"; + char id[8]; + itoa(ESP.getChipId(), id, 16); + strcat(APssid, id); + Serial.println(APssid); + const char* APpassword = "1234567890"; + Serial.println(); + Serial.print("Connecting to "); + Serial.println(WIFI_SSID); + + WiFi.softAP(APssid, APpassword); + WiFi.mode(WIFI_AP); + WiFi.begin(); + + //WiFi.config(ip, gateway, subnet); +// +// unsigned long connect_start = millis(); +// while(WiFi.status() != WL_CONNECTED) { +// delay(500); +// Serial.print("."); +// +// if(millis() - connect_start > WIFI_TIMEOUT) { +// Serial.println(); +// Serial.print("Tried "); +// Serial.print(WIFI_TIMEOUT); +// Serial.print("ms. Resetting ESP now."); +// ESP_RESET; +// } +// } + + Serial.println(""); + Serial.println("WiFi connected"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println(WiFi.softAPIP()); + Serial.println(); +} + + +/* + * Build
  • string for all modes. + */ +void modes_setup() { + modes = ""; + uint8_t num_modes = sizeof(myModes) > 0 ? sizeof(myModes) : ws2812fx.getModeCount(); + for(uint8_t i=0; i < num_modes; i++) { + uint8_t m = sizeof(myModes) > 0 ? myModes[i] : i; + modes += "
  • "; + modes += ws2812fx.getModeName(m); + 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).c_str(), NULL, 10); + if(tmp <= 0xFFFFFF) { + ws2812fx.setColor(tmp); + } + } + + if(server.argName(i) == "m") { + uint8_t tmp = (uint8_t) strtol(server.arg(i).c_str(), NULL, 10); + uint8_t new_mode = sizeof(myModes) > 0 ? myModes[tmp % sizeof(myModes)] : tmp % ws2812fx.getModeCount(); + ws2812fx.setMode(new_mode); + auto_cycle = false; + Serial.print("mode is "); Serial.println(ws2812fx.getModeName(ws2812fx.getMode())); + } + + if(server.argName(i) == "b") { + if(server.arg(i)[0] == '-') { + ws2812fx.setBrightness(ws2812fx.getBrightness() * 0.8); + } else if(server.arg(i)[0] == ' ') { + ws2812fx.setBrightness(min(max(ws2812fx.getBrightness(), 5) * 1.2, 255)); + } else { // set brightness directly + uint8_t tmp = (uint8_t) strtol(server.arg(i).c_str(), NULL, 10); + ws2812fx.setBrightness(tmp); + } + Serial.print("brightness is "); Serial.println(ws2812fx.getBrightness()); + } + + if(server.argName(i) == "s") { + if(server.arg(i)[0] == '-') { + ws2812fx.setSpeed(max(ws2812fx.getSpeed(), 5) * 1.2); + } else if(server.arg(i)[0] == ' ') { + ws2812fx.setSpeed(ws2812fx.getSpeed() * 0.8); + } else { + uint16_t tmp = (uint16_t) strtol(server.arg(i).c_str(), NULL, 10); + ws2812fx.setSpeed(tmp); + } + Serial.print("speed is "); Serial.println(ws2812fx.getSpeed()); + } + + if(server.argName(i) == "a") { + if(server.arg(i)[0] == '-') { + auto_cycle = false; + } else { + auto_cycle = true; + auto_last_change = 0; + } + } + } + server.send(200, "text/plain", "OK"); +} diff --git a/index.html.cpp b/index.html.cpp new file mode 100644 index 0000000..56a39cf --- /dev/null +++ b/index.html.cpp @@ -0,0 +1,114 @@ +#include +char index_html[] PROGMEM = R"=====( + + + + + + WS2812FX Control + + + + + +

    WS2812FX Control

    +
    + +
    +

    +
    + +
    + + +
      +
    • Speed:
    • +
    • +
    • +
    • +
    + + +
    +
    + +
    +
      +
    +
    + + +)====="; diff --git a/main.js.cpp b/main.js.cpp new file mode 100644 index 0000000..3c7f54c --- /dev/null +++ b/main.js.cpp @@ -0,0 +1,110 @@ +#include + +/* +The tiny Javascript/canvas based color picker is based on the clever work of the folks +at Sparkbox. https://seesparkbox.com/foundry/how_i_built_a_canvas_color_picker +*/ + +char main_js[] PROGMEM = R"=====( + +var activeButton = null; +var colorCanvas = null; + +window.addEventListener('DOMContentLoaded', (event) => { + // init the canvas color picker + colorCanvas = document.getElementById('color-canvas'); + var colorctx = colorCanvas.getContext('2d'); + + // Create color gradient + var gradient = colorctx.createLinearGradient(0, 0, colorCanvas.width - 1, 0); + gradient.addColorStop(0, "rgb(255, 0, 0)"); + gradient.addColorStop(0.16, "rgb(255, 0, 255)"); + gradient.addColorStop(0.33, "rgb(0, 0, 255)"); + gradient.addColorStop(0.49, "rgb(0, 255, 255)"); + gradient.addColorStop(0.66, "rgb(0, 255, 0)"); + gradient.addColorStop(0.82, "rgb(255, 255, 0)"); + gradient.addColorStop(1, "rgb(255, 0, 0)"); + + // Apply gradient to canvas + colorctx.fillStyle = gradient; + colorctx.fillRect(0, 0, colorCanvas.width - 1, colorCanvas.height - 1); + + // Create semi transparent gradient (white -> transparent -> black) + gradient = colorctx.createLinearGradient(0, 0, 0, colorCanvas.height - 1); + gradient.addColorStop(0, "rgba(255, 255, 255, 1)"); + gradient.addColorStop(0.48, "rgba(255, 255, 255, 0)"); + gradient.addColorStop(0.52, "rgba(0, 0, 0, 0)"); + gradient.addColorStop(1, "rgba(0, 0, 0, 1)"); + + // Apply gradient to canvas + colorctx.fillStyle = gradient; + colorctx.fillRect(0, 0, colorCanvas.width - 1, colorCanvas.height - 1); + + // setup the canvas click listener + colorCanvas.addEventListener('click', (event) => { + var imageData = colorCanvas.getContext('2d').getImageData(event.offsetX, event.offsetY, 1, 1); + + var selectedColor = 'rgb(' + imageData.data[0] + ',' + imageData.data[1] + ',' + imageData.data[2] + ')'; + //console.log('click: ' + event.offsetX + ', ' + event.offsetY + ', ' + selectedColor); + document.getElementById('color-value').value = selectedColor; + + selectedColor = imageData.data[0] * 65536 + imageData.data[1] * 256 + imageData.data[2]; + submitVal('c', selectedColor); + }); + + // get list of modes from ESP + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (xhttp.readyState == 4 && xhttp.status == 200) { + document.getElementById('modes').innerHTML = xhttp.responseText; + modes = document.querySelectorAll('ul#modes li a'); + modes.forEach(initMode); + } + }; + xhttp.open('GET', 'modes', true); + xhttp.send(); +}); + +function initMode(mode, index) { + mode.addEventListener('click', (event) => onMode(event, index)); +} + +function onColor(event, color) { + event.preventDefault(); + var match = color.match(/rgb\(([0-9]*),([0-9]*),([0-9]*)\)/); + if(match) { + var colorValue = Number(match[1]) * 65536 + Number(match[2]) * 256 + Number(match[3]); + //console.log('onColor:' + match[1] + "," + match[2] + "," + match[3] + "," + colorValue); + submitVal('c', colorValue); + } +} + +function onMode(event, mode) { + event.preventDefault(); + if(activeButton) activeButton.classList.remove('active') + activeButton = event.target; + activeButton.classList.add('active'); + submitVal('m', mode); +} + +function onBrightness(event, dir) { + event.preventDefault(); + submitVal('b', dir); +} + +function onSpeed(event, dir) { + event.preventDefault(); + submitVal('s', dir); +} + +function onAuto(event, dir) { + event.preventDefault(); + submitVal('a', dir); +} + +function submitVal(name, val) { + var xhttp = new XMLHttpRequest(); + xhttp.open('GET', 'set?' + name + '=' + val, true); + xhttp.send(); +} +)=====";