ESP3D/embedded/config/server.js
Luc da4190ec8b Add embedded favicon.ico scripts and support
Add WebSocket Subprotocol support per @terjeio suggestion
Change ESP800 report to use FlashFileSystem instead of FileSystem  per @terjeio suggestion for consistency
Add Host Path support for files hosted in subdirectory (WIP)
Add createPath flag for upload to create full path if does not exists
Update embedded page to support new ESP800 flags
Update WebSocket library to avoid warning
Update Features.md
Bump platformIO to 5.1.0
Bumb version to 204
2022-08-10 09:31:23 +08:00

677 lines
18 KiB
JavaScript

const express = require("express");
const chalk = require("chalk");
let path = require("path");
const fs = require("fs");
const port = 8080;
/*
* Web Server for development
* Web Socket server for development
*/
const wscolor = chalk.cyan;
const expresscolor = chalk.green;
const commandcolor = chalk.white;
const WebSocket = require("ws");
let currentID = 0;
const app = express();
const fileUpload = require("express-fileupload");
let serverpath = path.normalize(__dirname + "/../server/public/");
let sdpath = path.normalize(__dirname + "/../server/sd/");
let WebSocketServer = require("ws").Server,
wss = new WebSocketServer({ port: 81,handleProtocols:function(protocol) {console.log( "protocol received from client " + protocol );
return "webui-v3";
return null;}});
app.use(fileUpload({ preserveExtension: true, debug: false }));
app.listen(port, () =>
console.log(expresscolor(`[express] Listening on port ${port}!`))
);
//app.use(express.urlencoded({ extended: false }));
function SendBinary(text) {
const array = new Uint8Array(text.length);
for (let i = 0; i < array.length; ++i) {
array[i] = text.charCodeAt(i);
}
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(array);
}
});
}
app.post("/login", function (req, res) {
res.send("");
return;
});
app.get("/config", function (req, res) {
res.send(
"chip id: 56398\nCPU Freq: 240 Mhz<br/>" +
"CPU Temp: 58.3 C<br/>" +
"free mem: 212.36 KB<br/>" +
"SDK: v3.2.3-14-gd3e562907<br/>" +
"flash size: 4.00 MB<br/>" +
"size for update: 1.87 MB<br/>" +
"FS type: LittleFS<br/>" +
"FS usage: 104.00 KB/192.00 KB<br/>" +
"baud: 115200<br/>" +
"sleep mode: none<br/>" +
"wifi: ON<br/>" +
"hostname: esp3d<br/>" +
"HTTP port: 80<br/>" +
"Telnet port: 23<br/>" +
"WebDav port: 8383<br/>" +
"sta: ON<br/>" +
"mac: 80:7D:3A:C4:4E:DC<br/>" +
"SSID: WIFI_OFFICE_A2G<br/>" +
"signal: 100 %<br/>" +
"phy mode: 11n<br/>" +
"channel: 11<br/>" +
"ip mode: dhcp<br/>" +
"ip: 192.168.1.61<br/>" +
"gw: 192.168.1.1<br/>" +
"msk: 255.255.255.0<br/>" +
"DNS: 192.168.1.1<br/>" +
"ap: OFF<br/>" +
"mac: 80:7D:3A:C4:4E:DD<br/>" +
"serial: ON<br/>" +
"notification: OFF<br/>" +
"Target Fw: repetier<br/>" +
"FW ver: 3.0.0.a91<br/>" +
"FW arch: ESP32 "
);
return;
});
app.get("/command", function (req, res) {
console.log(commandcolor(`[server]/command params: ${req.query.cmd}`));
let url = req.query.cmd;
if (url.startsWith("[ESP800]json")) {
res.json(
{
cmd: "800",
status: "ok",
data:{
FWVersion: "3.0.0.a28",
FWTarget: 40,
SDConnection: "none",
Authentication: "Disabled",
WebCommunication: "Synchronous",
WebSocketIP: "localhost",
WebSocketPort: "81",
WebSocketSubProtocol: "webui-v3",
Hostname: "esp3d",
WiFiMode: "STA",
WebUpdate: "Enabled",
FlashFileSystem: "LittleFs",
HostPath: "/",
Time: "none",
Cam_ID: "4",
Cam_name: "ESP32 Cam",
}
}
);
return;
}
if (url.indexOf("ESP111") != -1) {
res.send("192.168.1.111");
return;
}
if (url.indexOf("ESP420") != -1) {
res.json({
Status: [
{ id: "chip id", value: "38078" },
{ id: "CPU Freq", value: "240 Mhz" },
{ id: "CPU Temp", value: "50.6 C" },
{ id: "free mem", value: "217.50 KB" },
{ id: "SDK", value: "v3.3.1-61-g367c3c09c" },
{ id: "flash size", value: "4.00 MB" },
{ id: "size for update", value: "1.87 MB" },
{ id: "FS type", value: "SPIFFS" },
{ id: "FS usage", value: "39.95 KB/169.38 KB" },
{ id: "baud", value: "115200" },
{ id: "sleep mode", value: "none" },
{ id: "wifi", value: "ON" },
{ id: "hostname", value: "esp3d" },
{ id: "HTTP port", value: "80" },
{ id: "Telnet port", value: "23" },
{ id: "Ftp ports", value: "21, 20, 55600" },
{ id: "sta", value: "ON" },
{ id: "mac", value: "30:AE:A4:21:BE:94" },
{ id: "SSID", value: "WIFI_OFFICE_B2G" },
{ id: "signal", value: "100 %" },
{ id: "phy mode", value: "11n" },
{ id: "channel", value: "2" },
{ id: "ip mode", value: "dhcp" },
{ id: "ip", value: "192.168.1.43" },
{ id: "gw", value: "192.168.1.1" },
{ id: "msk", value: "255.255.255.0" },
{ id: "DNS", value: "192.168.1.1" },
{ id: "ap", value: "OFF" },
{ id: "mac", value: "30:AE:A4:21:BE:95" },
{ id: "serial", value: "ON" },
{ id: "notification", value: "OFF" },
{ id: "FW ver", value: "3.0.0.a28" },
{ id: "FW arch", value: "ESP32" },
],
});
return;
}
if (url.indexOf("ESP410") != -1) {
res.json({
AP_LIST: [
{
SSID: "HP-Setup>71-M277 LaserJet",
SIGNAL: "92",
IS_PROTECTED: "0",
},
{ SSID: "WIFI_OFFICE_B2G", SIGNAL: "88", IS_PROTECTED: "1" },
{ SSID: "NETGEAR70", SIGNAL: "66", IS_PROTECTED: "1" },
{ SSID: "ZenFone6&#39;luc", SIGNAL: "48", IS_PROTECTED: "1" },
{ SSID: "Livebox-EF01", SIGNAL: "20", IS_PROTECTED: "1" },
{ SSID: "orange", SIGNAL: "20", IS_PROTECTED: "0" },
],
});
return;
}
if (url.indexOf("ESP400") != -1) {
res.json({
Settings: [
{
F: "network/network",
P: "130",
T: "S",
V: "esp3d",
H: "hostname",
S: "32",
M: "1",
},
{
F: "network/network",
P: "0",
T: "B",
V: "1",
H: "radio mode",
O: [{ none: "0" }, { sta: "1" }, { ap: "2" }],
},
{
F: "network/sta",
P: "1",
T: "S",
V: "WIFI_OFFICE_B2G",
S: "32",
H: "SSID",
M: "1",
},
{
F: "network/sta",
P: "34",
T: "S",
N: "1",
V: "********",
S: "64",
H: "pwd",
M: "8",
},
{
F: "network/sta",
P: "99",
T: "B",
V: "1",
H: "ip mode",
O: [{ dhcp: "1" }, { static: "0" }],
},
{
F: "network/sta",
P: "100",
T: "A",
V: "192.168.0.1",
H: "ip",
},
{
F: "network/sta",
P: "108",
T: "A",
V: "192.168.0.1",
H: "gw",
},
{
F: "network/sta",
P: "104",
T: "A",
V: "255.255.255.0",
H: "msk",
},
{
F: "network/ap",
P: "218",
T: "S",
V: "ESP3D",
S: "32",
H: "SSID",
M: "1",
},
{
F: "network/ap",
P: "251",
T: "S",
N: "1",
V: "********",
S: "64",
H: "pwd",
M: "8",
},
{
F: "network/ap",
P: "316",
T: "A",
V: "192.168.0.1",
H: "ip",
},
{
F: "network/ap",
P: "118",
T: "B",
V: "11",
H: "channel",
O: [
{ 1: "1" },
{ 2: "2" },
{ 3: "3" },
{ 4: "4" },
{ 5: "5" },
{ 6: "6" },
{ 7: "7" },
{ 8: "8" },
{ 9: "9" },
{ 10: "10" },
{ 11: "11" },
{ 12: "12" },
{ 13: "13" },
{ 14: "14" },
],
},
{
F: "service/http",
P: "328",
T: "B",
V: "1",
H: "enable",
O: [{ no: "0" }, { yes: "1" }],
},
{
F: "service/http",
P: "121",
T: "I",
V: "80",
H: "port",
S: "65001",
M: "1",
},
{
F: "service/telnetp",
P: "329",
T: "B",
V: "1",
H: "enable",
O: [{ no: "0" }, { yes: "1" }],
},
{
F: "service/telnetp",
P: "125",
T: "I",
V: "23",
H: "port",
S: "65001",
M: "1",
},
{
F: "service/ftp",
P: "1021",
T: "B",
V: "1",
H: "enable",
O: [{ no: "0" }, { yes: "1" }],
},
{
F: "service/ftp",
P: "1009",
T: "I",
V: "21",
H: "control port",
S: "65001",
M: "1",
},
{
F: "service/ftp",
P: "1013",
T: "I",
V: "20",
H: "active port",
S: "65001",
M: "1",
},
{
F: "service/ftp",
P: "1017",
T: "I",
V: "55600",
H: "passive port",
S: "65001",
M: "1",
},
{
F: "service/notification",
P: "1004",
T: "B",
V: "1",
H: "auto notif",
O: [{ no: "0" }, { yes: "1" }],
},
{
F: "service/notification",
P: "116",
T: "B",
V: "0",
H: "notification",
O: [{ none: "0" }, { pushover: "1" }, { email: "2" }, { line: "3" }],
},
{
F: "service/notification",
P: "332",
T: "S",
V: "********",
S: "63",
H: "t1",
M: "0",
},
{
F: "service/notification",
P: "396",
T: "S",
V: "********",
S: "63",
H: "t2",
M: "0",
},
{
F: "service/notification",
P: "855",
T: "S",
V: " ",
S: "127",
H: "ts",
M: "0",
},
{
F: "system/system",
P: "461",
T: "B",
V: "40",
H: "targetfw",
O: [
{ repetier: "50" },
{ marlin: "20" },
{ marlinkimbra: "35" },
{ smoothieware: "40" },
{ grbl: "10" },
{ unknown: "0" },
],
},
{
F: "system/system",
P: "112",
T: "I",
V: "115200",
H: "baud",
O: [
{ 9600: "9600" },
{ 19200: "19200" },
{ 38400: "38400" },
{ 57600: "57600" },
{ 74880: "74880" },
{ 115200: "115200" },
{ 230400: "230400" },
{ 250000: "250000" },
{ 500000: "500000" },
{ 921600: "921600" },
],
},
{
F: "system/system",
P: "320",
T: "I",
V: "10000",
H: "bootdelay",
S: "40000",
M: "0",
},
{
F: "system/system",
P: "129",
T: "F",
V: "255",
H: "outputmsg",
O: [{ M117: "16" }, { serial: "1" }, { telnet: "2" }],
},
],
});
return;
}
SendBinary("ok\n");
res.send("");
});
function fileSizeString(size) {
let s;
if (size < 1024) return size + " B";
if (size < 1024 * 1024) return (size / 1024).toFixed(2) + " KB";
if (size < 1024 * 1024 * 1024)
return (size / (1024 * 1024)).toFixed(2) + " MB";
if (size < 1024 * 1024 * 1024 * 1024)
return (size / (1024 * 1024 * 1024)).toFixed(2) + " GB";
return "X B";
}
function filesList(mypath,mainpath) {
let res = '{"files":[';
let nb = 0;
let total = sdpath==mainpath? (4096 * 1024 * 1024):(1.2 * 1024 * 1024);
let totalused = getTotalSize(mainpath);
let currentpath = path.normalize(mainpath + mypath);
console.log("[path]" + currentpath);
fs.readdirSync(currentpath).forEach((fileelement) => {
let fullpath = path.normalize(currentpath + "/" + fileelement);
let fst = fs.statSync(fullpath);
let fsize = -1;
if (fst.isFile()) {
fsize = fileSizeString(fst.size);
}
if (nb > 0) res += ",";
res += '{"name":"' + fileelement + '","size":"' + fsize + '"}';
nb++;
});
res +=
'],"path":"' +
mypath +
'","occupation":"' +
((100 * totalused) / total).toFixed(0) +
'","status":"ok","total":"' +
fileSizeString(total) +
'","used":"' +
fileSizeString(totalused) +
'"}';
return res;
}
const getAllFiles = function (dirPath, arrayOfFiles) {
let files = fs.readdirSync(dirPath);
arrayOfFiles = arrayOfFiles || [];
files.forEach(function (file) {
if (fs.statSync(dirPath + "/" + file).isDirectory()) {
arrayOfFiles = getAllFiles(dirPath + "/" + file, arrayOfFiles);
} else {
arrayOfFiles.push(dirPath + "/" + file);
}
});
return arrayOfFiles;
};
const getTotalSize = function (directoryPath) {
const arrayOfFiles = getAllFiles(directoryPath);
let totalSize = 0;
arrayOfFiles.forEach(function (filePath) {
totalSize += fs.statSync(filePath).size;
});
return totalSize;
};
function deleteFolderRecursive(path) {
if (fs.existsSync(path) && fs.lstatSync(path).isDirectory()) {
fs.readdirSync(path).forEach(function (file, index) {
let curPath = path + "/" + file;
if (fs.lstatSync(curPath).isDirectory()) {
// recurse
deleteFolderRecursive(curPath);
} else {
// delete file
fs.unlinkSync(curPath);
}
});
console.log(`[server]Deleting directory "${path}"...`);
if (fs.existsSync(path)) fs.rmdirSync(path);
} else console.log(`[server]No directory "${path}"...`);
}
app.all("/updatefw", function (req, res) {
res.send("ok");
});
app.all("/sdfiles", function (req, res) {
let mypath = req.query.path;
let url = req.originalUrl;
let filepath = path.normalize(sdpath + mypath + "/" + req.query.filename);
if (url.indexOf("action=deletedir") != -1) {
console.log("[server]delete directory " + filepath);
deleteFolderRecursive(filepath);
fs.readdirSync(mypath);
} else if (url.indexOf("action=delete") != -1) {
fs.unlinkSync(filepath);
console.log("[server]delete file " + filepath);
}
if (url.indexOf("action=createdir") != -1) {
fs.mkdirSync(filepath);
console.log("[server]new directory " + filepath);
}
if (typeof mypath == "undefined") {
if (typeof req.body.path == "undefined") {
console.log("[server]path is not defined");
mypath = "/";
} else {
mypath = (req.body.path == "/" ? "" : req.body.path) + "/";
}
}
console.log("[server]path is " + mypath);
if (!req.files || Object.keys(req.files).length === 0) {
return res.send(filesList(mypath,sdpath));
}
let myFile = req.files.myfiles;
if (typeof myFile.length == "undefined") {
let fullpath = path.normalize(sdpath + mypath + myFile.name);
console.log("[server]one file:" + fullpath);
myFile.mv(fullpath, function (err) {
if (err) return res.status(500).send(err);
res.send(filesList(mypath,sdpath));
});
return;
} else {
console.log(myFile.length + " files");
for (let i = 0; i < myFile.length; i++) {
let fullpath = path.normalize(sdpath + mypath + myFile[i].name);
console.log(fullpath);
myFile[i].mv(fullpath).then(() => {
if (i == myFile.length - 1) res.send(filesList(mypath,sdpath));
});
}
}
});
app.all("/files", function (req, res) {
let mypath = req.query.path;
let url = req.originalUrl;
let filepath = path.normalize(serverpath + mypath + "/" + req.query.filename);
if (url.indexOf("action=deletedir") != -1) {
console.log("[server]delete directory " + filepath);
deleteFolderRecursive(filepath);
fs.readdirSync(mypath);
} else if (url.indexOf("action=delete") != -1) {
fs.unlinkSync(filepath);
console.log("[server]delete file " + filepath);
}
if (url.indexOf("action=createdir") != -1) {
fs.mkdirSync(filepath);
console.log("[server]new directory " + filepath);
}
if (typeof mypath == "undefined") {
if (typeof req.body.path == "undefined") {
console.log("[server]path is not defined");
mypath = "/";
} else {
mypath = (req.body.path == "/" ? "" : req.body.path) + "/";
}
}
console.log("[server]path is " + mypath);
if (!req.files || Object.keys(req.files).length === 0) {
return res.send(filesList(mypath,serverpath));
}
let myFile = req.files.myfiles;
if (typeof myFile.length == "undefined") {
let fullpath = path.normalize(serverpath + mypath + myFile.name);
console.log("[server]one file:" + fullpath);
myFile.mv(fullpath, function (err) {
if (err) return res.status(500).send(err);
res.send(filesList(mypath, serverpath));
});
return;
} else {
console.log(myFile.length + " files");
for (let i = 0; i < myFile.length; i++) {
let fullpath = path.normalize(serverpath + mypath + myFile[i].name);
console.log(fullpath);
myFile[i].mv(fullpath).then(() => {
if (i == myFile.length - 1) res.send(filesList(mypath, serverpath));
});
}
}
});
wss.on("connection", (socket, request) => {
console.log(wscolor("[ws] New connection"));
console.log(wscolor(`[ws] currentID:${currentID}`));
socket.send(`currentID:${currentID}`);
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(`activeID:${currentID}`);
}
});
currentID++;
socket.on("message", (message) => {
console.log(wscolor("[ws] received: %s", message));
});
});
wss.on("error", (error) => {
console.log(wscolor("[ws] Error: %s", error));
});