# How the TeslaMate Vehicles Supervisor Manages Multiple Tesla Vehicles Concurrently

> Discover how the TeslaMate Vehicles supervisor efficiently manages multiple Tesla vehicles concurrently using isolated GenStateMachine processes and a one for one strategy for parallel operations and independent failure handling.

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

---

**The `TeslaMate.Vehicles` supervisor manages multiple Tesla vehicles concurrently by spawning an isolated `GenStateMachine` process for each car and coordinating them through a `:one_for_one` supervision strategy, enabling parallel operations via `Task.async_stream` while isolating failures to individual vehicles.**

The open-source TeslaMate platform achieves fleet-scale vehicle monitoring by leveraging Elixir's actor-model concurrency. In the `teslamate-org/teslamate` repository, the **Vehicles supervisor** (`TeslaMate.Vehicles`) dynamically instantiates per-vehicle processes that operate independently, allowing the system to poll, stream, and log data from dozens of Teslas simultaneously without blocking or cross-contamination.

## Architecture Overview

The supervision tree relies on two core components: a dynamic supervisor that manages the fleet, and individual state machines that encapsulate per-vehicle logic.

- **`TeslaMate.Vehicles`** ([`lib/teslamate/vehicles.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles.ex)): A **dynamic supervisor** that starts child specifications for every registered car. It implements a **`:one_for_one`** restart strategy with limits of 5 restarts within 60 seconds.
- **`TeslaMate.Vehicles.Vehicle`** ([`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex)): A **GenStateMachine** that handles polling loops, Tesla streaming API connections, state transitions (online, asleep, charging), and fuse-based error handling.
- **`TeslaMate.Vehicles.Vehicle.Summary`** ([`lib/teslamate/vehicles/vehicle/summary.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle/summary.ex)): An immutable data structure representing the current state of a single vehicle, built from the GenStateMachine's internal `%Data{}` struct.

## Supervisor Startup and Child Specification

When the application boots, `TeslaMate.Application` starts the supervisor via `TeslaMate.Vehicles.start_link/1`. The `init/1` callback dynamically constructs child specs from the Tesla API or fallback cache.

```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(fn {_mod, car: %Car{id: id}} -> id end)
    |> Enum.filter(fn {_mod, car: %Car{settings: settings}} -> settings.enabled end)

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

```

The supervisor performs three critical filtering steps:

1. **Fetches vehicle data** via `list_vehicles!/0` from the Tesla API or historic logs.
2. **Deduplicates** entries by `car.id` to prevent duplicate processes for the same vehicle.
3. **Filters by enabled status**, ensuring only cars with `settings.enabled == true` receive dedicated processes.

## The Vehicle GenStateMachine Process

Each child process is uniquely identified by the module name and car ID combination, ensuring distinct PIDs for concurrent execution.

```elixir
def child_spec(arg) do
  %{
    id: :"#{__MODULE__}_#{Keyword.fetch!(arg, :car).id}",
    start: {__MODULE__, :start_link, [arg]}
  }
end

```

The **GenStateMachine** encapsulates:

- **Independent polling intervals**: `asleep_interval/0` and `driving_interval/0` calculate sleep durations per state.
- **Per-vehicle streaming**: `connect_stream/1` establishes WebSocket connections to Tesla's streaming API, isolated to the process.
- **Fuse-based circuit breaking**: Uses `:fuse.ask/2` to prevent cascading failures when the Tesla API returns authentication errors or vehicle-not-found responses.

Because every vehicle operates in its own lightweight BEAM process, a charging Model 3 and a driving Model S maintain separate state machines without interference.

## Parallel Fleet Operations

To aggregate status across the entire fleet without sequential blocking, the `Vehicles.list/0` function leverages concurrent task spawning.

```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`**: Limits concurrent summary requests to prevent overwhelming the API or database.
- **`ordered: false`**: Returns results as they complete, reducing tail latency.
- **`timeout: 5_000`**: Fails individual requests after 5 seconds rather than blocking indefinitely.

`Supervisor.which_children/1` retrieves the current PIDs of all active `Vehicle` processes, enabling truly parallel data retrieval across the fleet.

## Fault Isolation and Recovery

The `:one_for_one` strategy ensures that a crash in one vehicle process—whether from a malformed API response or a streaming timeout—does not propagate to others. When a child terminates abnormally, the supervisor restarts only that specific vehicle process up to the configured restart limits.

For catastrophic failures (such as blown fuses from persistent API errors), the `restart/0` function provides a hard reset mechanism:

```elixir
def restart do
  with :ok <- Supervisor.stop(@name, :normal),
       :ok <- block_until_started(250) do
    :ok
  end
end

```

The `block_until_started/1` helper polls `Process.whereis/1` until the supervisor PID regenerates, ensuring the fleet reinitializes with fresh state before accepting new commands.

## Practical Code Examples

### Starting the Supervisor

In a typical deployment, the supervisor launches automatically via the application tree:

```elixir

# In lib/teslamate/application.ex

children = [
  {TeslaMate.Vehicles, []},
  # ... other children ...

]

Supervisor.start_link(children, strategy: :one_for_one, name: TeslaMate.Supervisor)

```

### Listing All Vehicles

Retrieve current summaries for the entire fleet:

```elixir
iex> TeslaMate.Vehicles.list()
[
  %TeslaMate.Vehicles.Vehicle.Summary{
    car: %TeslaMate.Log.Car{id: 1, name: "Model 3"},
    state: :online,
    # ...

  },
  %TeslaMate.Vehicles.Vehicle.Summary{...}
]

```

### Controlling Individual Vehicles

Programmatically suspend and resume data collection for specific cars:

```elixir

# Suspend logging for car ID 1

iex> TeslaMate.Vehicles.suspend_logging(1)
:ok

# Resume logging

iex> TeslaMate.Vehicles.resume_logging(1)
:ok

```

### Subscribing to Real-time Updates

LiveView components and Phoenix channels can subscribe to per-vehicle PubSub topics:

```elixir

# Subscribe to summary updates for car ID 42

:ok = TeslaMate.Vehicles.subscribe_to_summary(42)

# In your handle_info callback:

def handle_info(%TeslaMate.Vehicles.Vehicle.Summary{} = summary, socket) do
  {:noreply, assign(socket, :summary, summary)}
end

```

## Summary

- **Dynamic supervision**: `TeslaMate.Vehicles` is a dynamic supervisor in [`lib/teslamate/vehicles.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles.ex) that spawns a `GenStateMachine` for each enabled vehicle at boot time.
- **Process isolation**: Each Tesla runs in its own process with distinct supervision IDs (`:"Elixir.TeslaMate.Vehicles.Vehicle_#{id}"`), preventing cross-vehicle failures.
- **Concurrent aggregation**: The `list/0` function uses `Task.async_stream/3` with `max_concurrency: 10` to fetch summaries in parallel rather than sequentially.
- **Fault tolerance**: The `:one_for_one` restart strategy with limits of 5 restarts per 60 seconds ensures transient API failures don't crash the entire supervisor.
- **State machine encapsulation**: Per-vehicle logic including polling intervals, streaming connections, and fuse handling resides in `TeslaMate.Vehicles.Vehicle`.

## Frequently Asked Questions

### What happens if one vehicle process crashes?

The supervisor automatically restarts only the crashed child process due to the **`:one_for_one`** strategy defined in [`lib/teslamate/vehicles.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles.ex). All other vehicle processes continue running uninterrupted. If the process exceeds 5 restarts within 60 seconds, the supervisor itself terminates, triggering the application to reload the vehicle list via the `restart/0` function.

### How does TeslaMate handle API rate limits across multiple vehicles?

Each `Vehicle` GenStateMachine manages its own polling schedule via independent timers (`Process.send_after/3`) calculated by `asleep_interval/0` and `driving_interval/0`. The **`Task.async_stream`** in `list/0` limits concurrent summary requests to **10 simultaneous calls**, preventing thundering-herd scenarios when querying the Tesla API across large fleets.

### Can TeslaMate scale to monitor dozens of vehicles simultaneously?

Yes. Because each vehicle runs as a lightweight BEAM process consuming minimal memory (typically kilobytes per process), the supervisor can theoretically manage hundreds of vehicles. The primary bottleneck becomes the Tesla API rate limits and database connection pooling, not the Elixir process overhead.

### How do I programmatically control logging for specific vehicles?

Use the public API functions **`suspend_logging/1`** and **`resume_logging/1`**, passing the car ID as an integer. These functions locate the specific `Vehicle` process by ID and send synchronous messages to pause or resume the polling loop, immediately stopping data ingestion without affecting other vehicles in the fleet.