Initial commit
This commit is contained in:
parent
07017a21f9
commit
7028c60ea5
|
@ -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 <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
#define WEB_SERVER WebServer
|
||||
#define ESP_RESET ESP.restart()
|
||||
#else
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266WebServer.h>
|
||||
#define WEB_SERVER ESP8266WebServer
|
||||
#define ESP_RESET ESP.reset()
|
||||
#endif
|
||||
|
||||
#include <WS2812FX.h>
|
||||
#include <ArtnetWifi.h>
|
||||
#include <string>
|
||||
|
||||
//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 <li> 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 += "<li><a href='#'>";
|
||||
modes += ws2812fx.getModeName(m);
|
||||
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).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");
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
#include <pgmspace.h>
|
||||
char index_html[] PROGMEM = R"=====(
|
||||
<!doctype html>
|
||||
<html lang='en' dir='ltr'>
|
||||
<head>
|
||||
<meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
|
||||
<title>WS2812FX Control</title>
|
||||
<script type='text/javascript' src='main.js'></script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family:Arial,sans-serif;
|
||||
margin:10px;
|
||||
padding:0;
|
||||
background-color:#202020;
|
||||
color:#909090;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
}
|
||||
|
||||
.flex-row-wrap {
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
background-color: #d0d0d0;
|
||||
color:#404040;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
ul li a {
|
||||
display:block;
|
||||
margin:3px;
|
||||
padding:10px;
|
||||
border:2px solid #404040;
|
||||
border-radius:5px;
|
||||
color:#909090;
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
ul#modes li a {
|
||||
min-width:220px;
|
||||
}
|
||||
|
||||
ul.control li a {
|
||||
min-width:60px;
|
||||
min-height:24px;
|
||||
}
|
||||
|
||||
ul.control {
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
ul li a.active {
|
||||
border:2px solid #909090;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WS2812FX Control</h1>
|
||||
<div class='flex-row'>
|
||||
|
||||
<div class='flex-col'>
|
||||
<div><canvas id='color-canvas' width='360' height='360'></canvas><br/></div>
|
||||
<div><input type='text' id='color-value' oninput='onColor(event, this.value)'/></div>
|
||||
|
||||
<div>
|
||||
<ul class='control'>
|
||||
<li>Brightness:</li>
|
||||
<li><a href='#' onclick="onBrightness(event, '-')">☼</a></li>
|
||||
<li><a href='#' onclick="onBrightness(event, '+')">☀</a></li>
|
||||
</ul>
|
||||
|
||||
<ul class='control'>
|
||||
<li>Speed:</li>
|
||||
<li><a href='#' onclick="onSpeed(event, '-')">−</a></li>
|
||||
<li><a href='#' onclick="onSpeed(event, '+')">+</a></li>
|
||||
</ul>
|
||||
|
||||
<ul class='control'>
|
||||
<li>Auto cycle:</li>
|
||||
<li><a href='#' onclick="onAuto(event, '-')">■</a></li>
|
||||
<li><a href='#' onclick="onAuto(event, '+')">►</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ul id='modes' class='flex-row-wrap'>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)=====";
|
|
@ -0,0 +1,110 @@
|
|||
#include <pgmspace.h>
|
||||
|
||||
/*
|
||||
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();
|
||||
}
|
||||
)=====";
|
Loading…
Reference in New Issue