198 lines
5.8 KiB
JavaScript
198 lines
5.8 KiB
JavaScript
import 'dotenv/config';
|
|
import express from 'express';
|
|
import sqlite3Pkg from 'sqlite3';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const sqlite3 = sqlite3Pkg.verbose();
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
|
|
const app = express();
|
|
|
|
// Configuration
|
|
const DB_FILE = process.env.DB_FILE || 'wetterdaten.db';
|
|
const HTTP_PORT = parseInt(process.env.HTTP_PORT || '5003', 10);
|
|
|
|
// View engine setup
|
|
app.set('views', path.join(__dirname, 'views'));
|
|
app.set('view engine', 'pug');
|
|
|
|
// Middleware
|
|
app.use(express.json());
|
|
app.use('/static', express.static(path.join(__dirname, 'static')));
|
|
|
|
// Database handling class
|
|
class WetterDB {
|
|
constructor(dbFile) {
|
|
this.dbFile = dbFile;
|
|
this.initDb();
|
|
}
|
|
|
|
initDb() {
|
|
const db = new sqlite3.Database(this.dbFile);
|
|
db.serialize(() => {
|
|
db.run(`
|
|
CREATE TABLE IF NOT EXISTS wetterdaten (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
dateTime INTEGER NOT NULL,
|
|
barometer REAL,
|
|
outTemp REAL,
|
|
outHumidity INTEGER,
|
|
windSpeed REAL,
|
|
windDir REAL,
|
|
windGust REAL,
|
|
rainRate REAL,
|
|
rain REAL
|
|
)
|
|
`);
|
|
db.run(`
|
|
CREATE INDEX IF NOT EXISTS idx_dateTime ON wetterdaten(dateTime)
|
|
`);
|
|
});
|
|
db.close();
|
|
}
|
|
|
|
saveData(data) {
|
|
return new Promise((resolve, reject) => {
|
|
const db = new sqlite3.Database(this.dbFile);
|
|
const sql = `
|
|
INSERT INTO wetterdaten
|
|
(dateTime, barometer, outTemp, outHumidity, windSpeed, windDir, windGust, rainRate, rain)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`;
|
|
const params = [
|
|
data.dateTime,
|
|
data.barometer,
|
|
data.outTemp,
|
|
data.outHumidity,
|
|
data.windSpeed,
|
|
data.windDir,
|
|
data.windGust,
|
|
data.rainRate,
|
|
data.rain
|
|
];
|
|
|
|
db.run(sql, params, function(err) {
|
|
db.close();
|
|
if (err) {
|
|
console.error("Error saving data:", err);
|
|
reject(err);
|
|
} else {
|
|
console.log(`Daten gespeichert: ${data.dateTime}`);
|
|
resolve(this.lastID);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
getData(hours = 24) {
|
|
return new Promise((resolve, reject) => {
|
|
const db = new sqlite3.Database(this.dbFile);
|
|
|
|
// Calculate timestamp threshold (current time - hours) in seconds (Unix Timestamp)
|
|
const timeThreshold = Math.floor((Date.now() - hours * 60 * 60 * 1000) / 1000);
|
|
|
|
const sql = `
|
|
SELECT * FROM wetterdaten
|
|
WHERE dateTime >= ?
|
|
ORDER BY dateTime ASC
|
|
`;
|
|
|
|
db.all(sql, [timeThreshold], (err, rows) => {
|
|
db.close();
|
|
if (err) reject(err);
|
|
else resolve(rows);
|
|
});
|
|
});
|
|
}
|
|
|
|
getHourlyRain(hours = 24) {
|
|
return new Promise((resolve, reject) => {
|
|
const db = new sqlite3.Database(this.dbFile);
|
|
|
|
// Calculate timestamp threshold (current time - hours) in seconds (Unix Timestamp)
|
|
const timeThreshold = Math.floor((Date.now() - hours * 60 * 60 * 1000) / 1000);
|
|
|
|
const sql = `
|
|
SELECT
|
|
strftime('%Y-%m-%d %H:00:00', datetime(dateTime, 'unixepoch', 'localtime')) as hour,
|
|
SUM(rainRate) as total_rain
|
|
FROM wetterdaten
|
|
WHERE dateTime >= ?
|
|
GROUP BY hour
|
|
ORDER BY hour ASC
|
|
`;
|
|
|
|
db.all(sql, [timeThreshold], (err, rows) => {
|
|
db.close();
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
const result = rows.map(row => ({
|
|
hour: row.hour,
|
|
rain: row.total_rain || 0
|
|
}));
|
|
resolve(result);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
// Global DB instance
|
|
const db = new WetterDB(DB_FILE);
|
|
|
|
// Routes
|
|
app.get('/', (req, res) => {
|
|
res.render('index');
|
|
});
|
|
|
|
app.post('/api/data/upload', async (req, res) => {
|
|
try {
|
|
const data = req.body;
|
|
|
|
if (!data || Object.keys(data).length === 0) {
|
|
return res.status(400).json({ error: 'Keine Daten empfangen' });
|
|
}
|
|
|
|
await db.saveData(data);
|
|
|
|
res.status(200).json({
|
|
status: 'success',
|
|
message: 'Daten empfangen und gespeichert'
|
|
});
|
|
} catch (e) {
|
|
console.error(`Fehler beim Verarbeiten der POST-Anfrage: ${e}`);
|
|
res.status(400).json({ error: e.toString() });
|
|
}
|
|
});
|
|
|
|
app.get('/api/data/:period', async (req, res) => {
|
|
const period = req.params.period;
|
|
const hours = period === 'day' ? 24 : 168; // 168h = 1 week
|
|
|
|
try {
|
|
const [data, rainData] = await Promise.all([
|
|
db.getData(hours),
|
|
db.getHourlyRain(hours)
|
|
]);
|
|
|
|
res.json({
|
|
data: data,
|
|
rain_hourly: rainData
|
|
});
|
|
} catch (e) {
|
|
console.error(e);
|
|
res.status(500).json({ error: 'Internal Server Error' });
|
|
}
|
|
});
|
|
|
|
// Start server
|
|
app.listen(HTTP_PORT, '0.0.0.0', () => {
|
|
console.log("Wetterstation wird gestartet...");
|
|
console.log(`\nWeb-Interface verfügbar unter: http://localhost:${HTTP_PORT}`);
|
|
console.log(`HTTP-POST Endpoint: http://localhost:${HTTP_PORT}/api/data/upload`);
|
|
console.log("Drücke CTRL+C zum Beenden\n");
|
|
});
|