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:
- GPS Modul: GPS Modul von Amazon[*]
- ESP32 Controller: ESP32 Controller von Amazon[*]
- 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
- GPS Modul an den ESP32 Controller anschließen: Verbinde das GPS Modul gemäß den Anweisungen in der Produktbeschreibung mit dem ESP32 Controller.
- 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.
- HGLRC M100-5883 GPS-Kompassmodul, verbesserter Chip der 10. Generation, kompatibel mit FPV Fixed-Wing UAV[*]
- Geekstory GT-U7 GPS Modul GPS-Empfänger Navigationssatellit mit EEPROM Kompatibel mit 6M 51 Mikrocontroller STM32 UO R3+ IPEX Aktive GPS Antenne für Arduino Drohne Raspberry Pi Flug[*]
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!