You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

170 lines
5.0 KiB
JavaScript

// api/server.js
const express = require('express');
const cors = require('cors');
const fs = require('fs/promises');
const path = require('path');
const {
detectMode,
executeEzoCommand
} = require('./ezo-command-service');
const app = express();
const PORT = Number(process.env.PORT || 3000);
const HISTORY_FILES = {
temperature: 'timestamp,temperature\n',
ph: 'timestamp,ph\n',
do: 'timestamp,do\n',
ec: 'timestamp,ec\n'
};
const LOGS_DIRECTORY = process.env.LOGS_DIRECTORY
? path.resolve(process.env.LOGS_DIRECTORY)
: path.join(__dirname, '..', 'logs');
app.use(cors());
app.use(express.json());
app.use('/frontend', express.static(path.join(__dirname, '..', 'frontend')));
app.use('/data', express.static(path.join(__dirname, '..', 'data'), {
etag: false,
maxAge: 0
}));
app.use('/logs', express.static(LOGS_DIRECTORY, {
etag: false,
maxAge: 0
}));
app.use('/config', express.static(path.join(__dirname, '..', 'config'), {
etag: false,
maxAge: 0
}));
app.get('/', (req, res) => {
res.redirect('/frontend/index.html');
});
app.get('/api/system/ezo', (req, res) => {
const mode = detectMode();
res.json({
mode: mode.mode,
message: mode.message
});
});
// Función auxiliar para simular latencia
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Endpoint principal para obtener datos de los sensores (MOCK)
app.get('/api/sensors/:type', async (req, res) => {
const sensorType = req.params.type;
console.log(`[GET] Petición recibida para sensor: ${sensorType}`);
// Simulación de latencia I2C y procesamiento (400ms)
await delay(400);
const data = [];
const now = new Date();
// Generamos 60 registros simulados
for (let i = 60; i >= 0; i--) {
const timestamp = new Date(now.getTime() - i * 1000).toISOString();
// Simulación de valores con ruido térmico/químico
const mockValues = {
rtd: (25.0 + (Math.random() * 0.1 - 0.05)).toFixed(2),
ph: (7.2 + (Math.random() * 0.04 - 0.02)).toFixed(2),
do: (8.5 + (Math.random() * 0.1 - 0.05)).toFixed(2),
ec: (1050 + (Math.random() * 10 - 5)).toFixed(0)
};
if (sensorType === 'all') {
data.push({
Timestamp: timestamp,
Temperatura_C: mockValues.rtd,
pH: mockValues.ph,
DO_mgL: mockValues.do,
EC_uS: mockValues.ec
});
} else if (mockValues[sensorType]) {
data.push({
Timestamp: timestamp,
Sensor: sensorType.toUpperCase(),
Valor: mockValues[sensorType]
});
} else {
return res.status(400).json({ error: "Sensor no válido" });
}
}
res.json(data);
});
// Consola EZO: usa I2C real en Raspberry Pi y modo demo en otros equipos.
app.post('/api/sensors/:type/command', async (req, res) => {
try {
const result = await executeEzoCommand(
req.params.type.toLowerCase(),
req.body.command,
{ dangerousConfirmed: req.body.dangerousConfirmed === true }
);
res.json(result);
} catch (error) {
res.status(error.status || 500).json({
success: false,
error: error.message
});
}
});
// --- RUTAS DE GESTIÓN DE ALMACENAMIENTO ---
// Variable global para simular la configuración de escritura del demonio C++
let loggingRateSeconds = 1;
app.post('/api/config/logging', (req, res) => {
const requestedRate = Number(req.body.rate);
const allowedRates = new Set([1, 5, 10, 60]);
if (!allowedRates.has(requestedRate)) {
return res.status(400).json({ success: false, error: 'Frecuencia no válida' });
}
loggingRateSeconds = requestedRate;
console.log(`[SIMULACIÓN] Frecuencia configurada a: 1 registro cada ${loggingRateSeconds}s`);
res.json({
success: true,
simulated: true,
rate: loggingRateSeconds,
message: 'Configuración aplicada únicamente al backend simulado.'
});
});
app.post('/api/history/clear', async (req, res) => {
try {
await Promise.all(
Object.entries(HISTORY_FILES).map(([name, header]) =>
fs.writeFile(path.join(LOGS_DIRECTORY, `${name}.csv`), header, 'utf8')
)
);
console.log('[SISTEMA] Archivos CSV históricos truncados.');
res.json({
success: true,
message: 'Archivos de registro truncados correctamente.'
});
} catch (error) {
console.error('[ERROR] No se pudieron truncar los historiales:', error);
res.status(500).json({
success: false,
error: 'No se pudieron borrar los archivos históricos.'
});
}
});
if (require.main === module) {
app.listen(PORT, () => {
console.log(`[MOCK SERVER] Backend Node.js corriendo en http://localhost:${PORT}`);
});
}
module.exports = {
app,
HISTORY_FILES
};