Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Tester
  4. ...nicht in offiziellem Repo
  5. Life360 NextGeneration

NEWS

  • Neuer ioBroker-Blog online: Monatsrückblick März/April 2026
    BluefoxB
    Bluefox
    8
    1
    295

  • Verwendung von KI bitte immer deutlich kennzeichnen
    HomoranH
    Homoran
    9
    1
    274

  • Monatsrückblick Januar/Februar 2026 ist online!
    BluefoxB
    Bluefox
    18
    1
    916

Life360 NextGeneration

Geplant Angeheftet Gesperrt Verschoben ...nicht in offiziellem Repo
copilotstandortgeo locationmap
146 Beiträge 15 Kommentatoren 1.8k Aufrufe 17 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • skvarelS skvarel

    @Merlin123 .. das Widget funktioniert auch in VIS 2

    Merlin123M Offline
    Merlin123M Offline
    Merlin123
    schrieb am zuletzt editiert von
    #133

    @skvarel hab es auch mal eingebaut. Nett :)

    Beta-Tester

    BananaJoeB 1 Antwort Letzte Antwort
    1
    • Merlin123M Offline
      Merlin123M Offline
      Merlin123
      schrieb am zuletzt editiert von
      #134

      Hier mal die aktuelle Version meines "Adresse und POI" Suchscriptes. die POIs werden leider nicht immer gefunden. Hängt anscheinend mit der Größe des Gebäudes und dem ermittelten Standorts zusammen. Wenn man den Radius um den Standort zu groß macht, findet er teilweise falsche Sachen, macht man ihn zu klein findet man nichts.
      Weiteres Problem:
      Wenn man zu viele Anfragen an die Openmap API schickt, blockiert die erstmal.
      Im Script sind deswegen (vor allem beim Start) Verzögerungen drin. Trotzdem kann es mal zu Problemen kommen und das Script liefert dann keine Werte.
      Was auch drin ist: Er ändert nur die alten Werte, wenn die neuen in nem gewissen Maße abweichen. Soll Probleme verhindern, wenn der aktuelle Standort mal leicht variiert. Hatte das zu Hause, dass er erst die richtige Adresse angezeigt hat, und als ich im Garten nahe bei den Nachbarn war ist er bei der Adresse umgesprungen. Passiert jetzt nicht.
      Halt mit dem Nachteil, das unter Umständen die alte Adresse bleibt, auch wenn man beim NAchbar ist. Abhängig halt von den Abständen....

      Ist aktuell für 3 Personen ausgelegt. Wenn man die Daten für eine Person leer läß werden nur 2 bearbeitet usw.

      /************************************************************
       * Mehrpersonen-Standortscript für ioBroker + Life360
       *
       * Konfiguration:
       * Nur Name + Life360-ID eintragen.
       * Alles andere wird automatisch erzeugt.
       ************************************************************/
      
      /* ---------------------------------------------------------
       * PERSONENKONFIGURATION
       * --------------------------------------------------------- */
      const PERSON1_NAME = 'Oliver';
      const PERSON1_ID   = 'b9fxxxxa7e';
      
      const PERSON2_NAME = 'Hannah';
      const PERSON2_ID   = 'f7fxxxx8e9d';
      
      const PERSON3_NAME = 'Marleen';
      const PERSON3_ID   = '880xxxaea';
      
      /* ---------------------------------------------------------
       * ALLGEMEINE EINSTELLUNGEN
       * --------------------------------------------------------- */
      const MIN_INTERVAL_MS                 = 60 * 1000;
      const MIN_DISTANCE_M                  = 40;
      const DEBOUNCE_MS                     = 5000;
      
      const POI_RADIUS_M                    = 25;
      const SECOND_TRY_RADIUS               = 80;
      
      const GAP_BETWEEN_CALLS               = 2500;
      const STARTUP_PERSON_DELAY_MS         = 8000;
      const OVERPASS_RETRY_DELAY_MS         = 3000;
      
      const ADDRESS_STABLE_DISTANCE_M       = 20;
      const POI_STABLE_DISTANCE_M           = 120;
      const KEEP_POI_IF_OVERPASS_FAILS_M    = 150;
      
      const USER_AGENT = 'ioBroker-Standortscript/1.0 (Kontakt: oliver@voelker123.de)';
      
      // Debug
      const DEBUG_LOG_POI_CANDIDATES = true;
      const DEBUG_LOG_REJECTED_POIS  = true;
      
      /* ---------------------------------------------------------
       * PERSONENLISTE AUTOMATISCH ERZEUGEN
       * --------------------------------------------------------- */
      const PERSON_CONFIGS = [
          { name: PERSON1_NAME, id: PERSON1_ID },
          { name: PERSON2_NAME, id: PERSON2_ID },
          { name: PERSON3_NAME, id: PERSON3_ID }
      ];
      
      const PERSONS = PERSON_CONFIGS
          .filter(p => String(p.name || '').trim() && String(p.id || '').trim())
          .map(p => {
              const cleanName = String(p.name).trim();
              const cleanId = String(p.id).trim();
      
              return {
                  key: cleanName,
                  id: cleanId,
                  latState: `life360ng.0.people.${cleanId}.latitude`,
                  lonState: `life360ng.0.people.${cleanId}.longitude`,
                  targetState: `0_userdata.0.VIS.Position.Standort_${cleanName}`,
                  baseState: `0_userdata.0.VIS.Position.${cleanName}`
              };
          });
      
      if (!PERSONS.length) {
          throw new Error('Keine Personen konfiguriert. Bitte mindestens NAME und ID für eine Person eintragen.');
      }
      
      // 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`;
          person.poiTypeState  = `${person.baseState}.Standort_${person.key}_POI_Type`;
          person.poiDistState  = `${person.baseState}.Standort_${person.key}_POI_Distance`;
          person.poiLatState   = `${person.baseState}.Standort_${person.key}_POI_Lat`;
          person.poiLonState   = `${person.baseState}.Standort_${person.key}_POI_Lon`;
      
          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`);
          createStateIfMissing(person.poiTypeState, '', 'string', `${person.key} POI Typ`);
          createStateIfMissing(person.poiDistState, 0, 'number', `${person.key} POI Distanz`);
          createStateIfMissing(person.poiLatState, 0, 'number', `${person.key} POI Latitude`);
          createStateIfMissing(person.poiLonState, 0, 'number', `${person.key} POI Longitude`);
      
          on({ id: person.latState, change: 'ne' }, () => scheduleLookup(person, false));
          on({ id: person.lonState, change: 'ne' }, () => scheduleLookup(person, false));
      }
      
      /* ---------------------------------------------------------
       * Initial-Refresh beim Start: immer erzwingen, aber gestaffelt
       * --------------------------------------------------------- */
      setTimeout(() => {
          PERSONS.forEach((person, index) => {
              setTimeout(() => {
                  scheduleLookup(person, true);
              }, index * STARTUP_PERSON_DELAY_MS);
          });
      }, 2000);
      
      /* ---------------------------------------------------------
       * Hauptlogik
       * --------------------------------------------------------- */
      function scheduleLookup(person, force) {
          const rt = runtime[person.key];
      
          if (rt.debounceTimer) clearTimeout(rt.debounceTimer);
      
          rt.debounceTimer = setTimeout(() => {
              runLookup(person, !!force).catch(err => log(`${person.key}: Standortscript Fehler: ${err}`, 'error'));
          }, force ? 500 : DEBOUNCE_MS);
      }
      
      async function runLookup(person, force) {
          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;
          }
      
          const previousLat = parseFloat(getState(person.lastLatState).val);
          const previousLon = parseFloat(getState(person.lastLonState).val);
          const moveDistance = (isFinite(previousLat) && isFinite(previousLon) && !(previousLat === 0 && previousLon === 0))
              ? distanceMeters(previousLat, previousLon, lat, lon)
              : 999999;
      
          if (!force && !shouldLookup(person, lat, lon)) {
              log(`${person.key}: Lookup übersprungen (Cooldown oder Bewegung zu gering).`, 'debug');
              return;
          }
      
          rt.lookupRunning = true;
      
          try {
              log(`${person.key}: Starte Lookup für ${lat}, ${lon}${force ? ' (forced)' : ''}`, 'info');
      
              const oldAddress = String(getState(person.addressState).val || '').trim();
              const oldPoi = String(getState(person.poiState).val || '').trim();
              const oldPoiType = String(getState(person.poiTypeState).val || '').trim();
              const oldPoiLat = parseFloat(getState(person.poiLatState).val);
              const oldPoiLon = parseFloat(getState(person.poiLonState).val);
      
              const addressInfo = await getAddressFromNominatim(lat, lon);
              const rawAddressText = buildAddressText(addressInfo);
              const addressText = stabilizeAddress(oldAddress, rawAddressText, moveDistance);
      
              setState(person.addressState, addressText || '', true);
              setState(person.targetState, addressText || 'Unbekannter Standort', true);
      
              setState(person.lastLatState, lat, true);
              setState(person.lastLonState, lon, true);
              setState(person.lastTsState, Date.now(), true);
      
              await sleep(GAP_BETWEEN_CALLS);
      
              let poi = null;
              let finalText = addressText || 'Unbekannter Standort';
              let overpassFailed = false;
      
              try {
                  const firstPoiResult = await getNearbyNamedPoi(person, lat, lon, POI_RADIUS_M, addressText);
                  poi = firstPoiResult.poi;
                  overpassFailed = firstPoiResult.overpassFailed === true;
      
                  const mayRetryWithLargerRadius =
                      SECOND_TRY_RADIUS > POI_RADIUS_M &&
                      !overpassFailed &&
                      (
                          firstPoiResult.hadCandidates ||
                          addressLooksLikeBusiness(addressText)
                      );
      
                  if (!poi && mayRetryWithLargerRadius) {
                      await sleep(OVERPASS_RETRY_DELAY_MS);
                      const secondPoiResult = await getNearbyNamedPoi(person, lat, lon, SECOND_TRY_RADIUS, addressText);
                      poi = secondPoiResult.poi;
                      overpassFailed = secondPoiResult.overpassFailed === true;
                  }
              } catch (poiErr) {
                  overpassFailed = true;
                  log(`${person.key}: POI-Suche fehlgeschlagen (${poiErr}). Verwende Adresse bzw. alten POI.`, 'warn');
              }
      
              if (!poi && oldPoi && isFinite(oldPoiLat) && isFinite(oldPoiLon) && !(oldPoiLat === 0 && oldPoiLon === 0)) {
                  const distToOldPoi = distanceMeters(lat, lon, oldPoiLat, oldPoiLon);
                  const allowedKeepDistance = overpassFailed ? KEEP_POI_IF_OVERPASS_FAILS_M : POI_STABLE_DISTANCE_M;
      
                  if (isStablePoiType(oldPoiType) && distToOldPoi <= allowedKeepDistance) {
                      poi = {
                          name: oldPoi,
                          typeLabel: oldPoiType || 'remembered',
                          dist: distToOldPoi,
                          lat: oldPoiLat,
                          lon: oldPoiLon,
                          remembered: true
                      };
                      log(
                          `${person.key}: Behalte bisherigen POI '${oldPoi}' bei (Distanz ${distToOldPoi.toFixed(1)} m, OverpassFailed=${overpassFailed}).`,
                          'info'
                      );
                  }
              }
      
              if (poi && poi.name) {
                  setState(person.poiState, poi.name, true);
                  setState(person.poiTypeState, poi.typeLabel || '', true);
                  setState(person.poiDistState, round1(poi.dist || 0), true);
                  setState(person.poiLatState, poi.lat || 0, true);
                  setState(person.poiLonState, poi.lon || 0, true);
      
                  if (addressText && !addressText.toLowerCase().includes(poi.name.toLowerCase())) {
                      finalText = poi.name + ', ' + addressText;
                  } else {
                      finalText = poi.name;
                  }
              } else {
                  setState(person.poiState, '', true);
                  setState(person.poiTypeState, '', true);
                  setState(person.poiDistState, 0, true);
                  setState(person.poiLatState, 0, true);
                  setState(person.poiLonState, 0, true);
              }
      
              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;
      }
      
      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);
      }
      
      function stabilizeAddress(oldAddress, newAddress, moveDistance) {
          const oldA = String(oldAddress || '').trim();
          const newA = String(newAddress || '').trim();
      
          if (!newA) return oldA;
          if (!oldA) return newA;
          if (oldA === newA) return newA;
      
          if (moveDistance <= ADDRESS_STABLE_DISTANCE_M) {
              log(`Adress-Stabilisierung aktiv: Behalte '${oldA}' statt neu '${newA}' bei (Bewegung ${moveDistance.toFixed(1)} m).`, 'info');
              return oldA;
          }
      
          return newA;
      }
      
      function addressLooksLikeBusiness(addressText) {
          const a = String(addressText || '').toLowerCase();
      
          return (
              a.includes('luisenring') ||
              a.includes('mannheim') ||
              a.includes('industrie') ||
              a.includes('gewerbe') ||
              a.includes('business') ||
              a.includes('park')
          );
      }
      
      function buildGeneralPoiQuery(lat, lon, radius) {
          return `
      [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;
      `;
      }
      
      function buildBusinessPoiQuery(lat, lon, radius) {
          return `
      [out:json][timeout:6];
      (
        nwr(around:${radius},${lat},${lon})["name"]["office"];
        nwr(around:${radius},${lat},${lon})["name"]["office"="company"];
        nwr(around:${radius},${lat},${lon})["name"]["office"="energy_supplier"];
        nwr(around:${radius},${lat},${lon})["name"]["building"="office"];
      );
      out center tags;
      `;
      }
      
      async function getNearbyNamedPoi(person, lat, lon, radius, addressText) {
          const isBusinessAddress = addressLooksLikeBusiness(addressText);
          const query = isBusinessAddress
              ? buildBusinessPoiQuery(lat, lon, radius)
              : buildGeneralPoiQuery(lat, lon, radius);
      
          log(`${person.key}: Verwende ${isBusinessAddress ? 'Business' : 'General'}-POI-Query im Radius ${radius} m.`, 'info');
      
          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 === 504 || response.statusCode === 502 || response.statusCode === 429) {
              log(`${person.key}: Overpass HTTP ${response.statusCode}, verwende keinen neuen POI.`, 'warn');
              return { poi: null, hadCandidates: false, overpassFailed: true };
          }
      
          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) {
              log(`${person.key}: Keine POI-Kandidaten im Radius ${radius} m gefunden.`, 'info');
              return { poi: null, hadCandidates: false, overpassFailed: false };
          }
      
          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 rawName = el.tags && el.tags.name ? String(el.tags.name).trim() : '';
                  const name = cleanText(rawName);
                  const tags = el.tags || {};
                  const dist = (isFinite(cLat) && isFinite(cLon))
                      ? distanceMeters(lat, lon, cLat, cLon)
                      : 999999;
      
                  return {
                      name: name,
                      tags: tags,
                      dist: dist,
                      lat: cLat || 0,
                      lon: cLon || 0,
                      priority: poiPriority(tags, name),
                      maxDistance: getPoiMaxDistance(tags, name, addressText),
                      typeLabel: getPoiTypeLabel(tags)
                  };
              })
              .filter(x => x.name);
      
          if (!candidates.length) {
              log(`${person.key}: Overpass lieferte Objekte, aber keinen benannten Kandidaten.`, 'info');
              return { poi: null, hadCandidates: false, overpassFailed: false };
          }
      
          candidates.sort((a, b) => {
              if (a.priority !== b.priority) return a.priority - b.priority;
              return a.dist - b.dist;
          });
      
          if (DEBUG_LOG_POI_CANDIDATES) {
              log(`${person.key}: POI-Kandidaten Radius ${radius} m: ${JSON.stringify(candidates)}`, 'info');
          }
      
          for (const candidate of candidates) {
              if (candidate.dist <= candidate.maxDistance) {
                  log(
                      `${person.key}: POI akzeptiert: ${candidate.name} (${candidate.typeLabel}, Distanz ${candidate.dist.toFixed(1)} m, erlaubt ${candidate.maxDistance} m)`,
                      'info'
                  );
                  return { poi: candidate, hadCandidates: true, overpassFailed: false };
              } else if (DEBUG_LOG_REJECTED_POIS) {
                  log(
                      `${person.key}: POI verworfen: ${candidate.name} (${candidate.typeLabel}, Distanz ${candidate.dist.toFixed(1)} m, erlaubt ${candidate.maxDistance} m)`,
                      'info'
                  );
              }
          }
      
          log(`${person.key}: Kein POI bestand die Distanz-/Prioritätsprüfung im Radius ${radius} m.`, 'info');
          return { poi: null, hadCandidates: true, overpassFailed: false };
      }
      
      function poiPriority(tags, name) {
          const poiName = String(name || '').toLowerCase();
      
          if (tags.amenity === 'fire_station') return 1;
          if (tags.amenity === 'school') return 1;
          if (tags.amenity === 'kindergarten') return 1;
          if (tags.amenity === 'hospital') return 1;
          if (tags.amenity === 'clinic') return 1;
          if (tags.shop === 'supermarket') return 1;
      
          if (
              poiName.includes('schule') ||
              poiName.includes('grundschule') ||
              poiName.includes('realschule') ||
              poiName.includes('gymnasium')
          ) return 1;
      
          if (tags.office === 'energy_supplier') return 2;
          if (tags.office === 'company') return 3;
          if (tags.building === 'office') return 4;
      
          if (tags.shop === 'bakery') return 8;
          if (tags.shop === 'kiosk') return 8;
          if (tags.shop === 'convenience') return 7;
      
          if (tags.shop) return 2;
          if (tags.amenity) return 3;
          if (tags.tourism) return 4;
          if (tags.leisure) return 5;
          if (tags.office) return 6;
      
          return 99;
      }
      
      function getPoiMaxDistance(tags, name, addressText) {
          const poiName = String(name || '').toLowerCase();
          const addr = String(addressText || '').toLowerCase();
      
          if (tags.amenity === 'hospital') return 90;
          if (tags.amenity === 'fire_station') return 70;
      
          if (tags.amenity === 'school') {
              let dist = 60;
      
              if (
                  poiName.includes('schule') ||
                  poiName.includes('realschule') ||
                  poiName.includes('grundschule') ||
                  poiName.includes('gymnasium')
              ) {
                  dist = 90;
              }
      
              if (
                  addr.includes('schulstraße') ||
                  addr.includes('schule') ||
                  addr.includes('bobenheim-roxheim')
              ) {
                  dist = 130;
              }
      
              return dist;
          }
      
          if (tags.amenity === 'kindergarten') return 60;
          if (tags.amenity === 'clinic') return 50;
          if (tags.shop === 'supermarket') return 40;
      
          if (tags.office === 'energy_supplier') return 40;
          if (tags.office === 'company') return 35;
          if (tags.building === 'office') return 30;
      
          if (tags.shop === 'bakery') return 15;
          if (tags.shop === 'kiosk') return 15;
          if (tags.shop === 'convenience') return 20;
      
          if (tags.shop) return 25;
          if (tags.amenity) return 30;
          if (tags.tourism) return 30;
          if (tags.leisure) return 25;
          if (tags.office) return 25;
      
          return 20;
      }
      
      function getPoiTypeLabel(tags) {
          if (tags.amenity) return `amenity=${tags.amenity}`;
          if (tags.shop) return `shop=${tags.shop}`;
          if (tags.tourism) return `tourism=${tags.tourism}`;
          if (tags.leisure) return `leisure=${tags.leisure}`;
          if (tags.office) return `office=${tags.office}`;
          if (tags.building) return `building=${tags.building}`;
          return 'unknown';
      }
      
      function isStablePoiType(typeLabel) {
          const t = String(typeLabel || '').toLowerCase();
          return (
              t.includes('amenity=school') ||
              t.includes('amenity=fire_station') ||
              t.includes('amenity=hospital') ||
              t.includes('amenity=clinic') ||
              t.includes('amenity=kindergarten') ||
              t.includes('shop=supermarket') ||
              t.includes('office=company') ||
              t.includes('office=energy_supplier') ||
              t.includes('building=office')
          );
      }
      
      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));
      }
      
      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 round1(v) {
          return Math.round(Number(v || 0) * 10) / 10;
      }
      
      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
              });
          }
      }
      

      Beta-Tester

      1 Antwort Letzte Antwort
      1
      • Merlin123M Merlin123

        @skvarel hab es auch mal eingebaut. Nett :)

        BananaJoeB Online
        BananaJoeB Online
        BananaJoe
        Most Active
        schrieb am zuletzt editiert von
        #135

        @Merlin123 naja, alles von Bots.
        Dann anders: Bluefox pflegt den Adapter. Das sollte wohl beruhigen.

        ioBroker@Ubuntu 24.04 LTS (VMware) für: >260 Geräte, 5 Switche, 7 AP, 10 IP-Cam, 1 NAS 42TB, 1 ESXi 15TB, 4 Proxmox 1TB, 1 Hyper-V 48TB, 14 x Echo, 5x FireTV, 5 x Tablett/Handy VIS || >=160 Tasmota/Shelly || >=95 ZigBee || PV 8.1kW / Akku 14kWh || 2x USV APC 750W kaskadiert || Creality CR-10 SE 3D-Drucker

        1 Antwort Letzte Antwort
        1
        • skvarelS Online
          skvarelS Online
          skvarel
          Developer
          schrieb am zuletzt editiert von skvarel
          #136

          Ich bastel gerade an einem 'Fahrtenbuch'

          Die erste Idee ist ein json alas Ausgabe. Pro Person ein json in der jeder Änderung der Koordinaten in eine Tabelle geschrieben werden.

          An die Historie-Daten in der App komme ich leider nicht ran, also muss eine eigene Logik her.

          Man könnte es auch über den History Adapter machen, da zwingt man aber User zu einem weiteren Adapter. Ich fänd es schöner, wenn dieser Adapter es direkt bereitstellt.

          EDIT:

          Ich teste es morgen bei mir mit einem Script und werde es dann ggf. in den Adapter setzen.

          Erstmal nur als json (per Config de- und aktivierbar) .... dann vielleicht auch als Karte ?!

          #TeamInventwo
          Unsere Adapter:
          Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

          Wer uns mit einem Kaffee unterstützen möchte: PayPal

          1 Antwort Letzte Antwort
          2
          • skvarelS Online
            skvarelS Online
            skvarel
            Developer
            schrieb am zuletzt editiert von
            #137

            Ich schaue mir gearde das Thema 'GeoJSON' an. Das könnte genau das passende sein.

            #TeamInventwo
            Unsere Adapter:
            Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

            Wer uns mit einem Kaffee unterstützen möchte: PayPal

            1 Antwort Letzte Antwort
            1
            • C Offline
              C Offline
              Chrille1507
              schrieb am zuletzt editiert von
              #138

              Ich bekomme gerade folgende Fehler im Log (siehe Anhang). Geht es anderen hier auch so?

              20260420_log_LIFE360.JPG

              skvarelS 2 Antworten Letzte Antwort
              0
              • C Chrille1507

                Ich bekomme gerade folgende Fehler im Log (siehe Anhang). Geht es anderen hier auch so?

                20260420_log_LIFE360.JPG

                skvarelS Online
                skvarelS Online
                skvarel
                Developer
                schrieb am zuletzt editiert von skvarel
                #139

                @Chrille1507 .. lief der Adapter schon fehlerfrei bei dir oder ist das direkt nach der ersten Installation?

                #TeamInventwo
                Unsere Adapter:
                Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                Wer uns mit einem Kaffee unterstützen möchte: PayPal

                C 1 Antwort Letzte Antwort
                0
                • C Chrille1507

                  Ich bekomme gerade folgende Fehler im Log (siehe Anhang). Geht es anderen hier auch so?

                  20260420_log_LIFE360.JPG

                  skvarelS Online
                  skvarelS Online
                  skvarel
                  Developer
                  schrieb am zuletzt editiert von
                  #140

                  @Chrille1507 .. der Fehler sieht nach einem falschen Token aus. Siehe hier: #29

                  #TeamInventwo
                  Unsere Adapter:
                  Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                  Wer uns mit einem Kaffee unterstützen möchte: PayPal

                  1 Antwort Letzte Antwort
                  0
                  • skvarelS skvarel

                    @Chrille1507 .. lief der Adapter schon fehlerfrei bei dir oder ist das direkt nach der ersten Installation?

                    C Offline
                    C Offline
                    Chrille1507
                    schrieb zuletzt editiert von
                    #141

                    @skvarel Hallo, der Adapter lief vorher schon und auch ohne Probleme.

                    Edit: Aktuell läuft er wieder sauber durch ohne Fehlermeldungen.

                    1 Antwort Letzte Antwort
                    1
                    • skvarelS Online
                      skvarelS Online
                      skvarel
                      Developer
                      schrieb zuletzt editiert von skvarel
                      #142

                      Heute starte ich mit dem Testlauf vom 'Fahrtenbuch' :)


                      Jeden Tag wird ein neues GeoJSON mit dem Tagesdatum angelegt. In dem json sind dann alle Bewegungen über 20m hinterlegt.

                      Die 20m werden später über die Config einstellbar werden (wird benötigt um Gezappel zu unterbinden) und man dann auch auswählen, für welche Person man ein Fahrtenbuch möchte.

                      Per Datenpunkt lässt sich das Fahrtenbuch dann zusätzlich de-/aktivieren.

                      So zumindest der Plan ;)

                      Zur Zeit läuft es nur als Script in meinem ioBroker. Ich denke, zum Wochenende habe ich genügend Werte um es eventuell in den Adapter zu bauen.

                      ae0471d4-8342-4363-ba6f-ae73fc6161c2-image.jpeg

                      #TeamInventwo
                      Unsere Adapter:
                      Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                      Wer uns mit einem Kaffee unterstützen möchte: PayPal

                      1 Antwort Letzte Antwort
                      2
                      • skvarelS Online
                        skvarelS Online
                        skvarel
                        Developer
                        schrieb zuletzt editiert von skvarel
                        #143

                        Frage .... gibt es hier User, die die Anbindung an den Places-Adapter benötigen?

                        Da ich ja den alten Code übernommen hatte, überlege ich gerade etwas aufzuräumen.

                        Das wäre die neue Config, ohne Places-Adapter

                        73807fe0-c324-45c0-bcc0-d77e35458f07-image.jpeg

                        #TeamInventwo
                        Unsere Adapter:
                        Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                        Wer uns mit einem Kaffee unterstützen möchte: PayPal

                        1 Antwort Letzte Antwort
                        0
                        • Merlin123M Offline
                          Merlin123M Offline
                          Merlin123
                          schrieb zuletzt editiert von
                          #144

                          ich brauche den Places Adapter mal nicht.... Hatte ja weiter oben schon gefragt, was der Vorteil davon wäre....

                          Beta-Tester

                          1 Antwort Letzte Antwort
                          1
                          • skvarelS Online
                            skvarelS Online
                            skvarel
                            Developer
                            schrieb zuletzt editiert von skvarel
                            #145

                            Da ich vor habe, so viel wie möglich in dem Life360ng abzubilden, wird der Places auch nicht benötigt bzw. bietet dann aus meiner Sicht keinen Mehrwert. Gerade dann, wenn das mit dem Fahrtenbuch (History) sauber klappt.

                            #TeamInventwo
                            Unsere Adapter:
                            Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                            Wer uns mit einem Kaffee unterstützen möchte: PayPal

                            1 Antwort Letzte Antwort
                            1
                            • skvarelS Online
                              skvarelS Online
                              skvarel
                              Developer
                              schrieb zuletzt editiert von
                              #146

                              Info:

                              Da bei der Aufmahme ins Latest-Repo einige Fehlern im Code angemerkt wurden, verzögert sich die Aufnahme ins Repo und dadurch auch das Fahrtenbuch. Das wird es diesen Monat leider noch nicht geben.

                              Ich halte euch auf dem Laufenden!

                              #TeamInventwo
                              Unsere Adapter:
                              Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                              Wer uns mit einem Kaffee unterstützen möchte: PayPal

                              1 Antwort Letzte Antwort
                              0

                              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
                              Antworten
                              • In einem neuen Thema antworten
                              Anmelden zum Antworten
                              • Älteste zuerst
                              • Neuste zuerst
                              • Meiste Stimmen


                              Support us

                              ioBroker
                              Community Adapters
                              Donate

                              326

                              Online

                              32.8k

                              Benutzer

                              82.8k

                              Themen

                              1.3m

                              Beiträge
                              Community
                              Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                              ioBroker Community 2014-2025
                              logo
                              • Anmelden

                              • Du hast noch kein Konto? Registrieren

                              • Anmelden oder registrieren, um zu suchen
                              • Erster Beitrag
                                Letzter Beitrag
                              0
                              • Home
                              • Aktuell
                              • Tags
                              • Ungelesen 0
                              • Kategorien
                              • Unreplied
                              • Beliebt
                              • GitHub
                              • Docu
                              • Hilfe