/** * db.js – SQLite-Modul für Wetterdaten * * Exports: * openDb(path) – DB öffnen / anlegen * getLatestTs(db) – letzten archivierten Zeitstempel lesen * insertRecord(db, record, source) – einzelnen Datensatz einfügen * insertRecords(db, records, source) – Batch-Insert (Transaktion) */ import Database from "better-sqlite3"; // ── Schema ───────────────────────────────────────────────────────────────── const SCHEMA = ` CREATE TABLE IF NOT EXISTS readings ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts INTEGER NOT NULL, -- Unix-Zeit in Sekunden (UTC) source TEXT NOT NULL, -- 'archive' | 'loop' temp_out REAL, -- °C temp_out_high REAL, -- °C (nur Archiv) temp_out_low REAL, -- °C (nur Archiv) temp_in REAL, -- °C hum_out INTEGER, -- % hum_in INTEGER, -- % wind_avg REAL, -- km/h wind_high REAL, -- km/h (nur Archiv) wind_dir TEXT, -- Himmelsrichtung wind_high_dir TEXT, -- Himmelsrichtung (nur Archiv) pressure REAL, -- hPa rain REAL, -- mm (Archiv: Intervall; Loop: Tagessumme) rain_rate REAL, -- mm/h solar_rad INTEGER, -- W/m² UNIQUE(ts, source) ); CREATE INDEX IF NOT EXISTS idx_readings_ts ON readings(ts); `; // ── Öffnen ───────────────────────────────────────────────────────────────── /** * Öffnet (oder erstellt) die SQLite-Datenbank und initialisiert das Schema. * @param {string} dbPath – Pfad zur .db-Datei * @returns {Database} better-sqlite3 Instanz */ export function openDb(dbPath) { const db = new Database(dbPath); db.pragma("journal_mode = WAL"); db.exec(SCHEMA); return db; } // ── Lesen ────────────────────────────────────────────────────────────────── /** * Gibt den Unix-Zeitstempel (Sekunden) des neuesten Archiv-Eintrags zurück, * oder null wenn die Tabelle leer ist. */ export function getLatestTs(db) { const row = db.prepare( "SELECT MAX(ts) AS ts FROM readings WHERE source = 'archive'" ).get(); return row?.ts ?? null; } // ── Schreiben ────────────────────────────────────────────────────────────── const INSERT_SQL = ` INSERT OR IGNORE INTO readings (ts, source, temp_out, temp_out_high, temp_out_low, temp_in, hum_out, hum_in, wind_avg, wind_high, wind_dir, wind_high_dir, pressure, rain, rain_rate, solar_rad) VALUES (@ts, @source, @temp_out, @temp_out_high, @temp_out_low, @temp_in, @hum_out, @hum_in, @wind_avg, @wind_high, @wind_dir, @wind_high_dir, @pressure, @rain, @rain_rate, @solar_rad) `; function toRow(record, source) { return { ts: Math.floor(record.time.getTime() / 1000), source, temp_out: record.tempOut ?? null, temp_out_high: record.tempOutHigh ?? null, temp_out_low: record.tempOutLow ?? null, temp_in: record.tempIn ?? null, hum_out: record.humOut ?? null, hum_in: record.humIn ?? null, wind_avg: record.windAvg ?? null, wind_high: record.windHigh ?? null, wind_dir: record.windDir ?? null, wind_high_dir: record.windHighDir ?? null, pressure: record.pressure ?? null, rain: record.rain ?? null, rain_rate: record.rainRate ?? null, solar_rad: record.solarRad ?? null, }; } /** * Fügt einen einzelnen Datensatz in die DB ein. * Ignoriert Duplikate (gleiche ts + source) dank UNIQUE-Constraint. */ export function insertRecord(db, record, source) { db.prepare(INSERT_SQL).run(toRow(record, source)); } /** * Fügt ein Array von Datensätzen in einer einzigen Transaktion ein. * Gibt die Anzahl tatsächlich eingefügter Zeilen zurück. */ export function insertRecords(db, records, source) { const stmt = db.prepare(INSERT_SQL); const run = db.transaction(recs => { let count = 0; for (const r of recs) { const info = stmt.run(toRow(r, source)); count += info.changes; } return count; }); return run(records); }