mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 15:59:52 +03:00
optimized for speed
This commit is contained in:
@@ -2,9 +2,26 @@
|
|||||||
// Simulates file uploads and EventSource (SSE) for log messages
|
// Simulates file uploads and EventSource (SSE) for log messages
|
||||||
import formidable from 'formidable';
|
import formidable from 'formidable';
|
||||||
|
|
||||||
|
// Constants reused across requests
|
||||||
|
const VALID_EXTENSIONS = new Set(['bin', 'json', 'md5']);
|
||||||
|
const ONE_SECOND_MS = 1000;
|
||||||
|
const TEN_PERCENT = 10;
|
||||||
|
|
||||||
// Optimized padding function
|
// Optimized padding function
|
||||||
const pad = (number) => String(number).padStart(2, '0');
|
const pad = (number) => String(number).padStart(2, '0');
|
||||||
|
|
||||||
|
// Simple throttle helper (time-based)
|
||||||
|
const throttle = (fn, intervalMs) => {
|
||||||
|
let last = 0;
|
||||||
|
return (...args) => {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - last >= intervalMs) {
|
||||||
|
last = now;
|
||||||
|
fn(...args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// Cached date formatter to avoid prototype pollution
|
// Cached date formatter to avoid prototype pollution
|
||||||
const formatDate = (date) => {
|
const formatDate = (date) => {
|
||||||
const year = date.getUTCFullYear();
|
const year = date.getUTCFullYear();
|
||||||
@@ -28,22 +45,50 @@ export default () => {
|
|||||||
server.middlewares.use(async (req, res, next) => {
|
server.middlewares.use(async (req, res, next) => {
|
||||||
// Handle file uploads
|
// Handle file uploads
|
||||||
if (req.url.startsWith('/rest/uploadFile')) {
|
if (req.url.startsWith('/rest/uploadFile')) {
|
||||||
|
// CORS preflight support
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
res.writeHead(204, {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type, Cache-Control',
|
||||||
|
'Access-Control-Max-Age': '600'
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method !== 'POST') {
|
||||||
|
res.statusCode = 405;
|
||||||
|
res.setHeader('Allow', 'POST, OPTIONS');
|
||||||
|
res.end('Method Not Allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fileSize = parseInt(req.headers['content-length'] || '0', 10);
|
const fileSize = parseInt(req.headers['content-length'] || '0', 10);
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
|
|
||||||
// Track upload progress
|
// Track upload progress
|
||||||
|
const logThrottled = throttle((percentage) => {
|
||||||
|
console.log(`Upload progress: ${percentage}%`);
|
||||||
|
}, ONE_SECOND_MS);
|
||||||
|
|
||||||
req.on('data', (chunk) => {
|
req.on('data', (chunk) => {
|
||||||
progress += chunk.length;
|
progress += chunk.length;
|
||||||
if (fileSize > 0) {
|
if (fileSize > 0) {
|
||||||
const percentage = Math.round((progress / fileSize) * 100);
|
const percentage = Math.round((progress / fileSize) * 100);
|
||||||
console.log(`Upload progress: ${percentage}%`);
|
// Only log every ~1s and for meaningful changes (>=10%)
|
||||||
|
if (percentage % TEN_PERCENT === 0) {
|
||||||
|
logThrottled(percentage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const form = formidable({
|
const form = formidable({
|
||||||
maxFileSize: 50 * 1024 * 1024, // 50MB limit
|
maxFileSize: 50 * 1024 * 1024, // 50MB limit
|
||||||
keepExtensions: true
|
keepExtensions: true,
|
||||||
|
multiples: false,
|
||||||
|
allowEmptyFiles: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const [fields, files] = await form.parse(req);
|
const [fields, files] = await form.parse(req);
|
||||||
@@ -54,7 +99,9 @@ export default () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadedFile = files.file[0];
|
const uploadedFile = Array.isArray(files.file)
|
||||||
|
? files.file[0]
|
||||||
|
: files.file;
|
||||||
const fileName = uploadedFile.originalFilename;
|
const fileName = uploadedFile.originalFilename;
|
||||||
const fileExtension = fileName
|
const fileExtension = fileName
|
||||||
.substring(fileName.lastIndexOf('.') + 1)
|
.substring(fileName.lastIndexOf('.') + 1)
|
||||||
@@ -65,8 +112,7 @@ export default () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Validate file extension
|
// Validate file extension
|
||||||
const validExtensions = new Set(['bin', 'json', 'md5']);
|
if (!VALID_EXTENSIONS.has(fileExtension)) {
|
||||||
if (!validExtensions.has(fileExtension)) {
|
|
||||||
res.statusCode = 406;
|
res.statusCode = 406;
|
||||||
res.end('Invalid file extension');
|
res.end('Invalid file extension');
|
||||||
return;
|
return;
|
||||||
@@ -85,10 +131,10 @@ export default () => {
|
|||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Upload error:', err.message);
|
console.error('Upload error:', err && err.message ? err.message : err);
|
||||||
res.statusCode = err.httpCode || 400;
|
res.statusCode = err.httpCode || 400;
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
res.setHeader('Content-Type', 'text/plain');
|
||||||
res.end(err.message);
|
res.end(err && err.message ? err.message : 'Upload error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,12 +143,18 @@ export default () => {
|
|||||||
// Set SSE headers
|
// Set SSE headers
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
Connection: 'keep-alive',
|
Connection: 'keep-alive',
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache, no-transform',
|
||||||
'Content-Type': 'text/event-stream',
|
'Content-Type': 'text/event-stream',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
'Access-Control-Allow-Headers': 'Cache-Control'
|
'Access-Control-Allow-Headers': 'Cache-Control',
|
||||||
|
'X-Accel-Buffering': 'no' // disable proxy buffering (nginx, etc.)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Flush headers early when supported
|
||||||
|
if (typeof res.flushHeaders === 'function') {
|
||||||
|
res.flushHeaders();
|
||||||
|
}
|
||||||
|
|
||||||
let messageCount = 0;
|
let messageCount = 0;
|
||||||
const logLevels = [3, 4, 5, 6, 7, 8]; // Different log levels
|
const logLevels = [3, 4, 5, 6, 7, 8]; // Different log levels
|
||||||
const logNames = ['system', 'ems', 'wifi', 'mqtt', 'ntp', 'api'];
|
const logNames = ['system', 'ems', 'wifi', 'mqtt', 'ntp', 'api'];
|
||||||
@@ -131,15 +183,24 @@ export default () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Send initial message
|
// Send initial message
|
||||||
|
res.write(`retry: 2000\n\n`); // client reconnection delay
|
||||||
sendLogMessage();
|
sendLogMessage();
|
||||||
|
|
||||||
// Set up interval for periodic messages
|
// Set up interval for periodic messages
|
||||||
const interval = setInterval(sendLogMessage, 500);
|
const messageInterval = setInterval(sendLogMessage, 500);
|
||||||
|
if (typeof messageInterval.unref === 'function') messageInterval.unref();
|
||||||
|
|
||||||
|
// Heartbeat to keep connections alive through proxies
|
||||||
|
const heartbeat = setInterval(() => {
|
||||||
|
res.write(`:keep-alive ${Date.now()}\n\n`);
|
||||||
|
}, 15 * ONE_SECOND_MS);
|
||||||
|
if (typeof heartbeat.unref === 'function') heartbeat.unref();
|
||||||
|
|
||||||
// Clean up on connection close
|
// Clean up on connection close
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
console.log('SSE connection closed');
|
console.log('SSE connection closed');
|
||||||
clearInterval(interval);
|
clearInterval(messageInterval);
|
||||||
|
clearInterval(heartbeat);
|
||||||
if (!res.destroyed) {
|
if (!res.destroyed) {
|
||||||
res.end();
|
res.end();
|
||||||
}
|
}
|
||||||
@@ -147,6 +208,7 @@ export default () => {
|
|||||||
|
|
||||||
res.on('close', cleanup);
|
res.on('close', cleanup);
|
||||||
res.on('error', cleanup);
|
res.on('error', cleanup);
|
||||||
|
res.on('finish', cleanup);
|
||||||
} else {
|
} else {
|
||||||
next(); // Continue to next middleware
|
next(); // Continue to next middleware
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4376,143 +4376,55 @@ router
|
|||||||
// EMS-ESP Project stuff
|
// EMS-ESP Project stuff
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// Lookup maps to avoid repetitive branching per request
|
||||||
|
const DEVICE_DATA_MAP: Record<number, any> = {
|
||||||
|
1: emsesp_devicedata_1,
|
||||||
|
2: emsesp_devicedata_2,
|
||||||
|
3: emsesp_devicedata_3,
|
||||||
|
4: emsesp_devicedata_4,
|
||||||
|
5: emsesp_devicedata_5,
|
||||||
|
6: emsesp_devicedata_6,
|
||||||
|
7: emsesp_devicedata_7,
|
||||||
|
8: emsesp_devicedata_8,
|
||||||
|
9: emsesp_devicedata_9,
|
||||||
|
10: emsesp_devicedata_10,
|
||||||
|
11: emsesp_devicedata_11,
|
||||||
|
99: emsesp_devicedata_99
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEVICE_ENTITIES_MAP: Record<number, any> = {
|
||||||
|
1: emsesp_deviceentities_1,
|
||||||
|
2: emsesp_deviceentities_2,
|
||||||
|
3: emsesp_deviceentities_3,
|
||||||
|
4: emsesp_deviceentities_4,
|
||||||
|
5: emsesp_deviceentities_5,
|
||||||
|
6: emsesp_deviceentities_6,
|
||||||
|
7: emsesp_deviceentities_7,
|
||||||
|
8: emsesp_deviceentities_8,
|
||||||
|
9: emsesp_deviceentities_9,
|
||||||
|
10: emsesp_deviceentities_10
|
||||||
|
};
|
||||||
|
|
||||||
function deviceData(id: number) {
|
function deviceData(id: number) {
|
||||||
if (id == 1) {
|
if (id === 8) {
|
||||||
return new Response(encoder.encode(emsesp_devicedata_1) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 2) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_2) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 3) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_3) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 4) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_4) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 5) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_5) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 6) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_6) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 7) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_7) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 8) {
|
|
||||||
// test changing the selected flow temp on a Bosch Compress 7000i AW Heat Pump (Boiler/HP)
|
// test changing the selected flow temp on a Bosch Compress 7000i AW Heat Pump (Boiler/HP)
|
||||||
emsesp_devicedata_8.nodes[4].v = Math.floor(Math.random() * 100);
|
emsesp_devicedata_8.nodes[4].v = Math.floor(Math.random() * 100);
|
||||||
return new Response(encoder.encode(emsesp_devicedata_8) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (id == 9) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_9) as BodyInit, {
|
const data = DEVICE_DATA_MAP[id];
|
||||||
headers
|
if (data) {
|
||||||
});
|
return new Response(encoder.encode(data) as BodyInit, { headers });
|
||||||
}
|
|
||||||
if (id == 10) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_10) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 11) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_11) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 99) {
|
|
||||||
return new Response(encoder.encode(emsesp_devicedata_99) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deviceEntities(id: number) {
|
function deviceEntities(id: number) {
|
||||||
if (id == 1) {
|
const data = DEVICE_ENTITIES_MAP[id] || emsesp_deviceentities_none;
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_1) as BodyInit, {
|
return new Response(encoder.encode(data) as BodyInit, { headers });
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 2) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_2) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 3) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_3) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 4) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_4) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 5) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_5) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 6) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_6) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 7) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_7) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 8) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_8) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 9) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_9) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (id == 10) {
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_10) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// not found, return empty
|
|
||||||
return new Response(encoder.encode(emsesp_deviceentities_none) as BodyInit, {
|
|
||||||
headers
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepare dashboard data
|
// prepare dashboard data
|
||||||
function getDashboardEntityData(id: number) {
|
function getDashboardEntityData(id: number) {
|
||||||
let device_data = {};
|
const device_data = DEVICE_DATA_MAP[id] || { nodes: [] };
|
||||||
if (id == 1) device_data = emsesp_devicedata_1;
|
|
||||||
else if (id == 2) device_data = emsesp_devicedata_2;
|
|
||||||
else if (id == 3) device_data = emsesp_devicedata_3;
|
|
||||||
else if (id == 4) device_data = emsesp_devicedata_4;
|
|
||||||
else if (id == 5) device_data = emsesp_devicedata_5;
|
|
||||||
else if (id == 6) device_data = emsesp_devicedata_6;
|
|
||||||
else if (id == 7) device_data = emsesp_devicedata_7;
|
|
||||||
else if (id == 8) device_data = emsesp_devicedata_8;
|
|
||||||
else if (id == 9) device_data = emsesp_devicedata_9;
|
|
||||||
else if (id == 10) device_data = emsesp_devicedata_10;
|
|
||||||
else if (id == 11) device_data = emsesp_devicedata_11;
|
|
||||||
else if (id == 99) device_data = emsesp_devicedata_99;
|
|
||||||
|
|
||||||
// filter device_data on
|
// filter device_data on
|
||||||
// - only add favorite (mask has bit 8 set) except for Custom Entities (type 99)
|
// - only add favorite (mask has bit 8 set) except for Custom Entities (type 99)
|
||||||
|
|||||||
Reference in New Issue
Block a user