diff --git a/davis.js b/davis.js index c4ba2dc..0b6d39f 100644 --- a/davis.js +++ b/davis.js @@ -166,8 +166,8 @@ function parseLOOP1(pkt) { tempIn: temp(pkt.readInt16LE(9)), humOut: hum(pkt[33]), humIn: hum(pkt[11]), - windAvg: mph(pkt[14]), - windGust: mph(pkt[15]), + windAvg: mph(pkt[15]), + windGust: mph(pkt[14]), windDir: pkt.readUInt16LE(16) || null, forecast: pkt[89], pressure: press === 0 ? null : r1(press * 33.8639 / 1000), diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..7500492 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Deploy Script für werte-next +# Baut das Docker Image und lädt es zu docker.citysensor.de hoch + +set -e + +# Konfiguration +REGISTRY="docker.citysensor.de" +IMAGE_NAME="wetterserver" +TAG="${TAG:-$(date +%Y%m%d%H%M)}" # default Datum +FULL_IMAGE="${REGISTRY}/${IMAGE_NAME}:${TAG}" + +# Build-Datum +BUILD_DATE=$(date +%d.%m.%Y) + +echo "==========================================" +echo "Werte-Next Deploy Script" +echo "==========================================" +echo "Registry: ${REGISTRY}" +echo "Image: ${IMAGE_NAME}" +echo "Tag: ${TAG}" +echo "Build-Datum: ${BUILD_DATE}" +echo "==========================================" +echo "" + +# 1. Login zur Registry (falls noch nicht eingeloggt) +echo ">>> Login zu ${REGISTRY}..." +docker login "${REGISTRY}" +echo "" + +# 2. Multiplatform Builder einrichten (docker-container driver erforderlich) +echo ">>> Richte Multiplatform Builder ein..." +if ! docker buildx inspect multiplatform-builder &>/dev/null; then + docker buildx create --name multiplatform-builder --driver docker-container --bootstrap +fi +docker buildx use multiplatform-builder +echo "" + +# 3. Docker Image bauen und pushen (Multiplatform) +echo ">>> Baue Multiplatform Docker Image und pushe zu Registry..." +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --build-arg BUILD_DATE="${BUILD_DATE}" \ + -t "${FULL_IMAGE}" \ + --push \ + . + + # 4. Tagge auch als :latest + echo ">>> Tagge ${image} als :latest..." + docker buildx imagetools create \ + -t "${REGISTRY}/${image}:latest" \ + "${FULL_IMAGE}" + +echo ">>> Build und Push erfolgreich!" + +echo "" +echo "==========================================" +echo "✓ Deploy erfolgreich abgeschlossen!" +echo "==========================================" +echo "" +echo "Auf dem Server ausführen:" +echo " docker pull ${FULL_IMAGE}" +echo " docker-compose -f docker-compose.prod.yml up -d" +echo "" diff --git a/wetter.js b/wetter.js index 1e74741..a9d0f3e 100644 --- a/wetter.js +++ b/wetter.js @@ -18,6 +18,7 @@ import { readArchiveSince, connectStation, fetchLoopData } from "./davis.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DB_PATH = process.env.DB_PATH ?? path.join(__dirname, "wetter.db"); const LOOP_INTERVAL_MS = Number(process.env.LOOP_INTERVAL_MS ?? 30_000); +const DB_INTERVAL_MS = 5 * 60 * 1000; // alle 5 min in DB schreiben const POST_URL = process.env.POST_URL ?? null; // ── Hilfsfunktionen ──────────────────────────────────────────────────────── @@ -28,6 +29,40 @@ function log(msg) { console.log (`[${fmt24h(new Date())}] ${msg}`); } function warn(msg) { console.warn(`[${fmt24h(new Date())}] WARN ${msg}`); } function err(msg) { console.error(`[${fmt24h(new Date())}] ERROR ${msg}`); } +// ── 5-Minuten-Aggregation ────────────────────────────────────────────────── + +function aggregateBuffer(buf) { + const avg = (key) => { + const vals = buf.map(r => r[key]).filter(v => v !== null && v !== undefined); + if (!vals.length) return null; + return +( vals.reduce((a, b) => a + b, 0) / vals.length ).toFixed(1); + }; + const peak = (key) => { + const vals = buf.map(r => r[key]).filter(v => v !== null && v !== undefined); + return vals.length ? +Math.max(...vals).toFixed(1) : null; + }; + const last = (key) => { + for (let i = buf.length - 1; i >= 0; i--) + if (buf[i][key] !== null && buf[i][key] !== undefined) return buf[i][key]; + return null; + }; + return { + time: buf[buf.length - 1].time, + tempOut: avg('tempOut'), + tempIn: avg('tempIn'), + humOut: avg('humOut'), + humIn: avg('humIn'), + windAvg: avg('windAvg'), + windGust: peak('windGust'), // Spitzenwert der Periode + windDir: avg('windDir'), + pressure: avg('pressure'), + barTrend: last('barTrend'), + forecast: last('forecast'), + rain: last('rain'), + rainRate: avg('rainRate'), + }; +} + // ── Archiv nachladen ─────────────────────────────────────────────────────── async function catchUpArchive(db) { @@ -55,12 +90,24 @@ async function catchUpArchive(db) { const inserted = insertRecords(db, records, "archive"); log(`Archiv: ${inserted} neue Datensätze gespeichert (${records.length} empfangen).`); + + if (POST_URL) { + for (const r of records) { + fetch(POST_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(r), + }).catch(e => warn("Archiv-POST fehlgeschlagen: " + e.message)); + } + } } // ── LOOP-Schleife ────────────────────────────────────────────────────────── async function runLoop(db) { - let station = null; + let station = null; + const buffer = []; + let lastDbWrite = Date.now(); async function connect() { station = await connectStation(); @@ -70,7 +117,8 @@ async function runLoop(db) { async function tick() { try { const data = await fetchLoopData(station); - insertRecord(db, data, "loop"); + buffer.push(data); + if (POST_URL) { fetch(POST_URL, { method: "POST", @@ -78,6 +126,17 @@ async function runLoop(db) { body: JSON.stringify(data), }).catch(e => warn("POST fehlgeschlagen: " + e.message)); } + + const now = Date.now(); + if (now - lastDbWrite >= DB_INTERVAL_MS) { + const n = buffer.length; + const agg = aggregateBuffer(buffer); + buffer.length = 0; + lastDbWrite = now; + insertRecord(db, agg, "loop"); + log(`DB: 5-min-Mittel gespeichert (${n} Messwerte)`); + } + log( `Außen: ${data.tempOut?.toFixed(1)}°C ` + `InnenT: ${data.tempIn?.toFixed(1)}°C ` +