# How Phoenix PubSub Enables Real-Time Web Updates in TeslaMate: Event-Driven Architecture Explained

> Discover how TeslaMate leverages Phoenix PubSub for event-driven architecture and real-time web updates, pushing instant UI changes to LiveViews without page reloads.

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

---

**TeslaMate uses Phoenix PubSub as a centralized message bus that decouples backend state machines from frontend LiveViews, enabling instantaneous push-based UI updates without page reloads.**

TeslaMate is an open-source data logger and visualization tool for Tesla vehicles, built with Elixir and Phoenix. Real-time communication between the vehicle state machines, MQTT handlers, and the web interface relies entirely on **Phoenix PubSub**, a distributed publish-subscribe system. This architecture ensures that whenever vehicle data changes, settings update, or import processes progress, the web interface reflects those changes immediately.

## Initializing the PubSub Infrastructure

The PubSub system is established early in the application lifecycle. In [`lib/teslamate/application.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/application.ex), the supervision tree starts a `Phoenix.PubSub` process registered under the name `TeslaMate.PubSub`:

```elixir
children = [
  # ... other workers

  {Phoenix.PubSub, name: TeslaMate.PubSub},
  # ...

]

```

This single process acts as the message broker for the entire application, allowing any process to broadcast messages to topics and any other process to subscribe to them. By placing PubSub in the supervision tree, TeslaMate ensures that the message bus restarts automatically if it crashes, maintaining system resilience.

## Publishing Events from Backend Processes

Backend components publish events through the PubSub bus whenever state changes occur. This decouples data producers from consumers, allowing the vehicle state machines to operate independently of the web layer.

### Vehicle State Machine Broadcasts

The `TeslaMate.Vehicles.Vehicle` GenStateMachine manages individual vehicle connections and publishes two primary message types. After each state transition, `broadcast_summary/1` in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) constructs a payload containing the current state, timestamps, health status, and geofence information, then broadcasts it:

```elixir
defp broadcast_summary(state, %Data{car: car, last_response: vehicle} = data) do
  payload = Summary.into(vehicle, %{
    state: state,
    since: data.last_state_change,
    healthy?: healthy?(car.id),
    elevation: data.elevation,
    geofence: data.geofence,
    car: car
  })

  call(data.deps.pubsub, :broadcast, [
    TeslaMate.PubSub,
    summary_topic(car.id),
    payload
  ])
end

```

The function uses `Phoenix.PubSub.broadcast/3` to send the message to a topic specific to that car ID (`summary_topic(car.id)`), ensuring that only subscribers interested in that particular vehicle receive the update.

Additionally, when the system initiates or completes data fetches from Tesla's API, `broadcast_fetch/2` publishes status updates:

```elixir
def handle_event(:internal, {:broadcast_fetch, status}, _state, data) do
  call(data.deps.pubsub, :broadcast, [
    TeslaMate.PubSub,
    fetch_topic(data.car.id),
    {:status, status}
  ])
end

```

This allows the UI to display loading indicators or "syncing" states in real time.

### Settings Changes

The `TeslaMate.Settings` module in [`lib/teslamate/settings.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/settings.ex) publishes notifications when vehicle configurations change. The `subscribe_to_changes/1` function and its corresponding broadcast mechanism use PubSub to alert LiveViews that settings such as sleep mode preferences or unit preferences have been modified:

```elixir
def subscribe_to_changes(car_id) do
  Phoenix.PubSub.subscribe(TeslaMate.PubSub, topic(car_id))
end

```

### MQTT Integration Bridge

TeslaMate's MQTT subsystem also participates in the PubSub ecosystem. The `TeslaMate.Mqtt.PubSub.VehicleSubscriber` module (located in [`lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex)) subscribes to vehicle summary topics and forwards relevant data to MQTT brokers. This creates a bidirectional flow where MQTT messages can trigger PubSub events and vice versa, ensuring consistency across all integration points.

## Subscribing to Topics in LiveViews

Frontend components consume PubSub messages through Phoenix LiveView subscriptions. Rather than polling for updates, LiveViews register themselves as subscribers during the mount phase.

In [`lib/teslamate_web/live/car_live/summary.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/live/car_live/summary.ex), the LiveView establishes subscriptions to both summary and fetch topics for the specific car being displayed:

```elixir
defmodule TeslaMateWeb.Live.CarLive.Summary do
  use TeslaMateWeb, :live_view
  alias TeslaMate.Vehicles

  @impl true
  def mount(_params, _session, socket) do
    car = socket.assigns.car
    :ok = Vehicles.subscribe_to_summary(car.id)
    :ok = Vehicles.subscribe_to_fetch(car.id)
    {:ok, assign(socket, :summary, nil)}
  end
  # ...

end

```

These calls delegate to the public API in [`lib/teslamate/vehicles.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles.ex), which wraps the PubSub subscribe calls. Similarly, the import progress UI in [`lib/teslamate_web/live/import_live/index.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/live/import_live/index.ex) subscribes to import-specific topics via `TeslaMate.Import.subscribe/0`, allowing users to watch import completion status in real time.

## Handling Messages and Rendering Updates

Once subscribed, LiveViews implement `handle_info/2` callbacks to process incoming PubSub messages. When a message arrives, the LiveView updates its socket assigns, triggering a diff that Phoenix LiveView pushes to the browser via WebSocket.

For example, `TeslaMateWeb.Live.CarLive.Summary` handles both summary updates and fetch status changes:

```elixir
@impl true
def handle_info({:summary, summary}, socket) do
  {:noreply, assign(socket, :summary, summary)}
end

@impl true
def handle_info({:status, fetching?}, socket) do
  {:noreply, assign(socket, :fetching, fetching?)}
end

```

Because PubSub guarantees at-most-once delivery with low latency, these updates propagate to connected browsers within milliseconds of the backend state changing. The architecture supports horizontal scaling—multiple TeslaMate instances can share PubSub messages across nodes when configured with the Redis or PG2 adapters, though the default installation uses the single-node PG (Process Groups) adapter.

## Summary

- **Centralized message bus**: `TeslaMate.PubSub` runs as a supervised process in [`lib/teslamate/application.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/application.ex), providing the backbone for all real-time communication.
- **Topic-based routing**: Vehicle-specific topics (created via `summary_topic/1` and `fetch_topic/1`) ensure that updates reach only relevant subscribers, preventing broadcast storms.
- **Decoupled architecture**: The `TeslaMate.Vehicles.Vehicle` GenStateMachine publishes events without knowing which LiveViews (if any) are listening, enabling independent scaling of backend and frontend components.
- **LiveView integration**: Subscriptions occur during `mount/3` in LiveViews like [`lib/teslamate_web/live/car_live/summary.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/live/car_live/summary.ex), with `handle_info/2` callbacks updating the UI instantly when messages arrive.
- **MQTT bridge**: The PubSub system extends beyond the web interface to include MQTT subscribers, creating a unified event layer across all TeslaMate integrations.

## Frequently Asked Questions

### How does TeslaMate ensure that only specific car dashboards receive updates?

TeslaMate uses topic scoping in the PubSub system. Functions like `summary_topic(car_id)` and `fetch_topic(car_id)` in [`lib/teslamate/vehicles/vehicle.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/vehicles/vehicle.ex) generate unique topic strings for each vehicle. When a LiveView mounts, it subscribes only to topics matching the current `car.id`, ensuring that the browser receives updates exclusively for the vehicle being viewed, rather than all vehicles in the system.

### What happens to PubSub messages if the LiveView process crashes?

Phoenix PubSub maintains the subscription registry separately from the subscriber processes. If a LiveView crashes and restarts, it must re-subscribe during its new `mount/3` call. Missed messages during the brief downtime are not replayed (PubSub provides at-most-once delivery), but the LiveView immediately receives the next broadcast, quickly resynchronizing with the current vehicle state.

### Can TeslaMate scale across multiple servers using this PubSub architecture?

Yes. While the default configuration uses the PG (Process Groups) adapter suitable for single-node deployments, Phoenix PubSub supports distributed adapters like Redis or the built-in `Phoenix.PubSub.PG2`. By switching the adapter in [`lib/teslamate/application.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/application.ex), multiple TeslaMate nodes can share PubSub messages across the cluster, maintaining real-time updates for users regardless of which server handles their WebSocket connection.

### How does the import process communicate progress to the web interface?

The import subsystem uses the same PubSub infrastructure as the vehicle state machines. In [`lib/teslamate_web/live/import_live/index.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate_web/live/import_live/index.ex), the LiveView calls `TeslaMate.Import.subscribe/0` during initialization, which registers it for import-specific topics. As the import worker processes Tesla data files, it broadcasts progress messages through `TeslaMate.PubSub`, allowing the LiveView to render progress bars and completion status without polling the database.