# How the TeslaMate Vehicles Module Manages Multiple Tesla Vehicles Simultaneously

> Manage many Tesla vehicles at once with TeslaMate. Discover how the Vehicles module uses GenStateMachine processes for parallel monitoring, fault isolation, and independent state tracking across your fleet.

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

---

**The TeslaMate Vehicles module manages multiple Tesla vehicles simultaneously by spawning an isolated GenStateMachine process for each car under a single OTP Supervisor, enabling parallel monitoring, fault isolation, and independent state tracking across the entire fleet.**

TeslaMate is a self-hosted data logger for Tesla vehicles built with Elixir and Erlang/OTP. Its ability to track driving statistics, charging sessions, and battery health for multiple cars concurrently relies on a process-oriented architecture where the `TeslaMate.Vehicles` module acts as a supervision root. This design ensures that each vehicle operates as an independent entity, preventing failures in one car's API connection from affecting the monitoring of others.

## Supervisor Architecture in `TeslaMate.Vehicles`

At the core of the fleet management system lies the `TeslaMate.Vehicles` supervisor. When the application starts, this module queries the Tesla API to discover all available vehicles and dynamically constructs a supervision tree.

The `init/1` function in [`lib/teslamate/vehicles.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles.ex) fetches the vehicle list via `list_vehicles!/0` (which calls `TeslaMate.Api.list_vehicles/0`), removes duplicates with `Enum.uniq_by/2`, filters out disabled cars, and generates a child specification for each remaining vehicle:

```elixir
def init(opts) do
  children =
    opts
    |> Keyword.get_lazy(:vehicles, &list_vehicles!/0)
    |> Enum.map(&{Keyword.get(opts, :vehicle, Vehicle), car: create_or_update!(&1)})
    |> Enum.uniq_by(& &1.car.id)
    |> Enum.filter(& &1.car.settings.enabled)

  Supervisor.init(children,
    strategy: :one_for_one,
    max_restarts: 5,
    max_seconds: 60
  )
end

```

The supervisor employs a **`:one_for_one`** restart strategy. If a single vehicle process crashes due to a network timeout or API error, only that specific process restarts, leaving the other vehicle processes unaffected. Each child process is created using `Vehicle.child_spec/1`, which registers the process with a unique name based on the car's database ID.

## The Vehicle State Machine (`TeslaMate.Vehicles.Vehicle`)

Each Tesla vehicle runs as an independent **GenStateMachine** within [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex). This module encapsulates all per-vehicle logic, including API polling, streaming API connections, state transitions, and data logging.

The state machine maintains distinct states such as `{:asleep, interval}`, `{:online, _}`, `{:driving, status, drive}`, and `{:charging, proc}`. Upon initialization, the process:

1. Registers API error fuses for circuit breaker protection
2. Subscribes to settings changes via `TeslaMate.Settings`
3. Schedules an immediate data fetch using `{:next_event, :internal, :fetch}`

When the streaming API is enabled in `CarSettings.use_streaming_api`, the process spawns a dedicated stream connection via `connect_stream/1`. Stream messages (`{:stream, %Stream.Data{}}`) drive real-time state transitions, such as detecting when a drive starts or charging begins, while traditional polling continues as a fallback.

## Fleet Coordination and Parallel Execution

The Vehicles module manages multiple Tesla vehicles simultaneously through complete process isolation. Each vehicle maintains its own internal `%Data{}` struct containing the car record, last API response, stream PID, and current state. No shared mutable state exists between vehicles.

To aggregate data across the fleet, the supervisor provides the `list/0` function, which concurrently queries all vehicle processes:

```elixir
def list do
  Supervisor.which_children(@name)
  |> Task.async_stream(fn {_, pid, _, _} -> Vehicle.summary(pid) end,
        ordered: false, max_concurrency: 10, timeout: 5_000)
  |> Enum.map(fn {:ok, vehicle} -> vehicle end)
  |> Enum.sort_by(&{&1.car.display_priority, &1.car.id})
end

```

This implementation uses `Task.async_stream/3` with `max_concurrency: 10` to request summaries from all vehicles in parallel. If one vehicle is offline or experiencing high latency, the five-second timeout ensures the call returns without blocking the entire fleet query.

## Real-Time Communication via PubSub

Each vehicle process broadcasts updates through Phoenix PubSub to decouple data producers from consumers. The `handle_event/4` callback in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) emits two primary topics:

- **Summary updates** – Compact state representations sent to `summary_topic(car_id)` for the web interface
- **Fetch status** – Indicators of whether an API request is in progress

```elixir
def handle_event(:internal, {:broadcast_summary, state, data}, ...) do
  call(data.deps.pubsub, :broadcast,
    [TeslaMate.PubSub, summary_topic(data.car.id), payload])
end

```

The `TeslaMate.Mqtt.PubSub.VehicleSubscriber` module subscribes to these topics and forwards vehicle data to MQTT brokers, enabling integration with home automation systems without modifying the core vehicle processes.

## Practical Fleet Management API

The `TeslaMate.Vehicles` module exposes a unified API for interacting with the entire fleet or individual cars:

```elixir

# Retrieve all vehicle summaries ordered by display priority

summaries = TeslaMate.Vehicles.list()

# Subscribe the current process to live updates for a specific vehicle

TeslaMate.Vehicles.subscribe_to_summary(3)

# Temporarily pause data logging for a specific car

:ok = TeslaMate.Vehicles.suspend_logging(3)

# Resume logging

:ok = TeslaMate.Vehicles.resume_logging(3)

```

These functions delegate to the respective GenStateMachine processes via helper functions in [`lib/teslamate/vehicles.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles.ex), ensuring consistent behavior whether managing one vehicle or twenty.

## Summary

- **Process-per-vehicle architecture**: Each Tesla runs as an isolated GenStateMachine under the `TeslaMate.Vehicles` supervisor, ensuring fault tolerance via the `:one_for_one` strategy.
- **Dynamic fleet discovery**: The supervisor queries `TeslaMate.Api.list_vehicles/0` at startup and creates child specs using `Vehicle.child_spec/1`, filtering duplicates and disabled entries.
- **Parallel data collection**: Fleet-wide queries use `Task.async_stream` to concurrently fetch vehicle summaries without blocking.
- **State machine encapsulation**: All per-vehicle logic (polling, streaming, logging) lives in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex), with states like `{:driving, status, drive}` tracking real-time conditions.
- **Decoupled communication**: Vehicles broadcast updates via PubSub topics (`summary_topic/1`), consumed by the web UI and MQTT subscribers independently.

## Frequently Asked Questions

### How does TeslaMate prevent one vehicle's API failure from affecting the entire fleet?

Because each vehicle operates as an independent OS process under the `:one_for_one` supervision strategy, a crash or timeout in one vehicle's GenStateMachine triggers only a single process restart. The supervisor does not restart sibling processes, ensuring that API issues with one Tesla do not interrupt data collection for others.

### What happens when a new Tesla is added to the account after TeslaMate has started?

The current implementation loads the vehicle list once at startup via `list_vehicles!/0`. To add a new vehicle, the TeslaMate application requires a restart so the supervisor can re-execute `init/1` and generate a new child spec for the additional car. The system caches vehicle data in the database, allowing it to fall back to stored records if the Tesla API is unreachable during startup.

### Can TeslaMate monitor vehicles with different API tokens simultaneously?

Yes. While the standard configuration uses a single global API token, the architecture supports dependency injection through the `opts` parameter in `start_link/1`. Each `Vehicle` process receives its dependencies (including API credentials) via the `car` struct and `data.deps` map, theoretically allowing different tokens per vehicle if the supervisor initialization is customized.

### How does the streaming API coexist with traditional polling?

The GenStateMachine manages both data sources concurrently. When `use_streaming_api` is enabled, the process spawns a stream connection that sends messages directly to the vehicle process mailbox. These messages trigger immediate state transitions (e.g., from `online` to `driving`). Simultaneously, the process maintains scheduled polling intervals as a fallback mechanism to ensure data continuity if the streaming connection drops.