85 lines
2.8 KiB
JavaScript
85 lines
2.8 KiB
JavaScript
const express = require('express');
|
|
const sqlite3 = require('sqlite3').verbose();
|
|
const bodyParser = require('body-parser');
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 3000;
|
|
|
|
// Initialize SQLite database
|
|
const db = new sqlite3.Database('./readings.db');
|
|
db.serialize(() => {
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS readings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
dockDoor INTEGER,
|
|
timestamp TEXT,
|
|
temperature REAL,
|
|
humidity REAL,
|
|
heatIndex REAL
|
|
)
|
|
`);
|
|
});
|
|
|
|
// Compute heat index (NOAA formula)
|
|
function computeHeatIndex(T, R) {
|
|
const c1 = -42.379, c2 = 2.04901523, c3 = 10.14333127;
|
|
const c4 = -0.22475541, c5 = -6.83783e-3, c6 = -5.481717e-2;
|
|
const c7 = 1.22874e-3, c8 = 8.5282e-4, c9 = -1.99e-6;
|
|
const HI = c1 + c2*T + c3*R + c4*T*R + c5*T*T + c6*R*R + c7*T*T*R + c8*T*R*R + c9*T*T*R*R;
|
|
return Math.round(HI * 100) / 100;
|
|
}
|
|
|
|
// Middleware & static
|
|
app.use(bodyParser.json());
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
// SSE clients
|
|
let clients = [];
|
|
app.get('/api/stream', (req, res) => {
|
|
res.set({ 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' });
|
|
res.flushHeaders();
|
|
clients.push(res);
|
|
req.on('close', () => { clients = clients.filter(c => c !== res); });
|
|
});
|
|
function broadcast(event, data) {
|
|
const payload = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
clients.forEach(res => res.write(payload));
|
|
}
|
|
|
|
// APIs
|
|
app.post('/api/readings', (req, res) => {
|
|
const { dockDoor, timestamp, temperature, humidity } = req.body;
|
|
const heatIndex = computeHeatIndex(temperature, humidity);
|
|
db.run(
|
|
`INSERT INTO readings (dockDoor, timestamp, temperature, humidity, heatIndex) VALUES (?, ?, ?, ?, ?)`,
|
|
[dockDoor, timestamp, temperature, humidity, heatIndex],
|
|
function(err) {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
const reading = { id: this.lastID, dockDoor, timestamp, temperature, humidity, heatIndex };
|
|
broadcast('new-reading', reading);
|
|
res.json(reading);
|
|
}
|
|
);
|
|
});
|
|
|
|
app.get('/api/readings', (req, res) => {
|
|
db.all(`SELECT * FROM readings ORDER BY timestamp ASC`, (err, rows) => {
|
|
if (err) return res.status(500).json({ error: err.message });
|
|
res.json(rows);
|
|
});
|
|
});
|
|
|
|
app.get('/api/export', (req, res) => {
|
|
db.all(`SELECT * FROM readings ORDER BY timestamp ASC`, (err, rows) => {
|
|
if (err) return res.status(500).send(err.message);
|
|
res.setHeader('Content-disposition', 'attachment; filename=readings.csv');
|
|
res.set('Content-Type', 'text/csv');
|
|
res.write('id,dockDoor,timestamp,temperature,humidity,heatIndex\n');
|
|
rows.forEach(r => res.write(`${r.id},${r.dockDoor},${r.timestamp},${r.temperature},${r.humidity},${r.heatIndex}\n`));
|
|
res.end();
|
|
});
|
|
});
|
|
|
|
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
|