How TeslaMate's Data Logging Cycle Persists Positions During Drives

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, 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 insert the record:

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, 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 executes:

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 establishes these relationships:

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—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:

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 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.
  • 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 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →