Compare commits
3 Commits
fc8c7071f8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 842727a851 | |||
| 6ae153ce10 | |||
| c630ef50a7 |
@@ -18,6 +18,7 @@ services:
|
|||||||
BAUD_RATE: ${BAUD_RATE:-19200}
|
BAUD_RATE: ${BAUD_RATE:-19200}
|
||||||
DB_PATH: /data/wetter.db
|
DB_PATH: /data/wetter.db
|
||||||
LOOP_INTERVAL_MS: ${LOOP_INTERVAL_MS:-30000}
|
LOOP_INTERVAL_MS: ${LOOP_INTERVAL_MS:-30000}
|
||||||
|
COLLECTOR_API_KEY: ${COLLECTOR_API_KEY}
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
wetter_data:
|
wetter_data:
|
||||||
|
|||||||
4
davis.js
4
davis.js
@@ -166,8 +166,8 @@ function parseLOOP1(pkt) {
|
|||||||
tempIn: temp(pkt.readInt16LE(9)),
|
tempIn: temp(pkt.readInt16LE(9)),
|
||||||
humOut: hum(pkt[33]),
|
humOut: hum(pkt[33]),
|
||||||
humIn: hum(pkt[11]),
|
humIn: hum(pkt[11]),
|
||||||
windAvg: mph(pkt[14]),
|
windAvg: mph(pkt[15]),
|
||||||
windGust: mph(pkt[15]),
|
windGust: mph(pkt[14]),
|
||||||
windDir: pkt.readUInt16LE(16) || null,
|
windDir: pkt.readUInt16LE(16) || null,
|
||||||
forecast: pkt[89],
|
forecast: pkt[89],
|
||||||
pressure: press === 0 ? null : r1(press * 33.8639 / 1000),
|
pressure: press === 0 ? null : r1(press * 33.8639 / 1000),
|
||||||
|
|||||||
65
deploy.sh
Executable file
65
deploy.sh
Executable file
@@ -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_NAME} als :latest..."
|
||||||
|
docker buildx imagetools create \
|
||||||
|
-t "${REGISTRY}/${IMAGE_NAME}: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 ""
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wetter_1",
|
"name": "wetter_1",
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"author": "rxf",
|
"author": "rxf",
|
||||||
|
|||||||
76
wetter.js
76
wetter.js
@@ -18,7 +18,9 @@ import { readArchiveSince, connectStation, fetchLoopData } from "./davis.js";
|
|||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const DB_PATH = process.env.DB_PATH ?? path.join(__dirname, "wetter.db");
|
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 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;
|
const POST_URL = process.env.POST_URL ?? null;
|
||||||
|
const COLLECTOR_API_KEY = process.env.COLLECTOR_API_KEY;
|
||||||
|
|
||||||
// ── Hilfsfunktionen ────────────────────────────────────────────────────────
|
// ── Hilfsfunktionen ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -28,6 +30,54 @@ function log(msg) { console.log (`[${fmt24h(new Date())}] ${msg}`); }
|
|||||||
function warn(msg) { console.warn(`[${fmt24h(new Date())}] WARN ${msg}`); }
|
function warn(msg) { console.warn(`[${fmt24h(new Date())}] WARN ${msg}`); }
|
||||||
function err(msg) { console.error(`[${fmt24h(new Date())}] ERROR ${msg}`); }
|
function err(msg) { console.error(`[${fmt24h(new Date())}] ERROR ${msg}`); }
|
||||||
|
|
||||||
|
function postData(data) {
|
||||||
|
if (!POST_URL) return;
|
||||||
|
const headers = { "Content-Type": "application/json" };
|
||||||
|
if (COLLECTOR_API_KEY) headers["X-API-Key"] = COLLECTOR_API_KEY;
|
||||||
|
fetch(POST_URL, { method: "POST", headers, body: JSON.stringify(data) })
|
||||||
|
.then(async res => {
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
warn(`POST ${res.status} ${res.statusText}: ${text.slice(0, 200)}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => warn("POST fehlgeschlagen: " + e.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 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 ───────────────────────────────────────────────────────
|
// ── Archiv nachladen ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function catchUpArchive(db) {
|
async function catchUpArchive(db) {
|
||||||
@@ -55,29 +105,39 @@ async function catchUpArchive(db) {
|
|||||||
|
|
||||||
const inserted = insertRecords(db, records, "archive");
|
const inserted = insertRecords(db, records, "archive");
|
||||||
log(`Archiv: ${inserted} neue Datensätze gespeichert (${records.length} empfangen).`);
|
log(`Archiv: ${inserted} neue Datensätze gespeichert (${records.length} empfangen).`);
|
||||||
|
|
||||||
|
for (const r of records) postData(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── LOOP-Schleife ──────────────────────────────────────────────────────────
|
// ── LOOP-Schleife ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
async function runLoop(db) {
|
async function runLoop(db) {
|
||||||
let station = null;
|
let station = null;
|
||||||
|
const buffer = [];
|
||||||
|
let lastDbWrite = Date.now();
|
||||||
|
|
||||||
async function connect() {
|
async function connect() {
|
||||||
station = await connectStation();
|
station = await connectStation();
|
||||||
log("Verbunden mit Wetterstation.");
|
log("Verbunden mit Davis-Console.");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tick() {
|
async function tick() {
|
||||||
try {
|
try {
|
||||||
const data = await fetchLoopData(station);
|
const data = await fetchLoopData(station);
|
||||||
insertRecord(db, data, "loop");
|
buffer.push(data);
|
||||||
if (POST_URL) {
|
|
||||||
fetch(POST_URL, {
|
postData(data);
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
const now = Date.now();
|
||||||
body: JSON.stringify(data),
|
if (now - lastDbWrite >= DB_INTERVAL_MS) {
|
||||||
}).catch(e => warn("POST fehlgeschlagen: " + e.message));
|
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(
|
log(
|
||||||
`Außen: ${data.tempOut?.toFixed(1)}°C ` +
|
`Außen: ${data.tempOut?.toFixed(1)}°C ` +
|
||||||
`InnenT: ${data.tempIn?.toFixed(1)}°C ` +
|
`InnenT: ${data.tempIn?.toFixed(1)}°C ` +
|
||||||
|
|||||||
Reference in New Issue
Block a user