/*********************************************** * EQA 300 ioBroker → Traccar (OwnTracks über HTTP, Port 5144) * sendet NUR bei Positionsänderung + mit Speed + Battery * jetzt mit setInterval() statt schedule() ***********************************************/ 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 = "EQA"; // in Traccar muss das Gerät so heißen! // === ioBroker-Datenpunkte === const LAT_STATE = "mercedesme.0.W1N9N0KB9TJ192372.state.positionLat.doubleValue"; const LON_STATE = "mercedesme.0.W1N9N0KB9TJ192372.state.positionLong.doubleValue"; const SPD_STATE = "mercedesme.0.W1N9N0KB9TJ192372.state.speedUnitFromIC.doubleValue"; const BAT_STATE = "mercedesme.0.W1N9N0KB9TJ192372.state.soc.displayValue"; // === Sende- und Bewegungsparameter === const SEND_INTERVAL_SECONDS = 180; // wie oft prüfen (hier: alle 3 Minuten) const MIN_DISTANCE_METERS = 15; // minimale Bewegung für neue Übertragung // interne Speicherung der letzten Position let lastLat = null; let lastLon = null; // === Hilfsfunktion: Haversine-Distanz (Meter) === function distanceMeters(lat1, lon1, lat2, lon2) { const R = 6371000; // Erdradius in Metern 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)); } // === Hauptfunktion === function sendOwntracks(force = false) { const lat = getState(LAT_STATE)?.val; const lon = getState(LON_STATE)?.val; const rawSpeed = getState(SPD_STATE)?.val; const rawBatt = getState(BAT_STATE)?.val; const speed = rawSpeed !== undefined && rawSpeed !== null ? Number(rawSpeed) : NaN; const batt = rawBatt !== undefined && rawBatt !== null ? Number(rawBatt) : NaN; if (lat == null || lon == null) { log("OwnTracks→Traccar: keine gültigen Koordinaten – sende nicht", "warn"); return; } // Bewegung prüfen 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; } } const tst = Math.floor(Date.now() / 1000); // === OwnTracks-kompatibles JSON === const data = { "_type": "location", "lat": lat, "lon": lon, "tid": DEVICE_TID, "tst": tst, "acc": 5 }; if (!Number.isNaN(speed)) data.vel = speed; if (!Number.isNaN(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}, speed=${!Number.isNaN(speed)?speed:"-"}, batt=${!Number.isNaN(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(); } // === Intervall + Trigger === // → läuft alle SEND_INTERVAL_SECONDS Sekunden setInterval(() => sendOwntracks(false), SEND_INTERVAL_SECONDS * 1000); // → sofortige Übertragung bei Koordinatenänderung on({ id: LAT_STATE, change: "ne" }, () => sendOwntracks(false)); on({ id: LON_STATE, change: "ne" }, () => sendOwntracks(false)); // → erstes Mal sofort senden sendOwntracks(true); log(`OwnTracks→Traccar-Sender gestartet (alle ${SEND_INTERVAL_SECONDS}s, nur bei Bewegung, mit Speed & Battery).`);