383 lines
13 KiB
C++
383 lines
13 KiB
C++
#include "config.h"
|
|
|
|
#ifdef HTTP_SERVER_ENABLE
|
|
|
|
#include "http_server.h"
|
|
#include "effects.h"
|
|
#include "my_wifi.h"
|
|
#include "functions.h"
|
|
#include "prototypes.h"
|
|
#include <FS.h>
|
|
|
|
AsyncWebServer http_server(HTTP_SERVER_PORT);
|
|
AsyncWebSocket ws("/ws");
|
|
uint32_t monitor_client = 0;
|
|
|
|
void http_server_handle_file_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) {
|
|
File upload_file;
|
|
if (index == 0) { // Start of upload
|
|
LOGln("HTTP * Upload of %s starting...", filename.c_str());
|
|
upload_file = SPIFFS.open(filename, "w");
|
|
} else {
|
|
upload_file = SPIFFS.open(filename, "a");
|
|
}
|
|
|
|
upload_file.write(data, len);
|
|
|
|
if (final) {
|
|
LOGln("HTTP * Upload of %s done.", filename.c_str());
|
|
}
|
|
}
|
|
|
|
void ws_send_effects(AsyncWebSocketClient* client) {
|
|
String msg = "{\"effects\": [";
|
|
for (int i=0; i<effects_size; i++) {
|
|
if (i>0) msg += ", ";
|
|
msg += '"';
|
|
msg += effects[i].name;
|
|
msg += '"';
|
|
}
|
|
msg += "]}";
|
|
client->text(msg);
|
|
}
|
|
|
|
void ws_send_settings(AsyncWebSocketClient* client) {
|
|
String msg = "{\"settings\": [";
|
|
for (int i=0; i<all_settings_size; i++) {
|
|
if (i>0) msg += ", ";
|
|
msg += "{\"name\":\"";
|
|
msg += all_settings[i].name;
|
|
msg += "\",\"value\":";
|
|
msg += *(all_settings[i].value);
|
|
msg += ",\"type\":\"";
|
|
switch (all_settings[i].type) {
|
|
case TYPE_UINT8: msg += "uint8"; break;
|
|
case TYPE_UINT16: msg += "uint16"; break;
|
|
case TYPE_BOOL: msg += "bool"; break;
|
|
default: msg += "unknown";
|
|
}
|
|
msg += "\"}";
|
|
}
|
|
msg += "]}";
|
|
client->text(msg);
|
|
}
|
|
|
|
void ws_set_setting(String s) {
|
|
int8_t index = s.indexOf(":");
|
|
if (index < 1) return;
|
|
String key = s.substring(0, index);
|
|
String value = s.substring(index+1);
|
|
change_setting(key.c_str(), value.toInt());
|
|
}
|
|
|
|
void handle_ws(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) {
|
|
if (type == WS_EVT_CONNECT) {
|
|
LOGln("Websocket * Client connected. ID: %d", client->id());
|
|
} else if (type == WS_EVT_DISCONNECT) {
|
|
if (monitor_client == client->id()) monitor_client=0;
|
|
LOGln("Websocket * Client disconnected.");
|
|
} else if (type == WS_EVT_DATA) {
|
|
AwsFrameInfo* info = (AwsFrameInfo*)arg;
|
|
if (info->opcode == WS_TEXT) {
|
|
data[len] = 0;
|
|
String msg = String((char*)data);
|
|
LOGln("Websocket * In: %s", msg.c_str());
|
|
if (msg.startsWith("effect:")) {
|
|
change_current_effect(msg.substring(7));
|
|
} else if (msg.equals("effects?")) {
|
|
ws_send_effects(client);
|
|
} else if (msg.equals("settings?")) {
|
|
ws_send_settings(client);
|
|
} else if (msg.startsWith("setting:")) {
|
|
ws_set_setting(msg.substring(8));
|
|
} else if (msg.equals("monitor:1")) {
|
|
monitor_client = client->id();
|
|
} else if (msg.equals("monitor:0")) {
|
|
if (monitor_client == client->id()) monitor_client=0;
|
|
} else {
|
|
client->text("Unknown command. Accepted commands:\neffects?\nsettings?\nsetting:<key>:<value>\neffect:<effect>\nmonitor:<0/1>");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void http_server_setup() {
|
|
static const char* PROGMEM text_plain = "text/plain";
|
|
|
|
http_server.on("/", HTTP_GET, [&](AsyncWebServerRequest* request){
|
|
LOGln("HTTP * GET /");
|
|
String response = String(F("<html><head><title>Pitrix</title></head><body><h1>Pitrix</h1><p><a href='/settings'>Settings</a></p><p><a href='/effects'>Effect</a></p><p>Known animations:</p>"));
|
|
if (!SPIFFS.begin()) {
|
|
response += F("<strong>No SPIFFS file system found.</strong>");
|
|
} else {
|
|
response += F("<ul>");
|
|
Dir dir = SPIFFS.openDir("/");
|
|
while (dir.next()) {
|
|
char buffer[100];
|
|
snprintf_P(buffer, 100, PSTR("<li>%s (<a href='/delete?%s'>delete</a>)</li>"), dir.fileName().c_str(), dir.fileName().c_str());
|
|
response += buffer;
|
|
}
|
|
response += F("</ul>");
|
|
response += F("<form action='/upload' method='POST'><input type='file' name='file' /><input type='submit' value='Upload file' /></form>");
|
|
}
|
|
response += F("</body></html>");
|
|
request->send(200, "text/html", response);
|
|
});
|
|
http_server.on("/settings", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
|
String message = F("<html><head><title>Pitrix settings</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>\n");
|
|
for (int i=0; i<all_settings_size; i++) {
|
|
Setting s = all_settings[i];
|
|
uint16_t default_value = setting_default(&s);
|
|
uint16_t value = *(s.value);
|
|
|
|
message += F("<tr><td>");
|
|
if (default_value != value) {
|
|
message += F("<strong>");
|
|
}
|
|
message += s.name;
|
|
if (default_value != value) {
|
|
message += F("<strong>");
|
|
}
|
|
message += F("</td><td>");
|
|
message += value;
|
|
if (default_value != value) {
|
|
message += " (";
|
|
message += default_value;
|
|
message += ")";
|
|
}
|
|
char buffer[150];
|
|
snprintf_P(buffer, 150, PSTR("</td><td><form method='POST' action='/settings?redir=1'><input type='hidden' name='key' value='%s'/>"), s.name);
|
|
message += buffer;
|
|
if (s.type==TYPE_UINT8 || s.type==TYPE_UINT16) {
|
|
snprintf_P(buffer, 150, PSTR("<input type='number' name='value' value='%d' min='0' max='%d' />"), value, s.type==TYPE_UINT8 ? 255 : 65535);
|
|
} else if (s.type==TYPE_BOOL) {
|
|
snprintf_P(buffer, 150, PSTR("<input type='radio' name='value' value='0' %s> Off / <input type='radio' name='value' value='1' %s> On"), value==0?"checked":"", value==1?"checked":"");
|
|
}
|
|
message += buffer;
|
|
message += F("<input type='submit' value='Save' /></form></td></tr>\n");
|
|
}
|
|
message += F("</table></body></html>");
|
|
request->send(200, "text/html", message);
|
|
});
|
|
http_server.on("/settings", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
|
if (!request->hasParam("key", true) || !request->hasParam("value", true)) {
|
|
request->send(400, "text/plain", "Missing argument.");
|
|
return;
|
|
}
|
|
String name = request->getParam("key", true)->value();
|
|
uint16_t value = request->getParam("value", true)->value().toInt();
|
|
|
|
if (change_setting(name.c_str(), value)) {
|
|
if (request->hasParam("redir")) {
|
|
request->redirect("/settings");
|
|
} else {
|
|
request->send(200, "text/plain", "OK");
|
|
}
|
|
save_settings();
|
|
} else {
|
|
request->send(400, "text/plain", "Could not change setting.");
|
|
}
|
|
});
|
|
http_server.on("/settings/load", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
|
load_settings();
|
|
request->send(200, "text/plain", "OK");
|
|
});
|
|
http_server.on("/settings/save", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
|
save_settings();
|
|
request->send(200, "text/plain", "OK");
|
|
});
|
|
http_server.on("/settings.txt", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
|
File f;
|
|
if (SPIFFS.begin()) {
|
|
f=SPIFFS.open("/pitrix_settings.txt", "r");
|
|
if (f) {
|
|
String s = f.readString();
|
|
f.close();
|
|
request->send(200, "text/plain", s);
|
|
return;
|
|
}
|
|
}
|
|
request->send(500, "text/plain", "Could not read settings.");
|
|
});
|
|
http_server.on("/effects", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
|
String message = F("<html><head><title>Pitrix effects</title></head><body><h1>Pitrix settings</h1><a href='/'>Back to main page</a><table>");
|
|
char buffer[500];
|
|
for (int i=0; i<effects_size; i++) {
|
|
snprintf_P(buffer, 500, PSTR("<tr><td>%s</td><td><form method='post' action='/effects'><input type='hidden' name='name' value='%s'><input type='hidden' name='redir' value='1'><input type='submit' value='Select'></form></td></tr>"), effects[i].name, effects[i].name);
|
|
message += buffer;
|
|
}
|
|
message += F("</table></body></html>");
|
|
request->send(200, "text/html", message);
|
|
});
|
|
http_server.on("/effects", HTTP_POST, [&](AsyncWebServerRequest* request) {
|
|
if (!request->hasParam("name", true)) {
|
|
request->send(400, "text/plain", "Missing argument 'name'.");
|
|
return;
|
|
}
|
|
String name = request->getParam("name", true)->value();
|
|
if (change_current_effect(name)) {
|
|
if (request->hasParam("redir")) {
|
|
request->redirect("/effects");
|
|
} else {
|
|
request->send(200, "text/plain", "OK");
|
|
}
|
|
} else {
|
|
request->send(404, "text/plain", "Effect not found.");
|
|
}
|
|
});
|
|
http_server.on("/delete", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
|
LOGln("HTTP * GET /delete");
|
|
if (request->params()==0) {
|
|
request->send_P(400, text_plain, PSTR("No filename given"));
|
|
return;
|
|
}
|
|
String file = request->getParam(0)->value();
|
|
if (file == "/") {
|
|
request->send_P(400, text_plain, PSTR("Invalid path"));
|
|
return;
|
|
}
|
|
if (!SPIFFS.exists(file)) {
|
|
request->send_P(400, text_plain, PSTR("File does not exist."));
|
|
return;
|
|
}
|
|
SPIFFS.remove(file);
|
|
request->send_P(200, text_plain, PSTR("OK"));
|
|
});
|
|
http_server.on("/upload", HTTP_POST, [](AsyncWebServerRequest* request) {
|
|
request->send(200);
|
|
}, http_server_handle_file_upload);
|
|
http_server.on("/free_heap", HTTP_GET, [&](AsyncWebServerRequest* request){
|
|
LOGln("HTTP * GET /free_heap");
|
|
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
|
});
|
|
http_server.on("/uptime", HTTP_GET, [&](AsyncWebServerRequest* request){
|
|
LOGln("HTTP * GET /uptime");
|
|
request->send(200, "text/plain", String(millis()/1000));
|
|
});
|
|
http_server.on("/fps", HTTP_GET, [](AsyncWebServerRequest* request){
|
|
LOGln("HTTP * GET /fps");
|
|
request->send(200, "text/plain", String(FastLED.getFPS()));
|
|
});
|
|
http_server.on("/reboot", HTTP_POST, [](AsyncWebServerRequest* request){
|
|
LOGln("HTTP * POST /reboot");
|
|
ESP.restart();
|
|
});
|
|
http_server.on("/mode", HTTP_POST, [&](AsyncWebServerRequest* request){
|
|
LOGln("HTTP * POST /mode with value %s", request->getParam("plain", true)->value().c_str());
|
|
if (!request->hasParam("plain", true)) {
|
|
request->send_P(400, text_plain, PSTR("No effect given."));
|
|
return;
|
|
}
|
|
String val = request->getParam("plain", true)->value();
|
|
if (change_current_effect(val)) {
|
|
request->send(200, "text/plain", "OK");
|
|
} else {
|
|
request->send_P(400, text_plain, PSTR("Unknown effect."));
|
|
}
|
|
});
|
|
http_server.on("/monitor", HTTP_GET, [&](AsyncWebServerRequest* request) {
|
|
static const char* html PROGMEM = R"(
|
|
<html>
|
|
<head>
|
|
<title>Pitrix</title>
|
|
</head>
|
|
<body>
|
|
<p>
|
|
<button id='monitor_off'>Monitor OFF</button>
|
|
<button id='monitor_on'>Monitor ON</button>
|
|
</p>
|
|
<canvas id='target' width='800' height='500'></canvas>
|
|
<script type='text/javascript'>
|
|
width = height = 32;
|
|
active = false;
|
|
ctx = document.getElementById('target').getContext('2d');
|
|
socket = new WebSocket((document.location.protocol=='https:' ? 'wss' : 'ws') + '://' + document.location.host + '/ws');
|
|
socket.onopen = function() {
|
|
socket.send('effects?');
|
|
socket.send('settings?');
|
|
};
|
|
socket.binaryType = 'arraybuffer';
|
|
socket.onmessage = function(message) {
|
|
if ((typeof message.data) == 'string') {
|
|
var j = JSON.parse(message.data);
|
|
if (j.effects) {
|
|
console.log('Got effects.');
|
|
console.log(j.effects);
|
|
}
|
|
if (j.settings) {
|
|
console.log('Got settings.');
|
|
console.log(j.settings);
|
|
}
|
|
return;
|
|
}
|
|
if (!active) return;
|
|
var buffer = new Uint8Array(message.data);
|
|
width = buffer[0];
|
|
height = buffer[1];
|
|
if (buffer[2] != 255) return;
|
|
var offset = 3;
|
|
ctx.fillStyle = '#000000';
|
|
ctx.fillRect(0, 0, 20*width, 20*height);
|
|
for (var y=0; y<height; y++) for (var x=0; x<width; x++) {
|
|
var r = buffer[offset + 0];
|
|
var g = buffer[offset + 1];
|
|
var b = buffer[offset + 2];
|
|
offset = offset + 3;
|
|
ctx.fillStyle = 'rgb('+r+','+g+','+b+')';
|
|
ctx.fillRect(x*20+2, y*20+2, 16, 16);
|
|
}
|
|
};
|
|
document.getElementById('monitor_on').onclick = function() {
|
|
socket.send('monitor:1');
|
|
active = true;
|
|
};
|
|
document.getElementById('monitor_off').onclick = function() {
|
|
socket.send('monitor:0');
|
|
active = false;
|
|
ctx.fillStyle = '0x80808080';
|
|
ctx.fillRect(0, 0, width*20, height*20);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|
|
)";
|
|
|
|
request->send_P(200, PSTR("text/html"), html);
|
|
});
|
|
|
|
|
|
ws.onEvent(handle_ws);
|
|
http_server.addHandler(&ws);
|
|
|
|
|
|
http_server.begin();
|
|
|
|
MDNS.addService("_http", "_tcp", 80);
|
|
}
|
|
|
|
void http_server_send_framedata() {
|
|
if (ws.count()>0 && monitor_client>0) {
|
|
if (ws.hasClient(monitor_client)) {
|
|
uint16_t _size = LED_WIDTH * LED_HEIGHT * 3 + 4;
|
|
uint8_t* _buffer = new uint8_t[_size];
|
|
_buffer[0] = LED_WIDTH;
|
|
_buffer[1] = LED_HEIGHT;
|
|
_buffer[2] = 255;
|
|
for (uint8_t y=0; y<LED_HEIGHT; y++) for(uint8_t x=0; x<LED_WIDTH; x++) {
|
|
uint16_t index = XYsafe(x, y);
|
|
CRGB pixel = leds[index];
|
|
_buffer[3 + (y*LED_WIDTH + x)*3 + 0] = (pixel.r==255 ? 254 : pixel.r);
|
|
_buffer[3 + (y*LED_WIDTH + x)*3 + 1] = (pixel.g==255 ? 254 : pixel.g);
|
|
_buffer[3 + (y*LED_WIDTH + x)*3 + 2] = (pixel.b==255 ? 254 : pixel.b);
|
|
}
|
|
_buffer[_size - 1] = 255;
|
|
ws.binary(monitor_client, _buffer, _size);
|
|
delete _buffer;
|
|
} else {
|
|
monitor_client = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|