Landroid Worx Mähroboter DIY GPS Tracking

| | Allgemein

DIY GPS Tracking für den Landroid Worx Mähroboter

Wenn du auf der Suche nach einer Möglichkeit bist, deinen Landroid Worx Mähroboter mit GPS Tracking auszustatten, dann bist du hier genau richtig. In diesem Beitrag zeige ich dir, wie du mit einem GPS Modul und einem ESP32 Controller dein eigenes Tracking-System für deinen Mähroboter realisieren kannst. Zusätzlich erkläre ich dir, wie du die Genauigkeit der GPS-Daten mittels eines Kalman-Filters in Home Assistant verbesserst.

Die verwendeten Komponenten

Für dieses Projekt habe ich folgende Komponenten genutzt:

  1. GPS Modul: GPS Modul von Amazon[*]
  2. ESP32 Controller: ESP32 Controller von Amazon[*]
  3. USB Winkelstecker: USB Winkelstecker von Amazon[*]

Glücklicherweise hat der Landroid im Batteriefach eine USB-Buchse, die wir nutzen können. Dafür benötigen wir entweder ein Winkelstück oder wir schneiden uns selbst ein Kabel zurecht, um den Anschluss herzustellen.

Installation und Konfiguration

Schritt 1: Hardware-Verbindung
  1. GPS Modul an den ESP32 Controller anschließen: Verbinde das GPS Modul gemäß den Anweisungen in der Produktbeschreibung mit dem ESP32 Controller.
  2. ESP32 mit dem Landroid verbinden: Nutze den USB Winkelstecker, um den ESP32 Controller mit der USB-Buchse im Batteriefach des Landroids zu verbinden.
Schritt 2: Software-Konfiguration

Um die Daten des GPS Moduls zu erfassen und weiterzuverarbeiten, verwenden wir ESPHome und Home Assistant. Hier ist die notwendige Konfiguration für ESPHome:

esphome:
  name: gps-sensor
  friendly_name: GPS Sensor
  on_boot:
    priority: -10
    then:
      - uart.write: !lambda |-
          // UBX command to set update rate to 5Hz
          uint8_t setRate[] = {0xB5, 0x62, 0x06, 0x08, 0x06, 0x00, 0x64, 0x00, 0x01, 0x00, 0x01, 0x00};
          uint8_t ck_a = 0;
          uint8_t ck_b = 0;
          for (int i = 2; i < sizeof(setRate); i++) {
            ck_a += setRate[i];
            ck_b += ck_a;
          }
          std::vector<uint8_t> command(setRate, setRate + sizeof(setRate));
          command.push_back(ck_a);
          command.push_back(ck_b);
          return command;
      - uart.write: !lambda |-
          // UBX command to enable SBAS
          uint8_t enableSBAS[] = {0xB5, 0x62, 0x06, 0x16, 0x08, 0x00, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00};
          return std::vector<uint8_t>(enableSBAS, enableSBAS + sizeof(enableSBAS));
      - uart.write: !lambda |-
          // UBX command to set DGPS mode
          uint8_t setDGPS[] = {0xB5, 0x62, 0x06, 0x3E, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00};
          return std::vector<uint8_t>(setDGPS, setDGPS + sizeof(setDGPS));
      - uart.write: !lambda |-
          // UBX command to disable static navigation
          uint8_t disableStaticNav[] = {0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
          return std::vector<uint8_t>(disableStaticNav, disableStaticNav + sizeof(disableStaticNav));

esp32:
  board: esp32dev
  framework:
    type: arduino

logger:

api:
  encryption:
    key: "xxx"

ota:
  password: "xxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  ap:
    ssid: "Gps-Sensor Fallback Hotspot"
    password: "xxx"

captive_portal:

uart:
  tx_pin: 17
  rx_pin: 16
  baud_rate: 9600

gps:
  latitude:
    id: gps_latitude
    name: "GPS Latitude"
  longitude:
    id: gps_longitude
    name: "GPS Longitude"
  altitude:
    name: "GPS Altitude"
  speed:
    name: "GPS Speed"
  satellites:
    name: "GPS Satellites"
  course:
    name: "GPS Course"
  update_interval: 250ms

time:
  - platform: gps
    id: gps_time

text_sensor:
  - platform: template
    update_interval: 250ms
    name: "GPS Combined"
    id: gps_combined
    lambda: |-
      if (isnan(id(gps_latitude).state) || isnan(id(gps_longitude).state)) {
        return {};
      }
      char buffer[64];
      snprintf(buffer, sizeof(buffer), "%f,%f", id(gps_latitude).state, id(gps_longitude).state);
      return {buffer};

globals:
  - id: gps_initialized
    type: bool
    restore_value: no
    initial_value: 'false'

Home Assistant Konfiguration

Um die Daten aus dem ESP32 in Home Assistant zu nutzen und zu glätten, habe ich die folgende Konfiguration hinzugefügt:

input_text:
  gps_latitude_history:
    name: GPS Latitude History
    initial: ""
  gps_longitude_history:
    name: GPS Longitude History
    initial: ""
  kalman_filter_state:
    name: Kalman Filter State
    initial: ""    
  kalman_filter_state_lat:
    name: Kalman Filter Lat
    initial: "" 
  kalman_filter_state_long:
    name: Kalman Filter Long
    initial: "" 

Kalman-Filter Python Script

Um die GPS-Daten zu glätten, wird ein Kalman-Filter verwendet. Das folgende Python-Script implementiert diesen Filter:

# python_scripts/kalman_filter.py

latitude_history = hass.states.get('input_text.gps_latitude_history').state
longitude_history = hass.states.get('input_text.gps_longitude_history').state
kalman_state = hass.states.get('input_text.kalman_filter_state').state

if latitude_history:
    latitude

_history = latitude_history.split(',')
else:
    latitude_history = []

if longitude_history:
    longitude_history = longitude_history.split(',')
else:
    longitude_history = []

def init_kalman_filter(process_noise, measurement_noise, estimated_error, initial_value=0):
    return {
        'q': process_noise,
        'r': measurement_noise,
        'p': estimated_error,
        'x': initial_value,
        'k': 0
    }

def update_kalman_filter(kf, measurement):
    kf['p'] = kf['p'] + kf['q']
    kf['k'] = kf['p'] / (kf['p'] + kf['r'])
    kf['x'] = kf['x'] + kf['k'] * (measurement - kf['x'])
    kf['p'] = (1 - kf['k']) * kf['p']
    return kf

def float_to_hex(f):
    return ''.join('{:02x}'.format(b) for b in f.hex().encode('utf-8'))

def hex_to_float(h):
    return float.fromhex(bytes.fromhex(h).decode('utf-8'))

if kalman_state == "":
    if latitude_history and longitude_history:
        kf_lat = init_kalman_filter(0.1, 5, 1, float(latitude_history[-1]))
        kf_lon = init_kalman_filter(0.1, 5, 1, float(longitude_history[-1]))
    else:
        kf_lat = init_kalman_filter(0.1, 5, 1, 0.0)
        kf_lon = init_kalman_filter(0.1, 5, 1, 0.0)
else:
    kf_lat_state, kf_lon_state = kalman_state.split('|')
    kf_lat = init_kalman_filter(0.1, 5, 1, hex_to_float(kf_lat_state))
    kf_lon = init_kalman_filter(0.1, 5, 1, hex_to_float(kf_lon_state))

if latitude_history and longitude_history:
    latitude = float(latitude_history[-1])
    longitude = float(longitude_history[-1])
    kf_lat = update_kalman_filter(kf_lat, latitude)
    kf_lon = update_kalman_filter(kf_lon, longitude)

new_kalman_state = f"{float_to_hex(kf_lat['x'])}|{float_to_hex(kf_lon['x'])}"

hass.states.set('sensor.kalman_filter_output', 'on', {
    'latitude': kf_lat['x'],
    'longitude': kf_lon['x']
})

hass.services.call('input_text', 'set_value', {
    'entity_id': 'input_text.kalman_filter_state',
    'value': new_kalman_state
})

hass.services.call('input_text', 'set_value', {
    'entity_id': 'input_text.kalman_filter_state_lat',
    'value': str(kf_lat['x'])
})

hass.services.call('input_text', 'set_value', {
    'entity_id': 'input_text.kalman_filter_state_long',
    'value': str(kf_lon['x'])
})

logger.info(f"Kalman Filter updated: Latitude={kf_lat['x']}, Longitude={kf_lon['x']}")
logger.info(f"Kalman Filter state: {new_kalman_state}")

Automatisierung in Home Assistant

Um die GPS-Daten regelmäßig zu aktualisieren und den Kalman-Filter anzuwenden, habe ich die folgende Automation in Home Assistant erstellt:

alias: Update GPS Tracker
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.gps_sensor_gps_combined
condition:
  - condition: template
    value_template: "{{ not is_state('lawn_mower.schaf', 'docked') }}"
    enabled: false
action:
  - service: input_text.set_value
    data:
      entity_id: input_text.gps_latitude_history
      value: >-
        {{ (states('input_text.gps_latitude_history').split(',')[-(n-1):] |
        join(',')) + ',' +
        states('sensor.gps_sensor_gps_combined').split(',')[0] if
        states('input_text.gps_latitude_history') else
        states('sensor.gps_sensor_gps_combined').split(',')[0] }}
  - service: input_text.set_value
    data:
      entity_id: input_text.gps_longitude_history
      value: >-
        {{ (states('input_text.gps_longitude_history').split(',')[-(n-1):] |
        join(',')) + ',' +
        states('sensor.gps_sensor_gps_combined').split(',')[1] if
        states('input_text.gps_longitude_history') else
        states('sensor.gps_sensor_gps_combined').split(',')[1] }}
  - service: python_script.kalman_filter
    data: {}
    response_variable: python_script_output
  - service: device_tracker.see
    data_template:
      dev_id: gps_tracker
      gps:
        - "{{ states('input_text.kalman_filter_state_lat') }}"
        - "{{ states('input_text.kalman_filter_state_long') }}"
      attributes:
        source_type: gps
variables:
  "n": 10
mode: queued
max: 15

Fazit

Mit dieser Anleitung kannst du deinen Landroid Worx Mähroboter mit einem DIY GPS Tracking System ausstatten und die Genauigkeit der Daten mittels Kalman-Filter verbessern. Allerdings muss erwähnt werden, dass der verwendete GPS Sensor nicht der beste ist und sehr ungenaue Daten liefern kann. Trotz dieser Einschränkung kann man damit in Home Assistant verfolgen, welche Strecken der Mähroboter abgefahren hat. Interessant wäre es sicherlich, auch andere Module zu testen, wie z.B.

Diese Lösung ist natürlich nicht nur für Mähroboter geeignet. Sie lässt sich ebenso für andere Zwecke einsetzen, z.B. in Verbindung mit einem GSM Modul im Auto, um die Fahrzeugposition zu verfolgen. Ich hoffe, dieser Beitrag hilft dir bei deinem Projekt. Wenn du Fragen oder Anregungen hast, hinterlasse gerne einen Kommentar. Viel Spaß beim Basteln und Happy Mowing!

Neueste Beiträge

Die perfekte Hülle für dein iPhone 15: Die TORRAS Dr. Ultra Dünn Hülle

Wenn du auf der Suche nach einer Hülle bist, die dein iPhone 15 (oder auch alle anderen Modele wie 14 oder 16[*]) perfekt schützt und dabei trotzdem unglaublich dünn und stilvoll ist, dann solltest du dir die TORRAS Dr. Ultra Dünn Hülle[*] unbedingt ansehen. Als stolzer Besitzer eines neuen iPhones habe ich mich erneut für diese Hülle entschieden – und das aus gutem Grund, denn ich habe sie bereits für mein altes iPhone 12 genutzt und war damals schon begeistert.


Weiter >>

WLAN Router Einstellungen Telekom ISP / Tenda Nova MX21-Pro Mesh WLAN Wi-Fi 6E System

Persönliche Einblicke in die Einrichtung und Nutzung

Als passionierter Technologie-Enthusiast und stolzer Nutzer eines Telekom 500Mbit/s Glasfaser Internet Anschlusses war ich auf der Suche nach einem WLAN-System, das mit Leistung und Zuverlässigkeit überzeugen kann. Meine Wahl fiel auf das Tenda Nova MX21-Pro Mesh WLAN Wi-Fi 6E[*] System, und diese Entscheidung möchte ich heute ausführlich mit Ihnen teilen.


Weiter >>