NEWS
Life360 NextGeneration
-
Kennt jemand ne Lösung um zu den Koordinaten den Ort automatisch ermitteln zu lassen?
Also man übergibt den Standort und bekommt die Adresse oder z.B. "Rewe Markt" oder sowas zurück?
Wäre ne nette Ergänzung
UPDATE:
Hab das grad ne Idee.... Ich probier das mal und meld mich. Weiß aber nicht, ob ich das heute schaffe....UPDATE 2:
hab was, läuft grad der Test -
Kennt jemand ne Lösung um zu den Koordinaten den Ort automatisch ermitteln zu lassen?
Also man übergibt den Standort und bekommt die Adresse oder z.B. "Rewe Markt" oder sowas zurück?
Wäre ne nette Ergänzung
UPDATE:
Hab das grad ne Idee.... Ich probier das mal und meld mich. Weiß aber nicht, ob ich das heute schaffe....UPDATE 2:
hab was, läuft grad der Test -
ich muss gestehen, ich verstehe die App noch nicht... :/
Wann werden neue Standorte registriert als ein häufig besuchter Ort? Oder muss ich alle "relevanten" Standorte in der App oder im Adapter erfassen? Wie kommen "Location" in den Adapter von weiteren Anwendern (zB von meiner Frau, ohne das ich vor Ort gewesen oder sein muss
Ich habe zB die Arbeitsstätte meiner Frau im Adapter angelegt (so wie meine auch), allerdings wird der Standort meiner Frau bzw dem Handy dort nicht bzw als "noch nicht registriert" erfasstich habe nen Knoten im Kopf ;)
-
hab mal ChatGPT genutzt (ganz nach dem Vorbild eines hier auch aktiven Mitglieds
)
Herausgekommen ist ein Script, dass aus den Koordinaten zum einen ein Lookup nach der Adresse macht und schaut, ob es im näheren Umkreis einen benannten Ort gibt.
Das ganze ist so gemacht, dass die Abfrageintervalle ausreichend groß sind und auch nur bei Positionsänderungen ein Lookup gemacht wird, damit die Dienste keinen Streß machen.
Das ganze wird dann in Datenpunkten gespeichert und kann in der VIS genutzt werden.
Ist jetzt die 2. Version, hat sicher noch Verbesserungspotential.
Hab mal meine Laufrunde für nen Test benutzt:

Der erste Standort ist der aus dem Adapter, die Adresse und der Name darunter aus dem Script. Wenn kein Ort gefunden wird ist das entsprechende Feld leer
in den DPs sieht es so aus:

Und hier ist das Script.
ACHTUNG: Bitte im USER_AGENT eine echte Kontaktmöglichkeit eintragen. Sonst kann es sein, dass die Dienste Stress machen....Gibt auch diverse Parameter, die man anpassen kann.
Ihr müsst halt die Datenpunkte im Script entsprechend euren DPs anpassen...
Ich werde auch mal schauen, wie gut man das Script auf mehrere Personen skalieren kann./************************************************************ * Oliver Standort aus Life360 -> menschenlesbarer Text * * Ausgabe: * 0_userdata.0.VIS.Position.Oliver.Standort_Oliver * * Beispiel-Ergebnis: * "REWE, Musterstraße 1, 12345 Musterstadt" * oder * "Musterstraße 1, 12345 Musterstadt" * * Voraussetzungen: * - JavaScript-Adapter in ioBroker * - Internetzugriff * * WICHTIG: * Bitte im USER_AGENT eine echte Kontaktmöglichkeit eintragen. ************************************************************/ const LAT_STATE = 'life360ng.0.people.bxxxxxxe.latitude'; const LON_STATE = 'life360ng.0.people.bxxxxxa7e.longitude'; const TARGET_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver'; // Optional: zusätzliche Debug-/Hilfsstates const ADDRESS_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_Adresse'; const POI_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_POI'; const LASTLAT_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_LastLat'; const LASTLON_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_LastLon'; const LASTTS_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_LastLookupTs'; // Einstellungen const MIN_INTERVAL_MS = 60 * 1000; // max. 1 Lookup pro Minute const MIN_DISTANCE_M = 40; // nur neu abfragen, wenn mind. 40 m Bewegung const DEBOUNCE_MS = 5000; // 5 Sekunden warten, falls lat/lon nacheinander aktualisiert werden const POI_RADIUS_M = 15; // POI-Suche im Umkreis von 40 m const SECOND_TRY_RADIUS = 25; // optionaler zweiter Radius, falls bei 40 m nichts gefunden const GAP_BETWEEN_CALLS = 1500; // 1,5 s Pause zwischen Nominatim und Overpass // WICHTIG: bitte anpassen const USER_AGENT = 'ioBroker-Standortscript/1.0 (Kontakt: deine@mailadresse.de)'; let debounceTimer = null; let lookupRunning = false; /* --------------------------------------------------------- * Initiale States anlegen * --------------------------------------------------------- */ createStateIfMissing(TARGET_STATE, '', 'string', 'Oliver Standort'); createStateIfMissing(ADDRESS_STATE, '', 'string', 'Oliver Adresse'); createStateIfMissing(POI_STATE, '', 'string', 'Oliver POI'); createStateIfMissing(LASTLAT_STATE, null, 'number', 'Oliver letzte Lookup-Latitude'); createStateIfMissing(LASTLON_STATE, null, 'number', 'Oliver letzte Lookup-Longitude'); createStateIfMissing(LASTTS_STATE, 0, 'number', 'Oliver letzter Lookup-Zeitstempel'); /* --------------------------------------------------------- * Trigger * --------------------------------------------------------- */ on({id: LAT_STATE, change: 'ne'}, scheduleLookup); on({id: LON_STATE, change: 'ne'}, scheduleLookup); // optional einmal beim Scriptstart prüfen scheduleLookup(); /* --------------------------------------------------------- * Hauptlogik * --------------------------------------------------------- */ function scheduleLookup() { if (debounceTimer) clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { runLookup().catch(err => log('Standortscript Fehler: ' + err, 'error')); }, DEBOUNCE_MS); } async function runLookup() { if (lookupRunning) { log('Lookup läuft bereits, überspringe parallelen Aufruf.', 'debug'); return; } const lat = parseFloat(getState(LAT_STATE).val); const lon = parseFloat(getState(LON_STATE).val); if (!isFinite(lat) || !isFinite(lon)) { log('Ungültige Koordinaten, Lookup abgebrochen.', 'warn'); return; } if (!shouldLookup(lat, lon)) { log('Lookup übersprungen (Cooldown oder Bewegung zu gering).', 'debug'); return; } lookupRunning = true; try { // 1) Adresse holen const addressInfo = await getAddressFromNominatim(lat, lon); const addressText = buildAddressText(addressInfo); setState(TARGET_STATE, addressText || 'Unbekannter Standort', true); setState(ADDRESS_STATE, addressText || '', true); setState(POI_STATE, '', true); // Lookup-Metadaten sofort speichern, sobald die Adresse erfolgreich ermittelt wurde setState(LASTLAT_STATE, lat, true); setState(LASTLON_STATE, lon, true); setState(LASTTS_STATE, Date.now(), true); // 2) bewusste Pause zwischen den Requests await sleep(GAP_BETWEEN_CALLS); // 3) generischen POI im Umkreis suchen let poi = await getNearbyNamedPoi(lat, lon, POI_RADIUS_M); // optional zweiter Versuch mit größerem Radius if (!poi) { poi = await getNearbyNamedPoi(lat, lon, SECOND_TRY_RADIUS); } let finalText = addressText || 'Unbekannter Standort'; if (poi && poi.name) { setState(POI_STATE, poi.name, true); // Doppelte Infos vermeiden if (addressText && !addressText.toLowerCase().includes((poi.name || '').toLowerCase())) { finalText = poi.name + ', ' + addressText; } else { finalText = poi.name; } } setState(TARGET_STATE, finalText, true); log('Standort aktualisiert: ' + finalText, 'info'); } catch (err) { log('Fehler beim Standort-Lookup: ' + err, 'error'); } finally { lookupRunning = false; } } /* --------------------------------------------------------- * Entscheiden, ob Lookup nötig ist * --------------------------------------------------------- */ function shouldLookup(lat, lon) { const lastTs = Number(getState(LASTTS_STATE).val || 0); const lastLat = parseFloat(getState(LASTLAT_STATE).val); const lastLon = parseFloat(getState(LASTLON_STATE).val); const now = Date.now(); if (lastTs && (now - lastTs) < MIN_INTERVAL_MS) { return false; } if (isFinite(lastLat) && isFinite(lastLon)) { const dist = distanceMeters(lastLat, lastLon, lat, lon); if (dist < MIN_DISTANCE_M) { return false; } } return true; } /* --------------------------------------------------------- * Nominatim Reverse Geocoding * --------------------------------------------------------- */ async function getAddressFromNominatim(lat, lon) { const url = 'https://nominatim.openstreetmap.org/reverse' + '?format=jsonv2' + '&lat=' + encodeURIComponent(lat) + '&lon=' + encodeURIComponent(lon) + '&addressdetails=1' + '&zoom=18'; const response = await httpGetAsync(url, { 'User-Agent': USER_AGENT, 'Accept': 'application/json' }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Nominatim HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Nominatim JSON konnte nicht geparst werden'); } return data; } function buildAddressText(data) { if (!data) return ''; const a = data.address || {}; const road = a.road || a.pedestrian || a.footway || a.path || a.cycleway || ''; const houseNumber = a.house_number || ''; const postcode = a.postcode || ''; const city = a.city || a.town || a.village || a.hamlet || a.municipality || ''; let result = ''; if (road) { result += road; if (houseNumber) result += ' ' + houseNumber; } if (postcode || city) { if (result) result += ', '; result += [postcode, city].filter(Boolean).join(' '); } // Fallback auf display_name, falls die Einzelteile fehlen if (!result && data.display_name) { result = data.display_name; } return cleanText(result); } /* --------------------------------------------------------- * Overpass: benannten Ort/POI in der Nähe finden * --------------------------------------------------------- */ async function getNearbyNamedPoi(lat, lon, radius) { // Priorisiert benannte Objekte, die für Menschen sinnvoll sind const query = ` [out:json][timeout:10]; ( nwr(around:${radius},${lat},${lon})["name"]["shop"]; nwr(around:${radius},${lat},${lon})["name"]["amenity"]; nwr(around:${radius},${lat},${lon})["name"]["tourism"]; nwr(around:${radius},${lat},${lon})["name"]["leisure"]; nwr(around:${radius},${lat},${lon})["name"]["office"]; nwr(around:${radius},${lat},${lon})["name"]["building"]; ); out center tags; `; const response = await httpPostAsync( 'https://overpass-api.de/api/interpreter', query, { 'User-Agent': USER_AGENT, 'Content-Type': 'text/plain; charset=utf-8', 'Accept': 'application/json' } ); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Overpass HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Overpass JSON konnte nicht geparst werden'); } if (!data.elements || !data.elements.length) { return null; } // nächsten sinnvollen Treffer bestimmen const candidates = data.elements .map(el => { const cLat = el.lat || (el.center && el.center.lat); const cLon = el.lon || (el.center && el.center.lon); const name = el.tags && el.tags.name ? String(el.tags.name).trim() : ''; const dist = (isFinite(cLat) && isFinite(cLon)) ? distanceMeters(lat, lon, cLat, cLon) : 999999; return { name: cleanText(name), tags: el.tags || {}, lat: cLat, lon: cLon, dist: dist }; }) .filter(x => x.name); if (!candidates.length) return null; // etwas priorisieren: shop/amenity/tourism/leisure vor building candidates.sort((a, b) => { const pa = poiPriority(a.tags); const pb = poiPriority(b.tags); if (pa !== pb) return pa - pb; return a.dist - b.dist; }); return candidates[0]; } function poiPriority(tags) { if (tags.shop) return 1; if (tags.amenity) return 2; if (tags.tourism) return 3; if (tags.leisure) return 4; if (tags.office) return 5; if (tags.building) return 9; return 99; } /* --------------------------------------------------------- * HTTP-Helfer * --------------------------------------------------------- */ function httpGetAsync(url, headers) { return new Promise((resolve, reject) => { httpGet(url, { headers: headers, timeout: 15000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function httpPostAsync(url, body, headers) { return new Promise((resolve, reject) => { httpPost(url, body, { headers: headers, timeout: 20000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /* --------------------------------------------------------- * Mathe / Hilfsfunktionen * --------------------------------------------------------- */ function distanceMeters(lat1, lon1, lat2, lon2) { const R = 6371000; const toRad = deg => deg * Math.PI / 180; const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function cleanText(str) { return String(str || '') .replace(/\s+/g, ' ') .replace(/\s+,/g, ',') .trim(); } /* --------------------------------------------------------- * State-Helfer * --------------------------------------------------------- */ function createStateIfMissing(id, def, type, name) { if (!existsState(id)) { createState(id, def, { name: name, type: type, role: 'text', read: true, write: true }); } }Das ganz hat komplett ChatGPT gemacht... Bin durchaus sehr begeistert...... Meine JS Kenntnisse sind nur sehr rudimentär über "nicht vorhanden"....
-
hab mal ChatGPT genutzt (ganz nach dem Vorbild eines hier auch aktiven Mitglieds
)
Herausgekommen ist ein Script, dass aus den Koordinaten zum einen ein Lookup nach der Adresse macht und schaut, ob es im näheren Umkreis einen benannten Ort gibt.
Das ganze ist so gemacht, dass die Abfrageintervalle ausreichend groß sind und auch nur bei Positionsänderungen ein Lookup gemacht wird, damit die Dienste keinen Streß machen.
Das ganze wird dann in Datenpunkten gespeichert und kann in der VIS genutzt werden.
Ist jetzt die 2. Version, hat sicher noch Verbesserungspotential.
Hab mal meine Laufrunde für nen Test benutzt:

Der erste Standort ist der aus dem Adapter, die Adresse und der Name darunter aus dem Script. Wenn kein Ort gefunden wird ist das entsprechende Feld leer
in den DPs sieht es so aus:

Und hier ist das Script.
ACHTUNG: Bitte im USER_AGENT eine echte Kontaktmöglichkeit eintragen. Sonst kann es sein, dass die Dienste Stress machen....Gibt auch diverse Parameter, die man anpassen kann.
Ihr müsst halt die Datenpunkte im Script entsprechend euren DPs anpassen...
Ich werde auch mal schauen, wie gut man das Script auf mehrere Personen skalieren kann./************************************************************ * Oliver Standort aus Life360 -> menschenlesbarer Text * * Ausgabe: * 0_userdata.0.VIS.Position.Oliver.Standort_Oliver * * Beispiel-Ergebnis: * "REWE, Musterstraße 1, 12345 Musterstadt" * oder * "Musterstraße 1, 12345 Musterstadt" * * Voraussetzungen: * - JavaScript-Adapter in ioBroker * - Internetzugriff * * WICHTIG: * Bitte im USER_AGENT eine echte Kontaktmöglichkeit eintragen. ************************************************************/ const LAT_STATE = 'life360ng.0.people.bxxxxxxe.latitude'; const LON_STATE = 'life360ng.0.people.bxxxxxa7e.longitude'; const TARGET_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver'; // Optional: zusätzliche Debug-/Hilfsstates const ADDRESS_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_Adresse'; const POI_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_POI'; const LASTLAT_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_LastLat'; const LASTLON_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_LastLon'; const LASTTS_STATE = '0_userdata.0.VIS.Position.Oliver.Standort_Oliver_LastLookupTs'; // Einstellungen const MIN_INTERVAL_MS = 60 * 1000; // max. 1 Lookup pro Minute const MIN_DISTANCE_M = 40; // nur neu abfragen, wenn mind. 40 m Bewegung const DEBOUNCE_MS = 5000; // 5 Sekunden warten, falls lat/lon nacheinander aktualisiert werden const POI_RADIUS_M = 15; // POI-Suche im Umkreis von 40 m const SECOND_TRY_RADIUS = 25; // optionaler zweiter Radius, falls bei 40 m nichts gefunden const GAP_BETWEEN_CALLS = 1500; // 1,5 s Pause zwischen Nominatim und Overpass // WICHTIG: bitte anpassen const USER_AGENT = 'ioBroker-Standortscript/1.0 (Kontakt: deine@mailadresse.de)'; let debounceTimer = null; let lookupRunning = false; /* --------------------------------------------------------- * Initiale States anlegen * --------------------------------------------------------- */ createStateIfMissing(TARGET_STATE, '', 'string', 'Oliver Standort'); createStateIfMissing(ADDRESS_STATE, '', 'string', 'Oliver Adresse'); createStateIfMissing(POI_STATE, '', 'string', 'Oliver POI'); createStateIfMissing(LASTLAT_STATE, null, 'number', 'Oliver letzte Lookup-Latitude'); createStateIfMissing(LASTLON_STATE, null, 'number', 'Oliver letzte Lookup-Longitude'); createStateIfMissing(LASTTS_STATE, 0, 'number', 'Oliver letzter Lookup-Zeitstempel'); /* --------------------------------------------------------- * Trigger * --------------------------------------------------------- */ on({id: LAT_STATE, change: 'ne'}, scheduleLookup); on({id: LON_STATE, change: 'ne'}, scheduleLookup); // optional einmal beim Scriptstart prüfen scheduleLookup(); /* --------------------------------------------------------- * Hauptlogik * --------------------------------------------------------- */ function scheduleLookup() { if (debounceTimer) clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { runLookup().catch(err => log('Standortscript Fehler: ' + err, 'error')); }, DEBOUNCE_MS); } async function runLookup() { if (lookupRunning) { log('Lookup läuft bereits, überspringe parallelen Aufruf.', 'debug'); return; } const lat = parseFloat(getState(LAT_STATE).val); const lon = parseFloat(getState(LON_STATE).val); if (!isFinite(lat) || !isFinite(lon)) { log('Ungültige Koordinaten, Lookup abgebrochen.', 'warn'); return; } if (!shouldLookup(lat, lon)) { log('Lookup übersprungen (Cooldown oder Bewegung zu gering).', 'debug'); return; } lookupRunning = true; try { // 1) Adresse holen const addressInfo = await getAddressFromNominatim(lat, lon); const addressText = buildAddressText(addressInfo); setState(TARGET_STATE, addressText || 'Unbekannter Standort', true); setState(ADDRESS_STATE, addressText || '', true); setState(POI_STATE, '', true); // Lookup-Metadaten sofort speichern, sobald die Adresse erfolgreich ermittelt wurde setState(LASTLAT_STATE, lat, true); setState(LASTLON_STATE, lon, true); setState(LASTTS_STATE, Date.now(), true); // 2) bewusste Pause zwischen den Requests await sleep(GAP_BETWEEN_CALLS); // 3) generischen POI im Umkreis suchen let poi = await getNearbyNamedPoi(lat, lon, POI_RADIUS_M); // optional zweiter Versuch mit größerem Radius if (!poi) { poi = await getNearbyNamedPoi(lat, lon, SECOND_TRY_RADIUS); } let finalText = addressText || 'Unbekannter Standort'; if (poi && poi.name) { setState(POI_STATE, poi.name, true); // Doppelte Infos vermeiden if (addressText && !addressText.toLowerCase().includes((poi.name || '').toLowerCase())) { finalText = poi.name + ', ' + addressText; } else { finalText = poi.name; } } setState(TARGET_STATE, finalText, true); log('Standort aktualisiert: ' + finalText, 'info'); } catch (err) { log('Fehler beim Standort-Lookup: ' + err, 'error'); } finally { lookupRunning = false; } } /* --------------------------------------------------------- * Entscheiden, ob Lookup nötig ist * --------------------------------------------------------- */ function shouldLookup(lat, lon) { const lastTs = Number(getState(LASTTS_STATE).val || 0); const lastLat = parseFloat(getState(LASTLAT_STATE).val); const lastLon = parseFloat(getState(LASTLON_STATE).val); const now = Date.now(); if (lastTs && (now - lastTs) < MIN_INTERVAL_MS) { return false; } if (isFinite(lastLat) && isFinite(lastLon)) { const dist = distanceMeters(lastLat, lastLon, lat, lon); if (dist < MIN_DISTANCE_M) { return false; } } return true; } /* --------------------------------------------------------- * Nominatim Reverse Geocoding * --------------------------------------------------------- */ async function getAddressFromNominatim(lat, lon) { const url = 'https://nominatim.openstreetmap.org/reverse' + '?format=jsonv2' + '&lat=' + encodeURIComponent(lat) + '&lon=' + encodeURIComponent(lon) + '&addressdetails=1' + '&zoom=18'; const response = await httpGetAsync(url, { 'User-Agent': USER_AGENT, 'Accept': 'application/json' }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Nominatim HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Nominatim JSON konnte nicht geparst werden'); } return data; } function buildAddressText(data) { if (!data) return ''; const a = data.address || {}; const road = a.road || a.pedestrian || a.footway || a.path || a.cycleway || ''; const houseNumber = a.house_number || ''; const postcode = a.postcode || ''; const city = a.city || a.town || a.village || a.hamlet || a.municipality || ''; let result = ''; if (road) { result += road; if (houseNumber) result += ' ' + houseNumber; } if (postcode || city) { if (result) result += ', '; result += [postcode, city].filter(Boolean).join(' '); } // Fallback auf display_name, falls die Einzelteile fehlen if (!result && data.display_name) { result = data.display_name; } return cleanText(result); } /* --------------------------------------------------------- * Overpass: benannten Ort/POI in der Nähe finden * --------------------------------------------------------- */ async function getNearbyNamedPoi(lat, lon, radius) { // Priorisiert benannte Objekte, die für Menschen sinnvoll sind const query = ` [out:json][timeout:10]; ( nwr(around:${radius},${lat},${lon})["name"]["shop"]; nwr(around:${radius},${lat},${lon})["name"]["amenity"]; nwr(around:${radius},${lat},${lon})["name"]["tourism"]; nwr(around:${radius},${lat},${lon})["name"]["leisure"]; nwr(around:${radius},${lat},${lon})["name"]["office"]; nwr(around:${radius},${lat},${lon})["name"]["building"]; ); out center tags; `; const response = await httpPostAsync( 'https://overpass-api.de/api/interpreter', query, { 'User-Agent': USER_AGENT, 'Content-Type': 'text/plain; charset=utf-8', 'Accept': 'application/json' } ); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Overpass HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Overpass JSON konnte nicht geparst werden'); } if (!data.elements || !data.elements.length) { return null; } // nächsten sinnvollen Treffer bestimmen const candidates = data.elements .map(el => { const cLat = el.lat || (el.center && el.center.lat); const cLon = el.lon || (el.center && el.center.lon); const name = el.tags && el.tags.name ? String(el.tags.name).trim() : ''; const dist = (isFinite(cLat) && isFinite(cLon)) ? distanceMeters(lat, lon, cLat, cLon) : 999999; return { name: cleanText(name), tags: el.tags || {}, lat: cLat, lon: cLon, dist: dist }; }) .filter(x => x.name); if (!candidates.length) return null; // etwas priorisieren: shop/amenity/tourism/leisure vor building candidates.sort((a, b) => { const pa = poiPriority(a.tags); const pb = poiPriority(b.tags); if (pa !== pb) return pa - pb; return a.dist - b.dist; }); return candidates[0]; } function poiPriority(tags) { if (tags.shop) return 1; if (tags.amenity) return 2; if (tags.tourism) return 3; if (tags.leisure) return 4; if (tags.office) return 5; if (tags.building) return 9; return 99; } /* --------------------------------------------------------- * HTTP-Helfer * --------------------------------------------------------- */ function httpGetAsync(url, headers) { return new Promise((resolve, reject) => { httpGet(url, { headers: headers, timeout: 15000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function httpPostAsync(url, body, headers) { return new Promise((resolve, reject) => { httpPost(url, body, { headers: headers, timeout: 20000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /* --------------------------------------------------------- * Mathe / Hilfsfunktionen * --------------------------------------------------------- */ function distanceMeters(lat1, lon1, lat2, lon2) { const R = 6371000; const toRad = deg => deg * Math.PI / 180; const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function cleanText(str) { return String(str || '') .replace(/\s+/g, ' ') .replace(/\s+,/g, ',') .trim(); } /* --------------------------------------------------------- * State-Helfer * --------------------------------------------------------- */ function createStateIfMissing(id, def, type, name) { if (!existsState(id)) { createState(id, def, { name: name, type: type, role: 'text', read: true, write: true }); } }Das ganz hat komplett ChatGPT gemacht... Bin durchaus sehr begeistert...... Meine JS Kenntnisse sind nur sehr rudimentär über "nicht vorhanden"....
-
ich muss gestehen, ich verstehe die App noch nicht... :/
Wann werden neue Standorte registriert als ein häufig besuchter Ort? Oder muss ich alle "relevanten" Standorte in der App oder im Adapter erfassen? Wie kommen "Location" in den Adapter von weiteren Anwendern (zB von meiner Frau, ohne das ich vor Ort gewesen oder sein muss
Ich habe zB die Arbeitsstätte meiner Frau im Adapter angelegt (so wie meine auch), allerdings wird der Standort meiner Frau bzw dem Handy dort nicht bzw als "noch nicht registriert" erfasstich habe nen Knoten im Kopf ;)
ich muss gestehen, ich verstehe die App noch nicht... :/
Wann werden neue Standorte registriert als ein häufig besuchter Ort? Oder muss ich alle "relevanten" Standorte in der App oder im Adapter erfassen? Wie kommen "Location" in den Adapter von weiteren Anwendern (zB von meiner Frau, ohne das ich vor Ort gewesen oder sein muss
Ich habe zB die Arbeitsstätte meiner Frau im Adapter angelegt (so wie meine auch), allerdings wird der Standort meiner Frau bzw dem Handy dort nicht bzw als "noch nicht registriert" erfasstich habe nen Knoten im Kopf ;)
Die App kann ich dir hier nicht erklären, das würde den Rahmen sprengen.
Von allein wird da kein Standort angelegt, das muss man händisch machen. Entweder in der App, dann kommen die Orte auch in den ioBroker oder aber direkt im ioBroker, die gehen aber nicht in die App über! Der Adapter ist eine Einbahnstraße und ließt nur die App aus.
-
Hier ne version für mehrere Personen, aber noch nicht vollständig getestet...
/************************************************************ * Mehrpersonen-Standortscript für ioBroker + Life360 * * Für jede Person: * - Reverse Geocoding per Nominatim * - optional POI-Suche per Overpass * - Ausgabe in eigenen States * * WICHTIG: * USER_AGENT bitte mit echter Kontaktmöglichkeit anpassen. ************************************************************/ const PERSONS = [ { key: 'Oliver', latState: 'life360ng.0.people.bxxxe.latitude', lonState: 'life360ng.0.people.b9xxxxx7e.longitude', targetState: '0_userdata.0.VIS.Position.Standort_Oliver', baseState: '0_userdata.0.VIS.Position.Oliver' }, { key: 'Hannah', latState: 'life360ng.0.people.f7xxx9d.latitude', lonState: 'life360ng.0.people.f7fxxx9d.longitude', targetState: '0_userdata.0.VIS.Position.Standort_Hannah', baseState: '0_userdata.0.VIS.Position.Hannah' }, { key: 'Marleen', latState: 'life360ng.0.people.88xxxea.latitude', lonState: 'life360ng.0.people.88xxxaea.longitude', targetState: '0_userdata.0.VIS.Position.Standort_Marleen', baseState: '0_userdata.0.VIS.Position.Marleen' } ]; // Einstellungen const MIN_INTERVAL_MS = 60 * 1000; // max. 1 Lookup pro Person pro Minute const MIN_DISTANCE_M = 40; // nur neu abfragen, wenn mind. 40 m Bewegung const DEBOUNCE_MS = 5000; // 5 Sekunden warten, falls lat/lon nacheinander aktualisiert werden const POI_RADIUS_M = 15; // erste POI-Suche const SECOND_TRY_RADIUS = 25; // optionaler zweiter Versuch const MAX_ACCEPTED_POI_DISTANCE = 20; // nur wirklich nahe Treffer übernehmen const GAP_BETWEEN_CALLS = 1500; // Pause zwischen Nominatim und Overpass // WICHTIG: anpassen const USER_AGENT = 'ioBroker-Standortscript/1.0 (Kontakt: deine-mail@example.com)'; // Laufzeitstatus pro Person const runtime = {}; /* --------------------------------------------------------- * Initialisierung * --------------------------------------------------------- */ for (const person of PERSONS) { person.addressState = `${person.baseState}.Standort_${person.key}_Adresse`; person.poiState = `${person.baseState}.Standort_${person.key}_POI`; person.lastLatState = `${person.baseState}.Standort_${person.key}_LastLat`; person.lastLonState = `${person.baseState}.Standort_${person.key}_LastLon`; person.lastTsState = `${person.baseState}.Standort_${person.key}_LastLookupTs`; runtime[person.key] = { debounceTimer: null, lookupRunning: false }; createStateIfMissing(person.targetState, '', 'string', `${person.key} Standort`); createStateIfMissing(person.addressState, '', 'string', `${person.key} Adresse`); createStateIfMissing(person.poiState, '', 'string', `${person.key} POI`); createStateIfMissing(person.lastLatState, 0, 'number', `${person.key} letzte Lookup-Latitude`); createStateIfMissing(person.lastLonState, 0, 'number', `${person.key} letzte Lookup-Longitude`); createStateIfMissing(person.lastTsState, 0, 'number', `${person.key} letzter Lookup-Zeitstempel`); on({ id: person.latState, change: 'ne' }, () => scheduleLookup(person)); on({ id: person.lonState, change: 'ne' }, () => scheduleLookup(person)); scheduleLookup(person); } /* --------------------------------------------------------- * Hauptlogik * --------------------------------------------------------- */ function scheduleLookup(person) { const rt = runtime[person.key]; if (rt.debounceTimer) clearTimeout(rt.debounceTimer); rt.debounceTimer = setTimeout(() => { runLookup(person).catch(err => log(`${person.key}: Standortscript Fehler: ${err}`, 'error')); }, DEBOUNCE_MS); } async function runLookup(person) { const rt = runtime[person.key]; if (rt.lookupRunning) { log(`${person.key}: Lookup läuft bereits, überspringe parallelen Aufruf.`, 'debug'); return; } const lat = parseFloat(getState(person.latState).val); const lon = parseFloat(getState(person.lonState).val); if (!isFinite(lat) || !isFinite(lon)) { log(`${person.key}: Ungültige Koordinaten, Lookup abgebrochen.`, 'warn'); return; } if (!shouldLookup(person, lat, lon)) { log(`${person.key}: Lookup übersprungen (Cooldown oder Bewegung zu gering).`, 'debug'); return; } rt.lookupRunning = true; try { const addressInfo = await getAddressFromNominatim(lat, lon); const addressText = buildAddressText(addressInfo); setState(person.addressState, addressText || '', true); setState(person.targetState, addressText || 'Unbekannter Standort', true); setState(person.poiState, '', true); setState(person.lastLatState, lat, true); setState(person.lastLonState, lon, true); setState(person.lastTsState, Date.now(), true); await sleep(GAP_BETWEEN_CALLS); let poi = await getNearbyNamedPoi(lat, lon, POI_RADIUS_M); if (!poi) { poi = await getNearbyNamedPoi(lat, lon, SECOND_TRY_RADIUS); } let finalText = addressText || 'Unbekannter Standort'; if (poi && poi.name) { setState(person.poiState, poi.name, true); if (addressText && !addressText.toLowerCase().includes(poi.name.toLowerCase())) { finalText = poi.name + ', ' + addressText; } else { finalText = poi.name; } } setState(person.targetState, finalText, true); log(`${person.key}: Standort aktualisiert: ${finalText}`, 'info'); } catch (err) { log(`${person.key}: Fehler beim Standort-Lookup: ${err}`, 'error'); } finally { rt.lookupRunning = false; } } function shouldLookup(person, lat, lon) { const lastTsRaw = getState(person.lastTsState).val; const lastLatRaw = getState(person.lastLatState).val; const lastLonRaw = getState(person.lastLonState).val; const lastTs = Number(lastTsRaw || 0); const lastLat = parseFloat(lastLatRaw); const lastLon = parseFloat(lastLonRaw); const now = Date.now(); if (lastTs > 0 && (now - lastTs) < MIN_INTERVAL_MS) { return false; } if (isFinite(lastLat) && isFinite(lastLon) && !(lastLat === 0 && lastLon === 0)) { const dist = distanceMeters(lastLat, lastLon, lat, lon); if (dist < MIN_DISTANCE_M) { return false; } } return true; } /* --------------------------------------------------------- * Nominatim Reverse Geocoding * --------------------------------------------------------- */ async function getAddressFromNominatim(lat, lon) { const url = 'https://nominatim.openstreetmap.org/reverse' + '?format=jsonv2' + '&lat=' + encodeURIComponent(lat) + '&lon=' + encodeURIComponent(lon) + '&addressdetails=1' + '&zoom=18'; const response = await httpGetAsync(url, { 'User-Agent': USER_AGENT, 'Accept': 'application/json' }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Nominatim HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Nominatim JSON konnte nicht geparst werden'); } return data; } function buildAddressText(data) { if (!data) return ''; const a = data.address || {}; const road = a.road || a.pedestrian || a.footway || a.path || a.cycleway || ''; const houseNumber = a.house_number || ''; const postcode = a.postcode || ''; const city = a.city || a.town || a.village || a.hamlet || a.municipality || ''; let result = ''; if (road) { result += road; if (houseNumber) result += ' ' + houseNumber; } if (postcode || city) { if (result) result += ', '; result += [postcode, city].filter(Boolean).join(' '); } if (!result && data.display_name) { result = data.display_name; } return cleanText(result); } /* --------------------------------------------------------- * Overpass: benannten Ort/POI in der Nähe finden * --------------------------------------------------------- */ async function getNearbyNamedPoi(lat, lon, radius) { const query = ` [out:json][timeout:10]; ( nwr(around:${radius},${lat},${lon})["name"]["shop"]; nwr(around:${radius},${lat},${lon})["name"]["amenity"]; nwr(around:${radius},${lat},${lon})["name"]["tourism"]; nwr(around:${radius},${lat},${lon})["name"]["leisure"]; nwr(around:${radius},${lat},${lon})["name"]["office"]; ); out center tags; `; const response = await httpPostAsync( 'https://overpass-api.de/api/interpreter', query, { 'User-Agent': USER_AGENT, 'Content-Type': 'text/plain; charset=utf-8', 'Accept': 'application/json' } ); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Overpass HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Overpass JSON konnte nicht geparst werden'); } if (!data.elements || !data.elements.length) { return null; } const candidates = data.elements .map(el => { const cLat = el.lat || (el.center && el.center.lat); const cLon = el.lon || (el.center && el.center.lon); const name = el.tags && el.tags.name ? String(el.tags.name).trim() : ''; const dist = (isFinite(cLat) && isFinite(cLon)) ? distanceMeters(lat, lon, cLat, cLon) : 999999; return { name: cleanText(name), tags: el.tags || {}, dist: dist }; }) .filter(x => x.name); if (!candidates.length) return null; candidates.sort((a, b) => { const pa = poiPriority(a.tags); const pb = poiPriority(b.tags); if (pa !== pb) return pa - pb; return a.dist - b.dist; }); const best = candidates[0]; if (!best || best.dist > MAX_ACCEPTED_POI_DISTANCE) { return null; } return best; } function poiPriority(tags) { if (tags.shop) return 1; if (tags.amenity) return 2; if (tags.tourism) return 3; if (tags.leisure) return 4; if (tags.office) return 5; return 99; } /* --------------------------------------------------------- * HTTP-Helfer * --------------------------------------------------------- */ function httpGetAsync(url, headers) { return new Promise((resolve, reject) => { httpGet(url, { headers: headers, timeout: 15000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function httpPostAsync(url, body, headers) { return new Promise((resolve, reject) => { httpPost(url, body, { headers: headers, timeout: 20000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /* --------------------------------------------------------- * Hilfsfunktionen * --------------------------------------------------------- */ function distanceMeters(lat1, lon1, lat2, lon2) { const R = 6371000; const toRad = deg => deg * Math.PI / 180; const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function cleanText(str) { return String(str || '') .replace(/\s+/g, ' ') .replace(/\s+,/g, ',') .trim(); } function createStateIfMissing(id, def, type, name) { if (!existsState(id)) { createState(id, def, { name: name, type: type, role: type === 'number' ? 'value' : 'text', read: true, write: true }); } } -
Hier ne version für mehrere Personen, aber noch nicht vollständig getestet...
/************************************************************ * Mehrpersonen-Standortscript für ioBroker + Life360 * * Für jede Person: * - Reverse Geocoding per Nominatim * - optional POI-Suche per Overpass * - Ausgabe in eigenen States * * WICHTIG: * USER_AGENT bitte mit echter Kontaktmöglichkeit anpassen. ************************************************************/ const PERSONS = [ { key: 'Oliver', latState: 'life360ng.0.people.bxxxe.latitude', lonState: 'life360ng.0.people.b9xxxxx7e.longitude', targetState: '0_userdata.0.VIS.Position.Standort_Oliver', baseState: '0_userdata.0.VIS.Position.Oliver' }, { key: 'Hannah', latState: 'life360ng.0.people.f7xxx9d.latitude', lonState: 'life360ng.0.people.f7fxxx9d.longitude', targetState: '0_userdata.0.VIS.Position.Standort_Hannah', baseState: '0_userdata.0.VIS.Position.Hannah' }, { key: 'Marleen', latState: 'life360ng.0.people.88xxxea.latitude', lonState: 'life360ng.0.people.88xxxaea.longitude', targetState: '0_userdata.0.VIS.Position.Standort_Marleen', baseState: '0_userdata.0.VIS.Position.Marleen' } ]; // Einstellungen const MIN_INTERVAL_MS = 60 * 1000; // max. 1 Lookup pro Person pro Minute const MIN_DISTANCE_M = 40; // nur neu abfragen, wenn mind. 40 m Bewegung const DEBOUNCE_MS = 5000; // 5 Sekunden warten, falls lat/lon nacheinander aktualisiert werden const POI_RADIUS_M = 15; // erste POI-Suche const SECOND_TRY_RADIUS = 25; // optionaler zweiter Versuch const MAX_ACCEPTED_POI_DISTANCE = 20; // nur wirklich nahe Treffer übernehmen const GAP_BETWEEN_CALLS = 1500; // Pause zwischen Nominatim und Overpass // WICHTIG: anpassen const USER_AGENT = 'ioBroker-Standortscript/1.0 (Kontakt: deine-mail@example.com)'; // Laufzeitstatus pro Person const runtime = {}; /* --------------------------------------------------------- * Initialisierung * --------------------------------------------------------- */ for (const person of PERSONS) { person.addressState = `${person.baseState}.Standort_${person.key}_Adresse`; person.poiState = `${person.baseState}.Standort_${person.key}_POI`; person.lastLatState = `${person.baseState}.Standort_${person.key}_LastLat`; person.lastLonState = `${person.baseState}.Standort_${person.key}_LastLon`; person.lastTsState = `${person.baseState}.Standort_${person.key}_LastLookupTs`; runtime[person.key] = { debounceTimer: null, lookupRunning: false }; createStateIfMissing(person.targetState, '', 'string', `${person.key} Standort`); createStateIfMissing(person.addressState, '', 'string', `${person.key} Adresse`); createStateIfMissing(person.poiState, '', 'string', `${person.key} POI`); createStateIfMissing(person.lastLatState, 0, 'number', `${person.key} letzte Lookup-Latitude`); createStateIfMissing(person.lastLonState, 0, 'number', `${person.key} letzte Lookup-Longitude`); createStateIfMissing(person.lastTsState, 0, 'number', `${person.key} letzter Lookup-Zeitstempel`); on({ id: person.latState, change: 'ne' }, () => scheduleLookup(person)); on({ id: person.lonState, change: 'ne' }, () => scheduleLookup(person)); scheduleLookup(person); } /* --------------------------------------------------------- * Hauptlogik * --------------------------------------------------------- */ function scheduleLookup(person) { const rt = runtime[person.key]; if (rt.debounceTimer) clearTimeout(rt.debounceTimer); rt.debounceTimer = setTimeout(() => { runLookup(person).catch(err => log(`${person.key}: Standortscript Fehler: ${err}`, 'error')); }, DEBOUNCE_MS); } async function runLookup(person) { const rt = runtime[person.key]; if (rt.lookupRunning) { log(`${person.key}: Lookup läuft bereits, überspringe parallelen Aufruf.`, 'debug'); return; } const lat = parseFloat(getState(person.latState).val); const lon = parseFloat(getState(person.lonState).val); if (!isFinite(lat) || !isFinite(lon)) { log(`${person.key}: Ungültige Koordinaten, Lookup abgebrochen.`, 'warn'); return; } if (!shouldLookup(person, lat, lon)) { log(`${person.key}: Lookup übersprungen (Cooldown oder Bewegung zu gering).`, 'debug'); return; } rt.lookupRunning = true; try { const addressInfo = await getAddressFromNominatim(lat, lon); const addressText = buildAddressText(addressInfo); setState(person.addressState, addressText || '', true); setState(person.targetState, addressText || 'Unbekannter Standort', true); setState(person.poiState, '', true); setState(person.lastLatState, lat, true); setState(person.lastLonState, lon, true); setState(person.lastTsState, Date.now(), true); await sleep(GAP_BETWEEN_CALLS); let poi = await getNearbyNamedPoi(lat, lon, POI_RADIUS_M); if (!poi) { poi = await getNearbyNamedPoi(lat, lon, SECOND_TRY_RADIUS); } let finalText = addressText || 'Unbekannter Standort'; if (poi && poi.name) { setState(person.poiState, poi.name, true); if (addressText && !addressText.toLowerCase().includes(poi.name.toLowerCase())) { finalText = poi.name + ', ' + addressText; } else { finalText = poi.name; } } setState(person.targetState, finalText, true); log(`${person.key}: Standort aktualisiert: ${finalText}`, 'info'); } catch (err) { log(`${person.key}: Fehler beim Standort-Lookup: ${err}`, 'error'); } finally { rt.lookupRunning = false; } } function shouldLookup(person, lat, lon) { const lastTsRaw = getState(person.lastTsState).val; const lastLatRaw = getState(person.lastLatState).val; const lastLonRaw = getState(person.lastLonState).val; const lastTs = Number(lastTsRaw || 0); const lastLat = parseFloat(lastLatRaw); const lastLon = parseFloat(lastLonRaw); const now = Date.now(); if (lastTs > 0 && (now - lastTs) < MIN_INTERVAL_MS) { return false; } if (isFinite(lastLat) && isFinite(lastLon) && !(lastLat === 0 && lastLon === 0)) { const dist = distanceMeters(lastLat, lastLon, lat, lon); if (dist < MIN_DISTANCE_M) { return false; } } return true; } /* --------------------------------------------------------- * Nominatim Reverse Geocoding * --------------------------------------------------------- */ async function getAddressFromNominatim(lat, lon) { const url = 'https://nominatim.openstreetmap.org/reverse' + '?format=jsonv2' + '&lat=' + encodeURIComponent(lat) + '&lon=' + encodeURIComponent(lon) + '&addressdetails=1' + '&zoom=18'; const response = await httpGetAsync(url, { 'User-Agent': USER_AGENT, 'Accept': 'application/json' }); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Nominatim HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Nominatim JSON konnte nicht geparst werden'); } return data; } function buildAddressText(data) { if (!data) return ''; const a = data.address || {}; const road = a.road || a.pedestrian || a.footway || a.path || a.cycleway || ''; const houseNumber = a.house_number || ''; const postcode = a.postcode || ''; const city = a.city || a.town || a.village || a.hamlet || a.municipality || ''; let result = ''; if (road) { result += road; if (houseNumber) result += ' ' + houseNumber; } if (postcode || city) { if (result) result += ', '; result += [postcode, city].filter(Boolean).join(' '); } if (!result && data.display_name) { result = data.display_name; } return cleanText(result); } /* --------------------------------------------------------- * Overpass: benannten Ort/POI in der Nähe finden * --------------------------------------------------------- */ async function getNearbyNamedPoi(lat, lon, radius) { const query = ` [out:json][timeout:10]; ( nwr(around:${radius},${lat},${lon})["name"]["shop"]; nwr(around:${radius},${lat},${lon})["name"]["amenity"]; nwr(around:${radius},${lat},${lon})["name"]["tourism"]; nwr(around:${radius},${lat},${lon})["name"]["leisure"]; nwr(around:${radius},${lat},${lon})["name"]["office"]; ); out center tags; `; const response = await httpPostAsync( 'https://overpass-api.de/api/interpreter', query, { 'User-Agent': USER_AGENT, 'Content-Type': 'text/plain; charset=utf-8', 'Accept': 'application/json' } ); if (response.statusCode < 200 || response.statusCode >= 300) { throw new Error('Overpass HTTP ' + response.statusCode); } let data; try { data = JSON.parse(response.data); } catch (e) { throw new Error('Overpass JSON konnte nicht geparst werden'); } if (!data.elements || !data.elements.length) { return null; } const candidates = data.elements .map(el => { const cLat = el.lat || (el.center && el.center.lat); const cLon = el.lon || (el.center && el.center.lon); const name = el.tags && el.tags.name ? String(el.tags.name).trim() : ''; const dist = (isFinite(cLat) && isFinite(cLon)) ? distanceMeters(lat, lon, cLat, cLon) : 999999; return { name: cleanText(name), tags: el.tags || {}, dist: dist }; }) .filter(x => x.name); if (!candidates.length) return null; candidates.sort((a, b) => { const pa = poiPriority(a.tags); const pb = poiPriority(b.tags); if (pa !== pb) return pa - pb; return a.dist - b.dist; }); const best = candidates[0]; if (!best || best.dist > MAX_ACCEPTED_POI_DISTANCE) { return null; } return best; } function poiPriority(tags) { if (tags.shop) return 1; if (tags.amenity) return 2; if (tags.tourism) return 3; if (tags.leisure) return 4; if (tags.office) return 5; return 99; } /* --------------------------------------------------------- * HTTP-Helfer * --------------------------------------------------------- */ function httpGetAsync(url, headers) { return new Promise((resolve, reject) => { httpGet(url, { headers: headers, timeout: 15000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function httpPostAsync(url, body, headers) { return new Promise((resolve, reject) => { httpPost(url, body, { headers: headers, timeout: 20000 }, (err, response) => { if (err) return reject(err); resolve(response); }); }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /* --------------------------------------------------------- * Hilfsfunktionen * --------------------------------------------------------- */ function distanceMeters(lat1, lon1, lat2, lon2) { const R = 6371000; const toRad = deg => deg * Math.PI / 180; const dLat = toRad(lat2 - lat1); const dLon = toRad(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } function cleanText(str) { return String(str || '') .replace(/\s+/g, ' ') .replace(/\s+,/g, ',') .trim(); } function createStateIfMissing(id, def, type, name) { if (!existsState(id)) { createState(id, def, { name: name, type: type, role: type === 'number' ? 'value' : 'text', read: true, write: true }); } } -
@Merlin123 .. ich habe da ein paar Fragen. Ich schreibe dich morgen dazu mal privat an.
-
Super Arbeit, danke! Ich bin froh, dass der Adapter wieder funktioniert.
Weiß jemand, ob sich der Token irgendwann ändert und ich dann wieder aktiv werden muss?
Aber wie gesagt: Ich bin jetzt erstmal happy, dass ich wieder damit arbeiten kann und meine own Tracks vom Handy schmeißen kann.Vielen Dank und bitte dran bleiben – ich hoffe, der Adapter schafft es auch bald ins offizielle ioBroker Repository.
-
Super Arbeit, danke! Ich bin froh, dass der Adapter wieder funktioniert.
Weiß jemand, ob sich der Token irgendwann ändert und ich dann wieder aktiv werden muss?
Aber wie gesagt: Ich bin jetzt erstmal happy, dass ich wieder damit arbeiten kann und meine own Tracks vom Handy schmeißen kann.Vielen Dank und bitte dran bleiben – ich hoffe, der Adapter schafft es auch bald ins offizielle ioBroker Repository.
-
Super Arbeit, danke! Ich bin froh, dass der Adapter wieder funktioniert.
Weiß jemand, ob sich der Token irgendwann ändert und ich dann wieder aktiv werden muss?
Aber wie gesagt: Ich bin jetzt erstmal happy, dass ich wieder damit arbeiten kann und meine own Tracks vom Handy schmeißen kann.Vielen Dank und bitte dran bleiben – ich hoffe, der Adapter schafft es auch bald ins offizielle ioBroker Repository.
Vielen Dank und bitte dran bleiben – ich hoffe, der Adapter schafft es auch bald ins offizielle ioBroker Repository.
Danke. Ja, ich werde dranbleiben. Dieser Adapter ist eine Herzensangelegenheit ;)
Eine Aufnahme ins ioBroker Repository ist bereits beantragt. @mcm1957 hat bereits einen ersten Blick drüber geworfen .... an dieser Stelle, vielen Dank an mcm für seinen Support!
-
Super Arbeit, danke! Ich bin froh, dass der Adapter wieder funktioniert.
Weiß jemand, ob sich der Token irgendwann ändert und ich dann wieder aktiv werden muss?
Aber wie gesagt: Ich bin jetzt erstmal happy, dass ich wieder damit arbeiten kann und meine own Tracks vom Handy schmeißen kann.Vielen Dank und bitte dran bleiben – ich hoffe, der Adapter schafft es auch bald ins offizielle ioBroker Repository.
-
Kannst du dir das erklären @skvarel das nur bei der Tochter der Batteriewert auf -1 geht? Wenn ich aber den Adapter neu starte, habe ich ca 2 min den aktuellen Wert und dann wieder -1.

-
Kannst du dir das erklären @skvarel das nur bei der Tochter der Batteriewert auf -1 geht? Wenn ich aber den Adapter neu starte, habe ich ca 2 min den aktuellen Wert und dann wieder -1.

@stefu87_CH .. Ich tippe auf Einstellungen in der App oder generell im Handy. Irgendein Sleep-Mode ?!
Ich habe die Möglichkeiten alle vom Ursprungs-Adapter übernommen und nur den Login geändert und die Karte für den iFrame hinzugefügt.
Vielen Dank an @migoller an dieser Stelle!
-
@stefu87_CH .. Ich tippe auf Einstellungen in der App oder generell im Handy. Irgendein Sleep-Mode ?!
Ich habe die Möglichkeiten alle vom Ursprungs-Adapter übernommen und nur den Login geändert und die Karte für den iFrame hinzugefügt.
Vielen Dank an @migoller an dieser Stelle!
Ich tippe auf Einstellungen in der App oder generell im Handy. Irgendein Sleep-Mode ?!
Aber was mir dann spanisch vorkommt, das in der APP auf dem Handy immer der aktuelle Wert angezeigt wird. Werde das aber mal beobachten ob das einfach gerade Zufall ist, oder ob es sporadisch vorkommt.
-
Ich tippe auf Einstellungen in der App oder generell im Handy. Irgendein Sleep-Mode ?!
Aber was mir dann spanisch vorkommt, das in der APP auf dem Handy immer der aktuelle Wert angezeigt wird. Werde das aber mal beobachten ob das einfach gerade Zufall ist, oder ob es sporadisch vorkommt.
-
@stefu87_CH .. ich werde das hier auch mal beobachten. Ich habe nie auf die Werte geachtet, weil mir der Akku anderer Familienmitglieder nicht so wichtig ist.
@skvarel , @stefu87_ch :
Ich kann dazu berichten, dass meine Tochter grundsätzlich den Akkusparmodus eingeschaltet hat, aber der Battariestand wird bei mir trotzdem in den neuen Adapter übertragen... In der App auf dem Handy ist der natürlich auch sichtbar...
Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.
Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.
Mit deinem Input könnte dieser Beitrag noch besser werden 💗
Registrieren Anmelden
Ist wie gesagt alles KI erzeugt....