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:
@@ -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 }[];
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user