Compare commits
2 Commits
fc8c7071f8
...
6ae153ce10
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ae153ce10 | |||
| c630ef50a7 |
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 ""
|
||||||
64
wetter.js
64
wetter.js
@@ -18,6 +18,7 @@ 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;
|
||||||
|
|
||||||
// ── Hilfsfunktionen ────────────────────────────────────────────────────────
|
// ── 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 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}`); }
|
||||||
|
|
||||||
|
// ── 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 +90,54 @@ 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).`);
|
||||||
|
|
||||||
|
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 ──────────────────────────────────────────────────────────
|
// ── 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) {
|
if (POST_URL) {
|
||||||
|
console.log(`Poste zu ${POST_URL}`)
|
||||||
fetch(POST_URL, {
|
fetch(POST_URL, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
}).catch(e => warn("POST fehlgeschlagen: " + e.message));
|
}).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(
|
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