1
0
mirror of https://github.com/s00500/ESPUI.git synced 2025-07-03 19:50:20 +00:00

6 Commits

Author SHA1 Message Date
b21c5d3b2c 1.6.1 2018-12-27 11:37:50 +01:00
98d1215d7a #44 Adding define to reenable WS BasicAuth 2018-12-27 11:34:28 +01:00
7a10457f99 #44 Adding Basic Auth
- Also authing websockets
- Implemented on begin and beginSpiffs
- Added notes to Gui example
2018-12-26 13:38:38 +01:00
f31575b50c #43 Sending Initial GUI as one big array
- Added new  INITIAL_GUI Type
- spliting GUI Blob to events in controls js
- formating the json in jsonDom into one big array
2018-12-26 12:35:35 +01:00
980e20818f Merge pull request #42 from don41382/patch-2
udpateSlider implementation with label was missing
2018-12-02 14:22:43 +01:00
e9aca78c9c udpateSlider implementation with label was missing 2018-12-02 11:41:45 +01:00
9 changed files with 303 additions and 252 deletions

View File

@ -1,3 +1,4 @@
const UI_INITIAL_GUI = 100;
const UI_TITEL = 0; const UI_TITEL = 0;
const UI_LABEL = 1; const UI_LABEL = 1;
@ -112,26 +113,37 @@ function handleVisibilityChange() {
function start() { function start() {
document.addEventListener("visibilitychange", handleVisibilityChange, false); document.addEventListener("visibilitychange", handleVisibilityChange, false);
websock = new WebSocket("ws://" + window.location.hostname + "/ws"); websock = new WebSocket("ws://" + window.location.hostname + "/ws");
websock.onopen = function(evt) { websock.onopen = function (evt) {
console.log("websock open"); console.log("websock open");
$("#conStatus").addClass("color-green"); $("#conStatus").addClass("color-green");
$("#conStatus").text("Connected"); $("#conStatus").text("Connected");
websockConnected = true; websockConnected = true;
}; };
websock.onclose = function(evt) {
websock.onclose = function (evt) {
console.log("websock close"); console.log("websock close");
conStatusError(); conStatusError();
}; };
websock.onerror = function(evt) {
websock.onerror = function (evt) {
console.log(evt); console.log(evt);
conStatusError(); conStatusError();
}; };
websock.onmessage = function(evt) {
console.log(evt); var handleEvent = function (evt) {
//console.log(evt);
var data = JSON.parse(evt.data); var data = JSON.parse(evt.data);
var e = document.body; var e = document.body;
var center = ""; var center = "";
switch (data.type) { switch (data.type) {
case UI_INITIAL_GUI:
data.controls.forEach(element => {
var fauxEvent = {
data: JSON.stringify(element)
};
handleEvent(fauxEvent);
});
break;
case UI_TITEL: case UI_TITEL:
document.title = data.label; document.title = data.label;
$("#mainHeader").html(data.label); $("#mainHeader").html(data.label);
@ -139,42 +151,42 @@ function start() {
case UI_LABEL: case UI_LABEL:
$("#row").append( $("#row").append(
"<div class='two columns card tcenter " + "<div class='two columns card tcenter " +
colorClass(data.color) + colorClass(data.color) +
"'><h5 id='" + "'><h5 id='" +
data.id + data.id +
"'>" + "'>" +
data.label + data.label +
"</h5><hr /><span id='l" + "</h5><hr /><span id='l" +
data.id + data.id +
"' class='label label-wrap'>" + "' class='label label-wrap'>" +
data.value + data.value +
"</span></div>" "</span></div>"
); );
break; break;
case UI_BUTTON: case UI_BUTTON:
$("#row").append( $("#row").append(
"<div class='one columns card tcenter " + "<div class='one columns card tcenter " +
colorClass(data.color) + colorClass(data.color) +
"'><h5>" + "'><h5>" +
data.label + data.label +
"</h5><hr/><button onmousedown='buttonclick(" + "</h5><hr/><button onmousedown='buttonclick(" +
data.id + data.id +
", true)' onmouseup='buttonclick(" + ", true)' onmouseup='buttonclick(" +
data.id + data.id +
", false)' id='" + ", false)' id='" +
data.id + data.id +
"'>" + "'>" +
data.value + data.value +
"</button></div>" "</button></div>"
); );
$("#" + data.id).on({ $("#" + data.id).on({
touchstart: function(e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
buttonclick(data.id, true); buttonclick(data.id, true);
} }
}); });
$("#" + data.id).on({ $("#" + data.id).on({
touchend: function(e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
buttonclick(data.id, false); buttonclick(data.id, false);
} }
@ -199,16 +211,16 @@ function start() {
} }
$("#row").append( $("#row").append(
"<div id='" + "<div id='" +
data.id + data.id +
"' class='one columns card tcenter " + "' class='one columns card tcenter " +
colorClass(data.color) + colorClass(data.color) +
"'><h5>" + "'><h5>" +
data.label + data.label +
"</h5><hr/>" + "</h5><hr/>" +
label + label +
input + input +
"</label>" + "</label>" +
"</div>" "</div>"
); );
break; break;
case UI_CPAD: case UI_CPAD:
@ -220,106 +232,106 @@ function start() {
", false)' href='#' id='pc" + ", false)' href='#' id='pc" +
data.id + data.id +
"'>OK</a>"; "'>OK</a>";
//NO BREAK //NO BREAK
case UI_PAD: case UI_PAD:
$("#row").append( $("#row").append(
"<div class='two columns card tcenter " + "<div class='two columns card tcenter " +
colorClass(data.color) + colorClass(data.color) +
"'><h5>" + "'><h5>" +
data.label + data.label +
"</h5><hr/>" + "</h5><hr/>" +
"<nav class='control'>" + "<nav class='control'>" +
"<ul>" + "<ul>" +
"<li><a onmousedown='padclick(FOR, " + "<li><a onmousedown='padclick(FOR, " +
data.id + data.id +
", true)' onmouseup='padclick(FOR, " + ", true)' onmouseup='padclick(FOR, " +
data.id + data.id +
", false)' href='#' id='pf" + ", false)' href='#' id='pf" +
data.id + data.id +
"'>▲</a></li>" + "'>▲</a></li>" +
"<li><a onmousedown='padclick(RIGHT, " + "<li><a onmousedown='padclick(RIGHT, " +
data.id + data.id +
", true)' onmouseup='padclick(RIGHT, " + ", true)' onmouseup='padclick(RIGHT, " +
data.id + data.id +
", false)' href='#' id='pr" + ", false)' href='#' id='pr" +
data.id + data.id +
"'>▲</a></li>" + "'>▲</a></li>" +
"<li><a onmousedown='padclick(LEFT, " + "<li><a onmousedown='padclick(LEFT, " +
data.id + data.id +
", true)' onmouseup='padclick(LEFT, " + ", true)' onmouseup='padclick(LEFT, " +
data.id + data.id +
", false)' href='#' id='pl" + ", false)' href='#' id='pl" +
data.id + data.id +
"'>▲</a></li>" + "'>▲</a></li>" +
"<li><a onmousedown='padclick(BACK, " + "<li><a onmousedown='padclick(BACK, " +
data.id + data.id +
", true)' onmouseup='padclick(BACK, " + ", true)' onmouseup='padclick(BACK, " +
data.id + data.id +
", false)' href='#' id='pb" + ", false)' href='#' id='pb" +
data.id + data.id +
"'>▲</a></li>" + "'>▲</a></li>" +
"</ul>" + "</ul>" +
center + center +
"</nav>" + "</nav>" +
"</div>" "</div>"
); );
$("#pf" + data.id).on({ $("#pf" + data.id).on({
touchstart: function(e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
padclick(FOR, data.id, true); padclick(FOR, data.id, true);
} }
}); });
$("#pf" + data.id).on({ $("#pf" + data.id).on({
touchend: function(e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
padclick(FOR, data.id, false); padclick(FOR, data.id, false);
} }
}); });
$("#pl" + data.id).on({ $("#pl" + data.id).on({
touchstart: function(e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
padclick(LEFT, data.id, true); padclick(LEFT, data.id, true);
} }
}); });
$("#pl" + data.id).on({ $("#pl" + data.id).on({
touchend: function(e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
padclick(LEFT, data.id, false); padclick(LEFT, data.id, false);
} }
}); });
$("#pr" + data.id).on({ $("#pr" + data.id).on({
touchstart: function(e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
padclick(RIGHT, data.id, true); padclick(RIGHT, data.id, true);
} }
}); });
$("#pr" + data.id).on({ $("#pr" + data.id).on({
touchend: function(e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
padclick(RIGHT, data.id, false); padclick(RIGHT, data.id, false);
} }
}); });
$("#pb" + data.id).on({ $("#pb" + data.id).on({
touchstart: function(e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
padclick(BACK, data.id, true); padclick(BACK, data.id, true);
} }
}); });
$("#pb" + data.id).on({ $("#pb" + data.id).on({
touchend: function(e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
padclick(BACK, data.id, false); padclick(BACK, data.id, false);
} }
}); });
$("#pc" + data.id).on({ $("#pc" + data.id).on({
touchstart: function(e) { touchstart: function (e) {
e.preventDefault(); e.preventDefault();
padclick(CENTER, data.id, true); padclick(CENTER, data.id, true);
} }
}); });
$("#pc" + data.id).on({ $("#pc" + data.id).on({
touchend: function(e) { touchend: function (e) {
e.preventDefault(); e.preventDefault();
padclick(CENTER, data.id, false); padclick(CENTER, data.id, false);
} }
@ -336,23 +348,23 @@ function start() {
case UI_SLIDER: case UI_SLIDER:
$("#row").append( $("#row").append(
"<div class='two columns card tcenter card-slider " + "<div class='two columns card tcenter card-slider " +
colorClass(data.color) + colorClass(data.color) +
"'>" + "'>" +
"<h5 id='" + "<h5 id='" +
data.id + data.id +
"'>" + "'>" +
data.label + data.label +
"</h5><hr />" + "</h5><hr />" +
"<div id='sl" + "<div id='sl" +
data.id + data.id +
"' class='rkmd-slider slider-discrete slider-" + "' class='rkmd-slider slider-discrete slider-" +
colorClass(data.color) + colorClass(data.color) +
"'>" + "'>" +
"<input type='range' min='0' max='100' value='" + "<input type='range' min='0' max='100' value='" +
data.value + data.value +
"'>" + "'>" +
"</div>" + "</div>" +
"</div>" "</div>"
); );
$("#row").append( $("#row").append(
"<script>" + "rkmd_rangeSlider('#sl" + data.id + "');" + "</script>" "<script>" + "rkmd_rangeSlider('#sl" + data.id + "');" + "</script>"
@ -366,21 +378,21 @@ function start() {
case UI_NUMBER: case UI_NUMBER:
$("#row").append( $("#row").append(
"<div class='two columns card tcenter " + "<div class='two columns card tcenter " +
colorClass(data.color) + colorClass(data.color) +
"'>" + "'>" +
"<h5 id='" + "<h5 id='" +
data.id + data.id +
"'>" + "'>" +
data.label + data.label +
"</h5><hr />" + "</h5><hr />" +
"<input style='color:black;' id='num" + "<input style='color:black;' id='num" +
data.id + data.id +
"' type='number' value='" + "' type='number' value='" +
data.value + data.value +
"' onchange='numberchange(" + "' onchange='numberchange(" +
data.id + data.id +
")' />" + ")' />" +
"</div>" "</div>"
); );
break; break;
@ -391,21 +403,21 @@ function start() {
case UI_TEXT_INPUT: case UI_TEXT_INPUT:
$("#row").append( $("#row").append(
"<div class='two columns card tcenter " + "<div class='two columns card tcenter " +
colorClass(data.color) + colorClass(data.color) +
"'>" + "'>" +
"<h5 id='" + "<h5 id='" +
data.id + data.id +
"'>" + "'>" +
data.label + data.label +
"</h5><hr />" + "</h5><hr />" +
"<input style='color:black;' id='text" + "<input style='color:black;' id='text" +
data.id + data.id +
"' value='" + "' value='" +
data.value + data.value +
"' onchange='textchange(" + "' onchange='textchange(" +
data.id + data.id +
")' />" + ")' />" +
"</div>" "</div>"
); );
break; break;
@ -418,6 +430,8 @@ function start() {
break; break;
} }
}; };
websock.onmessage = handleEvent;
} }
function numberchange(number) { function numberchange(number) {

View File

@ -1,8 +1,8 @@
const UI_TITEL=0;const UI_LABEL=1;const UPDATE_LABEL=6;const UI_BUTTON=2;const UI_SWITCHER=3;const UPDATE_SWITCHER=7;const UI_PAD=4;const UI_CPAD=5;const UI_SLIDER=8;const UPDATE_SLIDER=9;const UI_NUMBER=10;const UPDATE_NUMBER=11;const UI_TEXT_INPUT=12;const UPDATE_TEXT_INPUT=13;const UI_GRAPH=14;const CLEAR_GRAPH=15;const ADD_GRAPH_POINT=16;const FOR=0;const BACK=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_NONE=7;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";break;case C_EMERALD:return"emerald";break;case C_PETERRIVER:return"peterriver";break;case C_WETASPHALT:return"wetasphalt";break;case C_SUNFLOWER:return"sunflower";break;case C_CARROT:return"carrot";break;case C_ALIZARIN:return"alizarin";break;case C_NONE:return"";break;default:return"";}} const UI_INITIAL_GUI=100;const UI_TITEL=0;const UI_LABEL=1;const UPDATE_LABEL=6;const UI_BUTTON=2;const UI_SWITCHER=3;const UPDATE_SWITCHER=7;const UI_PAD=4;const UI_CPAD=5;const UI_SLIDER=8;const UPDATE_SLIDER=9;const UI_NUMBER=10;const UPDATE_NUMBER=11;const UI_TEXT_INPUT=12;const UPDATE_TEXT_INPUT=13;const UI_GRAPH=14;const CLEAR_GRAPH=15;const ADD_GRAPH_POINT=16;const FOR=0;const BACK=1;const LEFT=2;const RIGHT=3;const CENTER=4;const C_TURQUOISE=0;const C_EMERALD=1;const C_PETERRIVER=2;const C_WETASPHALT=3;const C_SUNFLOWER=4;const C_CARROT=5;const C_ALIZARIN=6;const C_NONE=7;function colorClass(colorId){colorId=Number(colorId);switch(colorId){case C_TURQUOISE:return"turquoise";break;case C_EMERALD:return"emerald";break;case C_PETERRIVER:return"peterriver";break;case C_WETASPHALT:return"wetasphalt";break;case C_SUNFLOWER:return"sunflower";break;case C_CARROT:return"carrot";break;case C_ALIZARIN:return"alizarin";break;case C_NONE:return"";break;default:return"";}}
var websock;var websockConnected=false;function restart(){$(document).add("*").off();$("#row").html("");websock.close();start();} var websock;var websockConnected=false;function restart(){$(document).add("*").off();$("#row").html("");websock.close();start();}
function conStatusError(){websockConnected=false;$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection &#8635;");$("#conStatus").off();$("#conStatus").on({click:restart});} function conStatusError(){websockConnected=false;$("#conStatus").removeClass("color-green");$("#conStatus").addClass("color-red");$("#conStatus").html("Error / No Connection &#8635;");$("#conStatus").off();$("#conStatus").on({click:restart});}
function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}} function handleVisibilityChange(){if(!websockConnected&&!document.hidden){restart();}}
function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);websock=new WebSocket("ws://"+window.location.hostname+"/ws");websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log(evt);conStatusError();};websock.onmessage=function(evt){console.log(evt);var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:$("#row").append("<div class='two columns card tcenter "+ function start(){document.addEventListener("visibilitychange",handleVisibilityChange,false);websock=new WebSocket("ws://"+window.location.hostname+"/ws");websock.onopen=function(evt){console.log("websock open");$("#conStatus").addClass("color-green");$("#conStatus").text("Connected");websockConnected=true;};websock.onclose=function(evt){console.log("websock close");conStatusError();};websock.onerror=function(evt){console.log(evt);conStatusError();};var handleEvent=function(evt){var data=JSON.parse(evt.data);var e=document.body;var center="";switch(data.type){case UI_INITIAL_GUI:data.controls.forEach(element=>{var fauxEvent={data:JSON.stringify(element)};handleEvent(fauxEvent);});break;case UI_TITEL:document.title=data.label;$("#mainHeader").html(data.label);break;case UI_LABEL:$("#row").append("<div class='two columns card tcenter "+
colorClass(data.color)+ colorClass(data.color)+
"'><h5 id='"+ "'><h5 id='"+
data.id+ data.id+
@ -134,7 +134,7 @@ data.value+
"' onchange='textchange("+ "' onchange='textchange("+
data.id+ data.id+
")' />"+ ")' />"+
"</div>");break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);break;default:console.error("Unknown type or event");break;}};} "</div>");break;case UPDATE_TEXT_INPUT:$("#text"+data.id).val(data.value);break;default:console.error("Unknown type or event");break;}};websock.onmessage=handleEvent;}
function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);console.log(val);} function numberchange(number){var val=$("#num"+number).val();websock.send("nvalue:"+val+":"+number);console.log(val);}
function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);console.log(val);} function textchange(number){var val=$("#text"+number).val();websock.send("tvalue:"+val+":"+number);console.log(val);}
function buttonclick(number,isdown){if(isdown)websock.send("bdown:"+number);else websock.send("bup:"+number);} function buttonclick(number,isdown){if(isdown)websock.send("bdown:"+number);else websock.send("bup:"+number);}

View File

@ -157,11 +157,19 @@ void setup(void) {
/* /*
.begin loads and serves all files from PROGMEM directly. .begin loads and serves all files from PROGMEM directly.
If you want to serve the files from SPIFFS use .beginSPIFFS If you want to serve the files from SPIFFS use ESPUI.beginSPIFFS
(.prepareFileSystem has to be run in an empty sketch before) (.prepareFileSystem has to be run in an empty sketch before)
*/ */
dnsServer.start(DNS_PORT, "*", apIP); dnsServer.start(DNS_PORT, "*", apIP);
/*
* Optionally you can use HTTP BasicAuth. Keep in mind that this is NOT a
SECURE way of limiting access.
* Anyone who is able to sniff traffic will be able to intercept your password
since it is transmitted in cleartext ESPUI.begin("ESPUI Control", "myuser",
"mypassword");
*/
ESPUI.begin("ESPUI Control"); ESPUI.begin("ESPUI Control");
} }

View File

@ -6,16 +6,13 @@
"type": "git", "type": "git",
"url": "https://github.com/s00500/ESPUI.git" "url": "https://github.com/s00500/ESPUI.git"
}, },
"authors": [ "authors": [{
{ "name": "Lukas Bachschwell",
"name": "Lukas Bachschwell", "email": "lukas@lbsfilm.at",
"email": "lukas@lbsfilm.at", "url": "https://lbsfilm.at",
"url": "https://lbsfilm.at", "maintainer": true
"maintainer": true }],
} "dependencies": [{
],
"dependencies": [
{
"name": "ESP Async WebServer", "name": "ESP Async WebServer",
"authors": "Hristo Gochkov", "authors": "Hristo Gochkov",
"frameworks": "arduino" "frameworks": "arduino"
@ -26,7 +23,7 @@
"frameworks": "arduino" "frameworks": "arduino"
} }
], ],
"version": "1.6.0", "version": "1.6.1",
"frameworks": "arduino", "frameworks": "arduino",
"platforms": "*" "platforms": "*"
} }

View File

@ -1,5 +1,5 @@
name=ESPUI name=ESPUI
version=1.6.0 version=1.6.1
author=Lukas Bachschwell author=Lukas Bachschwell
maintainer=Lukas Bachschwell <lukas@lbsfilm.at> maintainer=Lukas Bachschwell <lukas@lbsfilm.at>
sentence=ESP32 and ESP8266 Web Interface Library sentence=ESP32 and ESP8266 Web Interface Library

View File

@ -492,6 +492,16 @@ void ESPUIClass::updateSlider(int id, int nValue, int clientId) {
} }
} }
void ESPUIClass::updateSlider(String label, int nValue, int clientId) {
if (!labelExists(label)) {
if (DEBUG_ESPUI)
Serial.println("UI ERROR: Element does not " + String(label) +
" exist, cannot update!");
return;
}
updateSlider(getIdByLabel(label), nValue, clientId);
}
void ESPUIClass::updateSwitcher(int id, bool nValue, int clientId) { void ESPUIClass::updateSwitcher(int id, bool nValue, int clientId) {
if (id < cIndex && controls[id]->type == UI_SWITCHER) { if (id < cIndex && controls[id]->type == UI_SWITCHER) {
controls[id]->value = nValue ? 1 : 0; controls[id]->value = nValue ? 1 : 0;
@ -603,28 +613,48 @@ bool ESPUIClass::labelExists(String label) {
return false; return false;
} }
// Convert & Transfer Arduino elements to JSON elements /*
Convert & Transfer Arduino elements to JSON elements
Initially this function used to send the control element data individually.
Due to a change in the ESPAsyncWebserver library this had top be changed to be
sent as one blob at the beginning. Therefore a new type is used as well
*/
void ESPUIClass::jsonDom(AsyncWebSocketClient *client) { void ESPUIClass::jsonDom(AsyncWebSocketClient *client) {
String json;
DynamicJsonBuffer jsonBuffer(2000);
JsonObject &root = jsonBuffer.createObject();
root["type"] = UI_INITIAL_GUI;
JsonArray &items = jsonBuffer.createArray();
for (int i = -1; i < cIndex; i++) { for (int i = -1; i < cIndex; i++) {
String json; JsonObject &item = jsonBuffer.createObject();
StaticJsonBuffer<200> jsonBuffer;
JsonObject &root = jsonBuffer.createObject();
if (i == -1) { if (i == -1) {
root["type"] = UI_TITEL; item["type"] = UI_TITEL;
root["label"] = String(ui_title); item["label"] = String(ui_title);
} else { } else {
root["type"] = controls[i]->type; item["type"] = controls[i]->type;
root["label"] = String(controls[i]->label); item["label"] = String(controls[i]->label);
root["value"] = String(controls[i]->value); item["value"] = String(controls[i]->value);
root["color"] = String(controls[i]->color); item["color"] = String(controls[i]->color);
root["id"] = String(i); item["id"] = String(i);
} }
root.printTo(json); items.add(item);
client->text(json);
} }
// Send as one big bunch
root["controls"] = items;
root.printTo(json);
client->text(json);
} }
void ESPUIClass::beginSPIFFS(const char *_title) { void ESPUIClass::beginSPIFFS(const char *_title) {
begin(_title, NULL, NULL);
basicAuth = false;
}
void ESPUIClass::beginSPIFFS(const char *_title, const char *username,
const char *password) {
ui_title = _title; ui_title = _title;
server = new AsyncWebServer(80); server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/ws"); ws = new AsyncWebSocket("/ws");
@ -646,10 +676,29 @@ void ESPUIClass::beginSPIFFS(const char *_title) {
ws->onEvent(onWsEvent); ws->onEvent(onWsEvent);
server->addHandler(ws); server->addHandler(ws);
server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
if (basicAuth && username != NULL && password != NULL) {
basicAuthPassword = password;
basicAuthUsername = username;
basicAuth = true;
if (WS_AUTHENTICATION)
ws->setAuthentication(this->basicAuthUsername, this->basicAuthPassword);
server->serveStatic("/", SPIFFS, "/")
.setDefaultFile("index.htm")
.setAuthentication(ESPUI.basicAuthUsername, ESPUI.basicAuthPassword);
} else if (basicAuth) {
Serial.println(
"Could not enable BasicAuth: Username or password are not set");
} else {
server->serveStatic("/", SPIFFS, "/").setDefaultFile("index.htm");
}
// Heap for general Servertest // Heap for general Servertest
server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
request->send(200, "text/plain", request->send(200, "text/plain",
String(ESP.getFreeHeap()) + " In SPIFFSmode"); String(ESP.getFreeHeap()) + " In SPIFFSmode");
}); });
@ -657,26 +706,50 @@ void ESPUIClass::beginSPIFFS(const char *_title) {
server->onNotFound( server->onNotFound(
[](AsyncWebServerRequest *request) { request->send(404); }); [](AsyncWebServerRequest *request) { request->send(404); });
server->on("/zepto.js", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse_P(
200, "application/javascript", JS_ZEPTO_GZIP, sizeof(JS_ZEPTO_GZIP));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
server->begin(); server->begin();
if (DEBUG_ESPUI) Serial.println("UI Initialized"); if (DEBUG_ESPUI) Serial.println("UI Initialized");
} }
void ESPUIClass::begin(const char *_title) { void ESPUIClass::begin(const char *_title) {
begin(_title, NULL, NULL);
basicAuth = false;
}
void ESPUIClass::begin(const char *_title, const char *username,
const char *password) {
if (basicAuth && username != NULL && password != NULL) {
basicAuthPassword = password;
basicAuthUsername = username;
basicAuth = true;
} else if (basicAuth) {
Serial.println(
"Could not enable BasicAuth: Username or password are not set");
}
ui_title = _title; ui_title = _title;
server = new AsyncWebServer(80); server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/ws"); ws = new AsyncWebSocket("/ws");
ws->onEvent(onWsEvent); ws->onEvent(onWsEvent);
server->addHandler(ws); server->addHandler(ws);
if (basicAuth && username != NULL && password != NULL) {
basicAuthPassword = password;
basicAuthUsername = username;
basicAuth = true;
if (WS_AUTHENTICATION)
ws->setAuthentication(this->basicAuthUsername, this->basicAuthPassword);
} else if (basicAuth) {
Serial.println(
"Could not enable BasicAuth: Username or password are not set");
}
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
AsyncWebServerResponse *response = AsyncWebServerResponse *response =
request->beginResponse_P(200, "text/html", HTML_INDEX); request->beginResponse_P(200, "text/html", HTML_INDEX);
request->send(response); request->send(response);
@ -685,6 +758,9 @@ void ESPUIClass::begin(const char *_title) {
// Javascript files // Javascript files
server->on("/js/zepto.min.js", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/js/zepto.min.js", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
AsyncWebServerResponse *response = request->beginResponse_P( AsyncWebServerResponse *response = request->beginResponse_P(
200, "application/javascript", JS_ZEPTO_GZIP, sizeof(JS_ZEPTO_GZIP)); 200, "application/javascript", JS_ZEPTO_GZIP, sizeof(JS_ZEPTO_GZIP));
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
@ -692,6 +768,9 @@ void ESPUIClass::begin(const char *_title) {
}); });
server->on("/js/controls.js", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/js/controls.js", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
AsyncWebServerResponse *response = AsyncWebServerResponse *response =
request->beginResponse_P(200, "application/javascript", request->beginResponse_P(200, "application/javascript",
JS_CONTROLS_GZIP, sizeof(JS_CONTROLS_GZIP)); JS_CONTROLS_GZIP, sizeof(JS_CONTROLS_GZIP));
@ -700,6 +779,9 @@ void ESPUIClass::begin(const char *_title) {
}); });
server->on("/js/slider.js", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/js/slider.js", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
AsyncWebServerResponse *response = request->beginResponse_P( AsyncWebServerResponse *response = request->beginResponse_P(
200, "application/javascript", JS_SLIDER_GZIP, sizeof(JS_SLIDER_GZIP)); 200, "application/javascript", JS_SLIDER_GZIP, sizeof(JS_SLIDER_GZIP));
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
@ -709,6 +791,9 @@ void ESPUIClass::begin(const char *_title) {
// Stylesheets // Stylesheets
server->on("/css/style.css", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/css/style.css", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
AsyncWebServerResponse *response = request->beginResponse_P( AsyncWebServerResponse *response = request->beginResponse_P(
200, "text/css", CSS_STYLE_GZIP, sizeof(CSS_STYLE_GZIP)); 200, "text/css", CSS_STYLE_GZIP, sizeof(CSS_STYLE_GZIP));
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
@ -717,6 +802,9 @@ void ESPUIClass::begin(const char *_title) {
server->on( server->on(
"/css/normalize.css", HTTP_GET, [](AsyncWebServerRequest *request) { "/css/normalize.css", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
AsyncWebServerResponse *response = request->beginResponse_P( AsyncWebServerResponse *response = request->beginResponse_P(
200, "text/css", CSS_NORMALIZE_GZIP, sizeof(CSS_NORMALIZE_GZIP)); 200, "text/css", CSS_NORMALIZE_GZIP, sizeof(CSS_NORMALIZE_GZIP));
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
@ -725,6 +813,9 @@ void ESPUIClass::begin(const char *_title) {
// Heap for general Servertest // Heap for general Servertest
server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) { server->on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
if (ESPUI.basicAuth && !request->authenticate(ESPUI.basicAuthUsername,
ESPUI.basicAuthPassword))
return request->requestAuthentication();
request->send(200, "text/plain", request->send(200, "text/plain",
String(ESP.getFreeHeap()) + " In Memorymode"); String(ESP.getFreeHeap()) + " In Memorymode");
}); });

View File

@ -2,6 +2,7 @@
#define ESPUI_h #define ESPUI_h
#define DEBUG_ESPUI true #define DEBUG_ESPUI true
#define WS_AUTHENTICATION false
#include "Arduino.h" #include "Arduino.h"
#include "ArduinoJson.h" #include "ArduinoJson.h"
@ -40,6 +41,7 @@ typedef struct Control {
} Control; } Control;
// Message Types (and control types) // Message Types (and control types)
#define UI_INITIAL_GUI 100
#define UI_TITEL 0 #define UI_TITEL 0
#define UI_LABEL 1 #define UI_LABEL 1
@ -100,8 +102,12 @@ typedef struct Control {
class ESPUIClass { class ESPUIClass {
public: public:
void begin(const char *_title); // Setup servers and page in Memorymode void begin(const char *_title); // Setup servers and page in Memorymode
void begin(const char *_title, const char *username, const char *password);
void beginSPIFFS(const char *_title); // Setup servers and page in SPIFFSmode void beginSPIFFS(const char *_title); // Setup servers and page in SPIFFSmode
void beginSPIFFS(const char *_title, const char *username,
const char *password);
void prepareFileSystem(); // Initially preps the filesystem and loads a lot void prepareFileSystem(); // Initially preps the filesystem and loads a lot
// of stuff into SPIFFS // of stuff into SPIFFS
@ -159,6 +165,9 @@ class ESPUIClass {
bool labelExists(String label); bool labelExists(String label);
private: private:
const char *basicAuthUsername;
const char *basicAuthPassword;
bool basicAuth = true;
AsyncWebServer *server; AsyncWebServer *server;
AsyncWebSocket *ws; AsyncWebSocket *ws;
}; };

File diff suppressed because one or more lines are too long

View File

@ -1,42 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
### import always available modules from jsmin import jsmin as jsminify
from htmlmin import minify as htmlminify
from csscompressor import compress as cssminify
import gzip
import sys import sys
import os.path import os.path
import argparse import argparse
import re import re
from glob import glob from glob import glob
### import not always installed modules
missing = []
try:
from jsmin import jsmin as jsminify
except ModuleNotFoundError as e:
missing.append(e)
try:
from htmlmin import minify as htmlminify
except ModuleNotFoundError as e:
missing.append(e)
try:
from csscompressor import compress as cssminify
except ModuleNotFoundError as e:
missing.append(e)
try:
import gzip
except ModuleNotFoundError as e:
missing.append(e)
if len(missing) > 0:
# ERROR: at least one module is missing
for m in missing:
print("Cannot find module '%s'." % m.name)
print("Can't find %s required python module%s. Please install %s. If you're not sure how, a web search" % (len(missing), "s" if len(missing) > 1 else "", "them" if len(missing) > 1 else "it"))
print("for 'python', your operating system/python distribution and 'install modules' should help.")
print("For most people on unix-y systems, this line should work (possibly w/o the '3'):\n pip3 install %s" % " ".join(m.name for m in missing))
print("Here's the long documentation: https://packaging.python.org/tutorials/installing-packages/")
sys.exit(0)
### String template for C header files
TARGET_TEMPLATE = '''const char {constant}[] PROGMEM = R"=====( TARGET_TEMPLATE = '''const char {constant}[] PROGMEM = R"=====(
{minidata} {minidata}
)====="; )=====";
@ -44,9 +17,7 @@ TARGET_TEMPLATE = '''const char {constant}[] PROGMEM = R"=====(
const uint8_t {constant}_GZIP[{gziplen}] PROGMEM = {{ {gzipdata} }}; const uint8_t {constant}_GZIP[{gziplen}] PROGMEM = {{ {gzipdata} }};
''' '''
def parse_arguments(args=None): def parse_arguments(args=None):
""" Command line argument parser definitions """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Prepares ESPUI header files by minifying and gzipping HTML, JS and CSS source files.") description="Prepares ESPUI header files by minifying and gzipping HTML, JS and CSS source files.")
parser.add_argument("--auto", "--all", "-a", dest="auto", action="store_true", parser.add_argument("--auto", "--all", "-a", dest="auto", action="store_true",
@ -65,20 +36,6 @@ def parse_arguments(args=None):
return args return args
def get_context(infile, outfile): def get_context(infile, outfile):
""" This function creates a 'context object': a dictionary containing all the data needed.
Dictionary members:
infile: Full path to the input file or directory as given or autodetected
indir: Full path to the infile parent.
dir: Full path to the input directory tree (indir or parent of indir).
name: Filename of infile excluding extension (i.e. filename up until the first dot)
type: Lowercase extension of infile; one of: html, js, css.
outfile: Full path to the output file or directory as given or autodetected.
outdir: Full path to output directory.
outfilename: Filename of outfile.
minifile: Full path and filename of the intermediary minified file.
constant: C header file constant name derived from infile.
If infile == minifile, the input file already is minified (contains ".min.")
"""
infile = os.path.realpath(infile) infile = os.path.realpath(infile)
dir, name, type = (os.path.basename(os.path.dirname(infile)), os.path.basename(infile).split(os.path.extsep)[0], os.path.basename(infile).split(os.path.extsep)[-1] ) dir, name, type = (os.path.basename(os.path.dirname(infile)), os.path.basename(infile).split(os.path.extsep)[0], os.path.basename(infile).split(os.path.extsep)[-1] )
type = type.strip(".").lower() type = type.strip(".").lower()
@ -101,11 +58,6 @@ def get_context(infile, outfile):
return locals() return locals()
def perform_gzip(c): def perform_gzip(c):
""" Performs GZIP on c['minidata'].
The returned context object will contain additional fields:
gzipdata: Comma-separated string of decimal byte values representing gzipped data.
gziplen: Count of decimal byte values in gzipdata.
"""
compressed = gzip.compress(bytes(c['minidata'], 'utf-8')) compressed = gzip.compress(bytes(c['minidata'], 'utf-8'))
c['gzipdata'] = ','.join([ str(b) for b in compressed ]) c['gzipdata'] = ','.join([ str(b) for b in compressed ])
c['gziplen'] = len(compressed) c['gziplen'] = len(compressed)
@ -113,9 +65,6 @@ def perform_gzip(c):
return c return c
def perform_minify(c): def perform_minify(c):
""" Performs minification on c['infile'].
The returned context object contains the additional field minidata: A string of minified file contents.
"""
with open(c['infile']) as infile: with open(c['infile']) as infile:
minifier = { minifier = {
'css': cssminify, 'css': cssminify,
@ -124,27 +73,19 @@ def perform_minify(c):
}.get(c['type']) or htmlminify }.get(c['type']) or htmlminify
print(" Using %s minifier" % c['type']) print(" Using %s minifier" % c['type'])
c['minidata'] = minifier(infile.read()) c['minidata'] = minifier(infile.read())
return c return perform_gzip(c)
def process_file(infile, outdir, storemini=True): def process_file(infile, outdir, storemini=True):
""" Processes one file """
print("Processing file %s" % infile) print("Processing file %s" % infile)
# Evaluate file and target context
c = get_context(infile, outdir) c = get_context(infile, outdir)
# Minify file data
c = perform_minify(c) c = perform_minify(c)
# Gzip minified data
c = perform_gzip(c)
if storemini: if storemini:
# Write intermediary minified file
if c['infile'] == c['minifile']: if c['infile'] == c['minifile']:
print(" Original file is already minified, refusing to overwrite it") print(" Original file is already minified, refusing to overwrite it")
else: else:
print(" Writing minified file %s" % c['minifile']) print(" Writing minified file %s" % c['minifile'])
with open(c['minifile'], 'w+') as minifile: with open(c['minifile'], 'w+') as minifile:
minifile.write(c['minidata']) minifile.write(c['minidata'])
# Write minified and gzipped data to C header file
with open(c['outfile'], 'w+') as outfile: with open(c['outfile'], 'w+') as outfile:
print(" Using C constant names %s and %s_GZIP" % (c['constant'], c['constant'])) print(" Using C constant names %s and %s_GZIP" % (c['constant'], c['constant']))
print(" Writing C header file %s" % c['outfile']) print(" Writing C header file %s" % c['outfile'])
@ -154,10 +95,6 @@ def filenamefilter(pattern, strings):
return filter(re.compile(pattern).search, strings) return filter(re.compile(pattern).search, strings)
def process_dir(sourcedir, outdir, recursive=True, storemini=True): def process_dir(sourcedir, outdir, recursive=True, storemini=True):
""" Processes a directory tree, recursively. Calls process_file on each HTML/CSS/JS file found.
Skips intermediary minified files. Standalone minified files (i.e. files containing ".min." that
do not have a full version) are processed without minifying again.
"""
pattern = r'/*\.(css|js|htm|html)$' pattern = r'/*\.(css|js|htm|html)$'
files = glob(sourcedir + "/**/*", recursive=True)+glob(sourcedir + "/*") if recursive else glob(sourcedir + "/*") files = glob(sourcedir + "/**/*", recursive=True)+glob(sourcedir + "/*") if recursive else glob(sourcedir + "/*")
files = filenamefilter(pattern, files) files = filenamefilter(pattern, files)
@ -168,7 +105,6 @@ def process_dir(sourcedir, outdir, recursive=True, storemini=True):
process_file(f, outdir, storemini) process_file(f, outdir, storemini)
def check_args(args): def check_args(args):
""" Checks argumental sanity and exits if the arguments are insane. """
abort = 0 abort = 0
if not os.path.exists(args.sources): if not os.path.exists(args.sources):
print("ERROR: Source %s does not exist" % args.sources) print("ERROR: Source %s does not exist" % args.sources)
@ -184,12 +120,8 @@ def check_args(args):
sys.exit(abort) sys.exit(abort)
def main(args): def main(args):
""" main entry point. """
# default source if not given: realpath(../examples/gui/data)
args.sources = os.path.realpath(args.sources or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "examples", "gui", "data"))) args.sources = os.path.realpath(args.sources or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "examples", "gui", "data")))
# default target if not given: realpath(../src)
args.target = os.path.realpath(args.target or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "src"))) args.target = os.path.realpath(args.target or os.sep.join((os.path.dirname(os.path.realpath(__file__)), "..", "src")))
# check arguments
check_args(args) check_args(args)
if os.path.isfile(args.sources): if os.path.isfile(args.sources):
print("Source %s is a file, will process one file only." % args.sources) print("Source %s is a file, will process one file only." % args.sources)