Implement Bodenfeuchte app: MQTT listener, SQLite storage, chart UI, Docker

- Custom Next.js server starts MQTT listener on boot
- Subscribes to zigbee2mqtt/Bodenfeuchte_1, stores soil_moisture in SQLite
- API route /api/data returns last 6 hours of measurements
- Frontend shows current value + Recharts line chart, auto-refresh every 60s
- Dockerfile + docker-compose with persistent volume for SQLite DB

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 18:10:18 +02:00
parent de23825cf6
commit 635b3ce598
11 changed files with 1671 additions and 72 deletions
+37
View File
@@ -0,0 +1,37 @@
import Database from 'better-sqlite3';
import path from 'path';
const DB_PATH = path.join(process.cwd(), 'data', 'bodenfeuchte.db');
let db: Database.Database;
export function getDb(): Database.Database {
if (!db) {
db = new Database(DB_PATH);
db.exec(`
CREATE TABLE IF NOT EXISTS measurements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER NOT NULL,
soil_moisture REAL NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_timestamp ON measurements(timestamp);
`);
}
return db;
}
export function insertMeasurement(soilMoisture: number): void {
const stmt = getDb().prepare(
'INSERT INTO measurements (timestamp, soil_moisture) VALUES (?, ?)'
);
stmt.run(Date.now(), soilMoisture);
}
export function getLast6Hours(): { timestamp: number; soil_moisture: number }[] {
const since = Date.now() - 6 * 60 * 60 * 1000;
return getDb()
.prepare(
'SELECT timestamp, soil_moisture FROM measurements WHERE timestamp >= ? ORDER BY timestamp ASC'
)
.all(since) as { timestamp: number; soil_moisture: number }[];
}
+35
View File
@@ -0,0 +1,35 @@
import mqtt from 'mqtt';
import { insertMeasurement } from './db';
const TOPIC = 'zigbee2mqtt/Bodenfeuchte_1';
export function startMqttListener(): void {
const client = mqtt.connect({
host: process.env.MQTT_BROKER ?? 'nuccy',
port: Number(process.env.MQTT_PORT ?? 1882),
username: process.env.MQTT_USER ?? 'rxf',
password: process.env.MQTT_PASSWORD ?? '',
});
client.on('connect', () => {
console.log('[MQTT] Connected, subscribing to', TOPIC);
client.subscribe(TOPIC);
});
client.on('message', (_topic, payload) => {
try {
const data = JSON.parse(payload.toString());
const value = data?.soil_moisture;
if (typeof value === 'number') {
insertMeasurement(value);
console.log('[MQTT] Stored soil_moisture:', value);
}
} catch {
console.error('[MQTT] Failed to parse message');
}
});
client.on('error', (err) => {
console.error('[MQTT] Error:', err.message);
});
}