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

Getting Started with Alfresco SDK/Development: A Beginner’s Guide to Automating File Organization with Alfresco Behaviors

Alfresco is an enterprise content management platform known for its flexibility and extensibility. One powerful way to extend its functionality is through Behaviors, which allow you to run custom logic whenever specific repository events occur. For example, you can trigger custom actions whenever nodes are created, updated, or deleted.


Weiter >>

Enhancing Alfresco’s Public API (ACS): A Step-by-Step Guide to Custom Node Extensions with MIME Type Restrictions

Introduction

In today’s digital landscape, controlling and validating the types of content users can upload into systems is essential for security and data integrity. Alfresco, a leading open-source content services platform, offers a flexible public API that enables developers to create custom extensions and adapt the platform to specific organizational needs. This article provides a step-by-step guide to implementing a MIME type restriction feature in Alfresco’s Nodes API, allowing for more controlled and secure content uploads.


Weiter >>

Warum der Air Assist unverzichtbar ist – Mein Erfahrungsbericht

Nachdem ich meinen ATOMSTACK A12 Ultra Laser[*] und die R2 V2 Drehwalze[*] in Betrieb genommen hatte, war es nur eine Frage der Zeit, bis ich mir zusätzlich ein Air Assist System zugelegt habe. Ich entschied mich für das DEWALLIE Air Assist Set[*], und ich kann schon vorweg sagen: Es war eine der besten Ergänzungen für meine Lasergravur-Setups, vor allem beim Arbeiten mit Holz!


Weiter >>

Mein neues Setup: Der ATOMSTACK R2 V2 Drehwalze und A12 Ultra/Pro Laser – Perfekt für Gravuren auf runden Objekten!

Als ich mir kürzlich den ATOMSTACK A12 Ultra Laser[*] zugelegt habe, war mir schnell klar, dass ich das volle Potenzial dieses leistungsstarken Gravierers ausschöpfen wollte. Also habe ich nicht lange gezögert und gleich die ATOMSTACK R2 V2 Drehwalze[*] dazu gekauft, die es ermöglicht, zylindrische Objekte wie Trinkflaschen, Gläser oder Stifte zu gravieren.


Weiter >>