126 lines
4.1 KiB
JavaScript
126 lines
4.1 KiB
JavaScript
require('dotenv').config();
|
||
const express = require('express');
|
||
const bodyParser = require('body-parser');
|
||
const path = require('path');
|
||
const knex = require('knex');
|
||
const axios = require('axios');
|
||
|
||
// Initialize MariaDB connection via Knex
|
||
const db = knex({
|
||
client: process.env.DB_CLIENT,
|
||
connection: {
|
||
host: process.env.DB_HOST,
|
||
port: process.env.DB_PORT,
|
||
user: process.env.DB_USER,
|
||
password: process.env.DB_PASSWORD,
|
||
database: process.env.DB_NAME
|
||
}
|
||
});
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 3000;
|
||
const slackWebhook = process.env.SLACK_WEBHOOK_URL;
|
||
|
||
// Create table if not exists, now with direction
|
||
(async () => {
|
||
if (!await db.schema.hasTable('readings')) {
|
||
await db.schema.createTable('readings', table => {
|
||
table.increments('id').primary();
|
||
table.integer('dockDoor');
|
||
table.string('direction');
|
||
table.timestamp('timestamp');
|
||
table.float('temperature');
|
||
table.float('humidity');
|
||
table.float('heatIndex');
|
||
});
|
||
}
|
||
})();
|
||
|
||
// 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;
|
||
}
|
||
|
||
// Determine direction based on door number
|
||
function getDirection(door) {
|
||
door = Number(door);
|
||
if (door >= 124 && door <= 138) return 'Inbound';
|
||
if (door >= 142 && door <= 201) return 'Outbound';
|
||
if (door >= 202 && door <= 209) return 'Inbound';
|
||
return 'Unknown';
|
||
}
|
||
|
||
app.use(bodyParser.json());
|
||
app.use(express.static(path.join(__dirname, 'public')));
|
||
|
||
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));
|
||
}
|
||
|
||
app.post('/api/readings', async (req, res) => {
|
||
try {
|
||
const { inbound, outbound } = req.body; // each: {dockDoor,temperature,humidity}
|
||
const timestamp = new Date();
|
||
const entries = [inbound, outbound].map(r => {
|
||
const direction = getDirection(r.dockDoor);
|
||
const heatIndex = computeHeatIndex(r.temperature, r.humidity);
|
||
return { ...r, direction, timestamp, heatIndex };
|
||
});
|
||
// Insert both
|
||
const ids = await db('readings').insert(entries);
|
||
const saved = entries.map((e, i) => ({ id: ids[i], ...e }));
|
||
|
||
// Broadcast and respond
|
||
saved.forEach(reading => broadcast('new-reading', reading));
|
||
|
||
// Slack notification with both
|
||
if (slackWebhook) {
|
||
const textLines = saved.map(r =>
|
||
`Door *${r.dockDoor}* (${r.direction}) – Temp: ${r.temperature}°F, Humidity: ${r.humidity}%, HI: ${r.heatIndex}`
|
||
);
|
||
await axios.post(slackWebhook, { text: 'New dual readings:\n' + textLines.join('\n') });
|
||
}
|
||
res.json(saved);
|
||
} catch (err) {
|
||
console.error('Error saving readings or sending Slack:', err);
|
||
res.status(500).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
app.get('/api/readings', async (req, res) => {
|
||
try {
|
||
const rows = await db('readings').orderBy('timestamp', 'asc');
|
||
res.json(rows);
|
||
} catch (err) {
|
||
res.status(500).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
app.get('/api/export', async (req, res) => {
|
||
try {
|
||
const rows = await db('readings').orderBy('timestamp', 'asc');
|
||
res.setHeader('Content-disposition', 'attachment; filename=readings.csv');
|
||
res.set('Content-Type', 'text/csv');
|
||
res.write('id,dockDoor,direction,timestamp,temperature,humidity,heatIndex\n');
|
||
rows.forEach(r =>
|
||
res.write(`${r.id},${r.dockDoor},${r.direction},${r.timestamp},${r.temperature},${r.humidity},${r.heatIndex}\n`)
|
||
);
|
||
res.end();
|
||
} catch (err) {
|
||
res.status(500).send(err.message);
|
||
}
|
||
});
|
||
|
||
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`)); |