DIY Camper GPS: Feeding Accurate Location Data into Home Assistant

TenFootStripes
By -
0

DIY Camper GPS

As we travel, we wanted accurate location updates for our camper's Home Assistant — not just to see where we’ve been, but also to make sure automations respond correctly to our current location. With precise GPS data, our Home Assistant server can adjust for time zone changes, pull accurate local weather, and make sure any location-based or time-sensitive automations run exactly when they should.


Why I Needed a Dedicated GPS for the Camper

At first, I was using the GPS built into the Quectel EM12-G modem of my LTE router (a MikroTik WAP R AC), but every time the GPS was active, the LTE interface would act up. This would sometimes cause the GPS to go offline, so I had to write a script on the MikroTik to check the GPS status and restart it if it was dead — but that didn’t always work. In some cases, the GPS would stop reporting latitude, longitude, speed, and other positioning data, yet still show the data age as current, which is what I was triggering the script on. From the router’s point of view, everything looked fine, even though no usable location data was coming in. I needed a solution that gave me reliable GPS data without interfering with our internet connection, so I decided to build a dedicated GPS for the camper.


Building a Standalone GPS for the Camper

I already had an ESP32 microcontroller, so I paired it with a NEO-6M GPS module and used ESPHome to integrate it directly into Home Assistant. This gave me a completely independent GPS source that consistently reports our coordinates and feeds clean, reliable data into Home Assistant for tracking, automations, and location-aware features.

First, I flashed the ESP32 with a basic ESPHome config that didn’t include GPS. I did this so I could update the firmware over Wi-Fi remotely, without having to be inside the camper. Once that was working, I added a static IP and configured the GPS to report latitude, longitude, speed, altitude, and course. I also added logic to only update the stored coordinates when we moved more than 50 meters — this prevents small GPS drift from constantly updating Home Assistant when the camper isn’t really moving.


picture showing my diy gps prototype
Prototype of my DIY GPS


Here’s the full YAML I use. I keep this as a reference for myself, you are more than welcome to use it on your own build, and it also shows exactly how the GPS is configured:


esphome:
  name: esphome-web-3bb178
  friendly_name: camper-gps
  min_version: 2025.11.0
  name_add_mac_suffix: false

esp32:
  variant: esp32
  framework:
    type: esp-idf

logger:

api:

ota:
  - platform: esphome

wifi:
  ssid: "!secret"
  password: "!secret"
  manual_ip:
    static_ip: 10.0.10.206
    gateway: 10.0.10.1
    subnet: 255.255.255.0

uart:
  id: uart_gps
  tx_pin: GPIO17
  rx_pin: GPIO16
  baud_rate: 9600

globals:
  - id: last_lat
    type: float
    restore_value: yes
    initial_value: '0.0'

  - id: last_lon
    type: float
    restore_value: yes
    initial_value: '0.0'

  - id: has_fix
    type: bool
    restore_value: yes
    initial_value: 'false'

gps:
  uart_id: uart_gps
  update_interval: 20s

  latitude:
    id: gps_lat
    internal: true

  longitude:
    id: gps_lon
    internal: true

interval:
  - interval: 20s
    then:
      lambda: |-
        if (!id(gps_lat).has_state() || !id(gps_lon).has_state()) {
          return;
        }

        // First GPS fix
        if (!id(has_fix)) {
          id(last_lat) = id(gps_lat).state;
          id(last_lon) = id(gps_lon).state;
          id(has_fix) = true;
          return;
        }

        // Distance calculation
        const float R = 6371000.0;
        float lat1 = id(last_lat) * (M_PI / 180.0);
        float lon1 = id(last_lon) * (M_PI / 180.0);
        float lat2 = id(gps_lat).state * (M_PI / 180.0);
        float lon2 = id(gps_lon).state * (M_PI / 180.0);

        float dlat = lat2 - lat1;
        float dlon = lon2 - lon1;

        float a = sin(dlat / 2) * sin(dlat / 2) +
                  cos(lat1) * cos(lat2) *
                  sin(dlon / 2) * sin(dlon / 2);

        float distance = R * 2 * atan2(sqrt(a), sqrt(1 - a));

        // Update both coordinates if moved ≥50 m
        if (distance >= 50) {
          id(last_lat) = id(gps_lat).state;
          id(last_lon) = id(gps_lon).state;
        }

sensor:
  - platform: template
    id: latitude_sensor
    name: "Latitude"
    unit_of_measurement: "°"
    accuracy_decimals: 6
    icon: mdi:latitude
    lambda: |-
      return id(last_lat);

  - platform: template
    id: longitude_sensor
    name: "Longitude"
    unit_of_measurement: "°"
    accuracy_decimals: 6
    icon: mdi:longitude
    lambda: |-
      return id(last_lon);


With this setup, the GPS feeds directly into Home Assistant and updates automatically while driving, without spamming location changes when we’re stopped as GPS data tends to "dance around" as satellites come and go and the GPS gets a lock on different ones.


Hardware Setup

The NEO-6M GPS module is connected to the ESP32 over UART, using GPIO17 for TX and GPIO16 for RX (be sure to reverse TX and RCV on the GPS module ie Tx to RCV and RCV to TX). The module uses a very small ceramic antenna that plugs directly into the GPS board, keeping the overall footprint compact.


The GPS module is powered from the 3.3V pin on the ESP32, while the ESP32 itself is powered through its USB port using a very small buck converter tied into the USB power lines. This lets me run the entire setup directly off the camper’s 12V system without needing any extra power adapters.


With the GPS separated from the router, the LTE modem is much more stable now, and the ESP32 integrates cleanly into Home Assistant using ESPHome’s API and OTA updates. That means I can update firmware, adjust sensors, or tweak logic from remote, without physically accessing the hardware.


Integration with Home Assistant

All GPS data flows straight into Home Assistant. Latitude and longitude are exposed as sensors, making them easy to use in dashboards and automations. Speed, altitude, course, satellite count, and GPS-provided time are also available.


Right now, the system updates the coordinates only when we’ve moved a significant distance, which keeps the data clean and intentional. Going forward, I plan to use Home Assistant itself to detect when the camper is parked — for example, by monitoring speed and assuming we’re stationary if it stays below 1 mph for a set amount of time — and then treating that last GPS update as the final parked location.


Lessons Learned

  1. Dedicated GPS is worth it: It removes reliability issues and keeps the LTE modem stable.
  2. My router-based GPS can lie: You can have “fresh” data age while latitude and longitude are no longer updating.
  3. ESPHome makes this easy: Template logic and internal variables work well for smoothing GPS updates.
  4. Keep a backup of your YAML: ESPHome’s dashboard sometimes hides the full config, so having a local copy saves headaches if you need to tweak the code at all.
  5. Flash a basic config first: Getting OTA working early lets you finish everything else from remote if you desire. In my case, the comfort of my living room instead of the camper. 😀
  6. Reuse what you have: I already had the ESP32, which made this solution simple and cost-effective. And having a 3d Printer allows me to make a housing for the project when I'm ready
  7. I still need to make it so that the GPS does one final update once the camper comes to rest for more than 10 minutes or so. Currently it can be up to 49 meters off if it updates right before I park.


How Are you Doing GPS?

How do you get GPS coordinates into your mobile Home Assistant setup? Are you running a router module, a standalone board, or some trick I haven’t discovered yet? Drop your setup below — I’m always curious how other campers keep their coordinates in check! 


Tags:

Post a Comment

0 Comments

Post a Comment (0)

#buttons=(Ok, Go it!) #days=(20)

We use cookies to track your site preferences, not your trail through the woods. Happy camping!
Ok, Go it!