Files
homelab-iobroker-scripts/java-scripts/Traccar Logging: E 300 de.js

154 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/***********************************************
* E 300
* ioBroker → Traccar (OwnTracks über HTTP, Port 5144)
* sendet NUR bei Positionsänderung + mit Speed + Battery
* DEBOUNCE: Lat/Lon werden zusammengefasst, um Tearing zu vermeiden
***********************************************/
const http = require("http");
// === Grundkonfiguration ===
const TRACCAR_HOST = "192.168.2.125"; // IP oder Hostname deines Traccar
const TRACCAR_PORT = 5144; // OwnTracks-Port
const DEVICE_TID = "EC"; // in Traccar muss das Gerät so heißen!
// === ioBroker-Datenpunkte ===
const LAT_STATE = "mercedesme.0.W1KLH0JB9SA165764.state.positionLat.doubleValue";
const LON_STATE = "mercedesme.0.W1KLH0JB9SA165764.state.positionLong.doubleValue";
const SPD_STATE = "mercedesme.0.W1KLH0JB9SA165764.state.speedUnitFromIC.doubleValue";
const BAT_STATE = "mercedesme.0.W1KLH0JB9SA165764.state.soc.displayValue";
// === Sende- und Bewegungsparameter ===
const SEND_INTERVAL_SECONDS = 60; // periodische Sicherung
const MIN_DISTANCE_METERS = 5; // minimale Bewegung
const DEBOUNCE_MS = 700; // Wartezeit, um Lat/Lon gemeinsam zu lesen
const MAX_TS_SKEW_MS = 2000; // max. Zeitdifferenz zwischen Lat- und Lon-ts
// interne Speicherung der letzten gesendeten Position
let lastLat = null;
let lastLon = null;
// Debounce-Handling
let debounceTimer = null;
// Distanzberechnung (Haversine)
function distanceMeters(lat1, lon1, lat2, lon2) {
const R = 6371000;
const toRad = v => v * Math.PI / 180;
const dLat = toRad(lat2 - lat1);
const dLon = toRad(lon2 - lon1);
const a = Math.sin(dLat/2)**2 +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(dLon/2)**2;
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
function readNumeric(id) {
const s = getState(id);
if (!s) return { val: null, ts: 0 };
const v = (s.val === null || s.val === undefined) ? null : Number(s.val);
return { val: Number.isFinite(v) ? v : null, ts: s.ts || 0 };
}
// sendet **ohne** Debounce mit den aktuellsten, zusammengehörigen Werten
function sendOwntracks(force = false) {
const { val: lat, ts: tsLat } = readNumeric(LAT_STATE);
const { val: lon, ts: tsLon } = readNumeric(LON_STATE);
const { val: speed } = readNumeric(SPD_STATE);
const { val: batt } = readNumeric(BAT_STATE);
if (lat == null || lon == null) {
log("OwnTracks→Traccar: ungültige Koordinaten sende nicht", "warn");
return;
}
// Prüfe, ob Lat/Lon zeitlich „zusammen gehören“
const skew = Math.abs(tsLat - tsLon);
if (!force && skew > MAX_TS_SKEW_MS) {
// Lat/Lon kamen zeitversetzt rein → nochmal kurz warten/sammeln
log(`OwnTracks→Traccar: Lat/Lon ts-skew ${skew}ms > ${MAX_TS_SKEW_MS}ms sende später.`, "info");
scheduleDebounced(); // neu sammeln
return;
}
// Bewegung checken
if (!force && lastLat !== null && lastLon !== null) {
const dist = distanceMeters(lastLat, lastLon, lat, lon);
if (dist < MIN_DISTANCE_METERS) {
log(`OwnTracks→Traccar: keine relevante Bewegung (${dist.toFixed(1)} m) nichts gesendet.`);
return;
}
// Optionaler Ausreißer-Filter: Distanz absurd groß? (z.B. >5 km seit letztem Punkt).
if (dist > 5000) {
log(`OwnTracks→Traccar: Ausreißer verworfen (${dist.toFixed(0)} m).`, "warn");
return;
}
}
const tst = Math.floor(Date.now() / 1000);
const data = {
"_type": "location",
"lat": lat,
"lon": lon,
"tid": DEVICE_TID,
"tst": tst,
"acc": 5
};
if (Number.isFinite(speed)) data.vel = speed;
if (Number.isFinite(batt)) data.batt = batt;
const payload = JSON.stringify(data);
const options = {
host: TRACCAR_HOST,
port: TRACCAR_PORT,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload),
"Connection": "close"
}
};
const req = http.request(options, res => {
let body = "";
res.on("data", chunk => body += chunk);
res.on("end", () => {
log(`OwnTracks→Traccar: gesendet lat=${lat}, lon=${lon}, status=${res.statusCode}, vel=${Number.isFinite(speed)?speed:"-"}, batt=${Number.isFinite(batt)?batt:"-"}`);
if (body && body.trim()) log("OwnTracks→Traccar: Antwort: " + body);
lastLat = lat;
lastLon = lon;
});
});
req.on("error", err => log("OwnTracks→Traccar: HTTP-Fehler: " + err, "error"));
req.write(payload);
req.end();
}
// Debounce-Planer: sammelt Lat+Lon kurz, bevor gesendet wird
function scheduleDebounced() {
if (debounceTimer) {
clearTimeout(debounceTimer);
debounceTimer = null;
}
debounceTimer = setTimeout(() => {
debounceTimer = null;
sendOwntracks(false);
}, DEBOUNCE_MS);
}
// === Intervall + Trigger ===
// periodischer Sicherungs-Check (falls mal kein Trigger käme)
setInterval(() => sendOwntracks(false), SEND_INTERVAL_SECONDS * 1000);
// bei Änderung von Lat ODER Lon nur debouncen (nicht sofort senden)
on({ id: LAT_STATE, change: "ne" }, scheduleDebounced);
on({ id: LON_STATE, change: "ne" }, scheduleDebounced);
// erstes Mal sofort senden
sendOwntracks(true);
log(`OwnTracks→Traccar-Sender gestartet (Debounce ${DEBOUNCE_MS}ms, max ts-skew ${MAX_TS_SKEW_MS}ms, Δdist ≥ ${MIN_DISTANCE_METERS}m, Intervall ${SEND_INTERVAL_SECONDS}s).`);