Ecto Schema for TeslaMate Charges, Drives, and Positions: Complete Technical Guide

TeslaMate persists vehicle telemetry using three core Ecto schemas—Charge, Drive, and Position—located in lib/teslamate/log/ to map raw Tesla API data to structured PostgreSQL tables with relational integrity and precise decimal handling.

TeslaMate, the open-source Tesla data logger built with Elixir and Phoenix, structures all vehicle persistence through these Ecto schemas for charges, drives, and positions. Understanding these models is essential for querying charging history, analyzing trip efficiency, or extending the application with custom telemetry features.

Schema Architecture Overview

The three schemas form the backbone of TeslaMate's logging system under the TeslaMate.Log namespace. Each module follows a consistent pattern using Ecto.Schema and defines a changeset/2 function for data validation and casting.

Schema Source File Primary Table Key Associations
Charge lib/teslamate/log/charge.ex charges belongs_to :charging_process, ChargingProcess
Drive lib/teslamate/log/drive.ex drives belongs_to :car, Car, belongs_to :start_position/end_position, Position, has_many :positions, Position
Position lib/teslamate/log/position.ex positions belongs_to :car, Car, belongs_to :drive, Drive

The Charge Schema

Located at lib/teslamate/log/charge.ex, the Charge schema captures individual data points within a charging session. It stores raw values reported directly by the vehicle's onboard charger.

Key Fields and Validations

  • Temporal data: date (UTC DateTime)
  • Battery metrics: battery_level, charge_energy_added, ideal_battery_range_km, rated_battery_range_km
  • Charger telemetry: charger_power, charger_voltage, charger_actual_current, charger_phases, fast_charger_present, fast_charger_type
  • Environmental: outside_temp, battery_heater_no_power, battery_heater_on, battery_heater

The schema uses read_after_writes: true on decimal fields (such as charge_energy_added and ideal_battery_range_km) to prevent precision loss when PostgreSQL rounds floating-point values. Validations enforce required fields including date, charging_process_id, charge_energy_added, charger_power, and ideal_battery_range_km, plus a positive integer constraint on charger_phases.

Charge Changeset Example

alias TeslaMate.Log.{Charge, Repo}

attrs = %{
  date: DateTime.utc_now(),
  battery_level: 45,
  charge_energy_added: Decimal.new("12.3"),
  charger_power: 7_200,
  ideal_battery_range_km: Decimal.new("350.0"),
  charging_process_id: 123
}

%Charge{}
|> Charge.changeset(attrs)
|> Repo.insert()

The Drive Schema

The Drive schema in lib/teslamate/log/drive.ex represents a complete vehicle trip between two geographical points. It aggregates telemetry into high-level metrics while maintaining references to start and end positions.

Relational Structure

The Drive schema maintains multiple associations for geospatial analysis:

  • Positions: belongs_to :start_position, Position and belongs_to :end_position, Position
  • Geocoding: belongs_to :start_address, Address and belongs_to :end_address, Address
  • Geofencing: belongs_to :start_geofence, GeoFence and belongs_to :end_geofence, GeoFence
  • Telemetry collection: has_many :positions, Position
  • Vehicle: belongs_to :car, Car

Drive Metrics and Fields

Core fields include start_date, end_date, start_km, end_km, and calculated distance. Performance data encompasses speed_max, power_max, and power_min. Temperature tracking uses inside_temp_avg, outside_temp_avg, and odometer values anchor the trip to the vehicle's total mileage. Battery efficiency is calculated via start_ideal_battery_range_km and end_ideal_battery_range_km (plus rated equivalents).

Creating a Drive Record

alias TeslaMate.Log.{Drive, Repo}

attrs = %{
  car_id: 1,
  start_date: ~U[2024-05-01 08:00:00Z],
  end_date: ~U[2024-05-01 09:30:00Z],
  start_km: 12345.0,
  end_km: 12410.6,
  distance: 65.6,
  duration_min: 90,
  start_address_id: 5,
  end_address_id: 9
}

%Drive{}
|> Drive.changeset(attrs)
|> Repo.insert()

The Position Schema

Defined in lib/teslamate/log/position.ex, the Position schema stores one-second (or telemetry-frequency) snapshots of vehicle state while the car is moving or stationary with significant state changes.

Geospatial and Telemetry Fields

Each position records precise location data (latitude, longitude, elevation) alongside motion metrics (speed, power, odometer). Battery state is captured through battery_level, usable_battery_level, est_battery_range_km, and ideal_battery_range_km. Climate data includes inside_temp, outside_temp, and fan_status, while safety systems are monitored via tpms_pressure_fl, tpms_pressure_fr, tpms_pressure_rl, and tpms_pressure_rr (tire pressure monitoring).

Associations and Constraints

Positions belong to both a Car and optionally a Drive (when part of a trip). The schema enforces presence of car_id and valid geolocation coordinates, ensuring data integrity for mapping and range analysis.

Querying Latest Position

alias TeslaMate.Log.{Position, Repo}

latest_position =
  Position
  |> order_by([p], desc: p.date)
  |> limit(1)
  |> Repo.one()

IO.inspect(latest_position.latitude, label: "Current Latitude")

Querying Across Schemas

Because drives and positions maintain bidirectional associations, you can preload related data efficiently. This query retrieves all drives for a specific vehicle with their associated position snapshots:

alias TeslaMate.Log.{Drive, Repo}
import Ecto.Query

car_id = 1

drives =
  Drive
  |> where([d], d.car_id == ^car_id)
  |> preload([:positions, :start_address, :end_address])
  |> Repo.all()

Enum.each(drives, fn drive ->
  IO.puts("Drive #{drive.id}: #{drive.distance} km with #{length(drive.positions)} telemetry points")
end)

Summary

  • Charge (lib/teslamate/log/charge.ex): Captures granular charging session data with decimal precision safeguards and heater status tracking.
  • Drive (lib/teslamate/log/drive.ex): Aggregates trips with start/end geocoding, geofencing support, and calculated distance and efficiency metrics.
  • Position (lib/teslamate/log/position.ex): Stores high-frequency telemetry including GPS coordinates, battery state, climate settings, and tire pressures.
  • All three schemas implement changeset/2 functions that cast attributes and enforce foreign-key constraints to maintain PostgreSQL relational integrity.

Frequently Asked Questions

How does TeslaMate handle decimal precision in charge records?

The Charge schema uses the read_after_writes: true option on decimal fields like charge_energy_added and ideal_battery_range_km. This ensures PostgreSQL does not truncate precision after insert, maintaining accurate energy calculations for efficiency analytics.

Can positions exist without being associated with a drive?

Yes. While positions collected during a trip belong to a drive via belongs_to :drive, Drive, the association is optional. Positions recorded while the vehicle is parked (e.g., periodic "asleep" checks or pre-conditioning events) exist independently with only a car_id reference.

What is the relationship between drives and addresses in the schema?

The Drive schema uses belongs_to associations for both start_address and end_address, which reference the Address schema. These are populated through reverse-geocoding services after a drive completes, enabling location-based filtering and trip naming (e.g., "Home to Office").

Where are the database constraints defined for these schemas?

Foreign-key constraints and database-level validations are enforced through Ecto changesets in each schema file (charge.ex, drive.ex, position.ex). The changeset/2 functions validate required fields and check constraints (such as positive charger_phases), while the actual database migrations (not shown in the schema files) define the PostgreSQL table structures and referential integrity rules.

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 →