# How TeslaMate's Data Logging Cycle Persists Positions During Drives

> Discover how TeslaMate's data logging cycle persists positions during drives with its state machine pipeline and Log process for accurate trip tracking.

- Repository: [TeslaMate/teslamate](https://github.com/teslamate-org/teslamate)
- Tags: internals
- Published: 2026-06-23

---

**TeslaMate uses a state machine-driven pipeline where streaming telemetry is converted to position records and persisted via the Log process, linking each point to an active drive through foreign-key relationships.**

The teslamate-org/teslamate application continuously tracks a vehicle's location while driving by orchestrating data between streaming APIs, state machines, and the database. Understanding how this data logging cycle persists positions during drives reveals the architecture behind the detailed route maps and drive statistics visible in the TeslaMate dashboard.

## How Streaming Data Enters the State Machine

The process begins when the `TeslaApi.Stream` process forwards each `%Stream.Data{}` packet to the vehicle state machine. In [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex), the `Vehicle.handle_event/4` function receives these packets and initiates the position logging sequence.

For every incoming packet, the state machine calls the helper function `create_position/2` to build a map containing latitude, longitude, odometer reading, battery state of charge, temperature, and other telemetry points. This transformation happens inside the `TeslaMate.Vehicles.Vehicle` module before any database interaction occurs.

## Creating and Persisting Position Records

Once the position map is constructed, the vehicle state machine delegates persistence to the `TeslaMate.Log` process. The following call at line 292 in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) insert the record:

```elixir
call(data.deps.log, :insert_position, [data.car, create_position(vehicle, data)])

```

The `Log.insert_position/2` function, defined in [`lib/teslamate/log/position.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/log/position.ex), handles the actual database insertion. This architecture separates state management from data persistence, allowing the vehicle state machine to continue processing streaming data without blocking on I/O operations.

## Associating Positions with Active Drives

The system distinguishes between standalone positions and those belonging to an active drive. When a position arrives for a car that is not currently associated with a drive, the `start_drive/2` helper at line 1795 in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) executes:

```elixir
defp start_drive(position, %Data{car: car, deps: deps} = data) do
  # Insert a new drive row linked to the car

  {:ok, drive} = call(deps.drives, :insert, [car])

  # Store the first position as the start_position of the drive

  {:ok, _pos} = call(deps.log, :insert_position, [drive, position])

  {drive, %{data | drive: drive}}
end

```

While the drive is active, subsequent positions are stored with a foreign-key reference to the drive in the `drive_id` column. The schema definition in [`lib/teslamate/log/drive.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/log/drive.ex) establishes these relationships:

```elixir
belongs_to :start_position, Position
belongs_to :end_position,   Position
has_many   :positions, Position

```

## Handling Drive Completion and Timeouts

The state machine manages drive finalization through timeout events. When the vehicle stops moving long enough to trigger `{:timeout, :store_position}`—handled around line 900 in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex)—the system ends the current drive and updates the `end_position_id` field. The state machine then schedules the next position-storage timeout, creating a continuous loop that persists positions at regular intervals while driving and gracefully handles the transition to parked states.

## Background Elevation Enrichment

Raw GPS coordinates lack elevation data, which TeslaMate adds asynchronously. The separate `TeslaMate.Terrain` state machine periodically queries for positions missing elevation values via `fetch_positions/2`. This background worker batches requests to elevation APIs and updates existing records:

```elixir
def handle_event(event, {:fetch_positions, min_id}, :ready, %Data{} = data) do
  {positions, next} = call(data.deps.log, :get_positions_without_elevation, [min_id, [limit: 1_000]])

  Enum.each(positions, fn p ->
    {:ok, elevation} = @terrain.get_elevation({p.latitude, p.longitude})
    call(data.deps.log, :update_position, [p, %{elevation: elevation}])
  end)

  {:next_state, :ready, data, {:next_event, :internal, {:fetch_positions, next}}}
end

```

This enrichment occurs after initial persistence, ensuring the critical data logging cycle for positions remains fast and uninterrupted by external API calls.

## Querying Persisted Positions for the UI

When users view a specific drive, the `DriveController` pre-loads the complete list of positions ordered by `date`. The `DriveController.show/2` function retrieves these records to render the full route map and telemetry timeline, consuming the data structured and stored by the logging pipeline.

## Summary

- The **vehicle state machine** (`TeslaMate.Vehicles.Vehicle`) orchestrates the data logging cycle by handling streaming packets from `TeslaApi.Stream`.
- **Position creation** occurs via `create_position/2`, which transforms raw API data into structured maps for persistence.
- **Database insertion** happens through `Log.insert_position/2`, called at [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) line 292 and line 1795, linking positions to cars or active drives.
- **Drive lifecycle management** uses `start_drive/2` to establish new trips and timeout events to finalize them, maintaining foreign-key relationships defined in [`lib/teslamate/log/drive.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/log/drive.ex).
- **Elevation data** is appended asynchronously by the `TeslaMate.Terrain` worker, keeping the primary logging path performant.

## Frequently Asked Questions

### How often does TeslaMate store positions during a drive?

TeslaMate persists positions based on timeouts scheduled within the vehicle state machine. When the `{:timeout, :store_position}` event fires, the system inserts the latest telemetry and reschedules the next storage event, creating a continuous logging cycle that balances data granularity with database efficiency.

### What triggers the creation of a new drive versus appending to an existing one?

The `start_drive/2` function in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) creates a new drive record when a position arrives for a car that has no currently active drive. Subsequent positions are appended to this drive via foreign-key associations until the vehicle stops moving long enough to trigger the drive finalization timeout.

### How does TeslaMate handle missing elevation data for recorded positions?

The `TeslaMate.Terrain` state machine runs independently to fetch elevation data for batches of positions lacking altitude values. It queries the database for records without elevation, calls external terrain APIs, and updates the positions via `Log.update_position/2`, ensuring the primary logging cycle remains unobstructed by external service latency.

### Where does the UI retrieve the route data for displaying drives?

The `DriveController.show/2` function in [`lib/teslamate_web/controllers/drive_controller.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/controllers/drive_controller.ex) pre-loads all positions associated with a drive, ordered by date. This allows the interface to render the complete route map and associated telemetry without additional processing of the raw logging data.