Compare commits

..

1 Commits
main ... master

Author SHA1 Message Date
jimmy 7028c60ea5 Initial commit 2023-11-28 20:38:13 +13:00
3 changed files with 545 additions and 0 deletions

321
esp8266_webinterface.ino Normal file
View File

@ -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");
}

114
index.html.cpp Normal file
View File

@ -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, '-')">&#9788;</a></li>
<li><a href='#' onclick="onBrightness(event, '+')">&#9728;</a></li>
</ul>
<ul class='control'>
<li>Speed:</li>
<li><a href='#' onclick="onSpeed(event, '-')">&#8722;</a></li>
<li><a href='#' onclick="onSpeed(event, '+')">&#43;</a></li>
</ul>
<ul class='control'>
<li>Auto cycle:</li>
<li><a href='#' onclick="onAuto(event, '-')">&#9632;</a></li>
<li><a href='#' onclick="onAuto(event, '+')">&#9658;</a></li>
</ul>
</div>
</div>
<div>
<ul id='modes' class='flex-row-wrap'>
</div>
</div>
</body>
</html>
)=====";

110
main.js.cpp Normal file
View File

@ -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();
}
)=====";