# How TeslaMate's MQTT Handler and Vehicle Subscriber Coordinate Messaging

> Discover how TeslaMate's MQTT Handler and Vehicle Subscriber coordinate messaging using Phoenix PubSub for efficient telemetry broadcasting and routing to vehicle modules.

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

---

**TeslaMate coordinates messaging between its MQTT Handler and Vehicle Subscriber through an internal Phoenix PubSub layer, where the handler broadcasts parsed telemetry to a dedicated topic and the subscriber routes those events to vehicle-specific domain modules.**

The `teslamate-org/teslamate` repository implements a robust publish-subscribe pattern to bridge external MQTT brokers with its internal vehicle state management. Understanding how the MQTT Handler and Vehicle Subscriber coordinate messaging reveals the event-driven architecture that keeps telemetry ingestion decoupled from business logic.

## Architecture Overview

### The PubSub Coordination Layer

At the core of TeslaMate's messaging coordination sits **Phoenix PubSub**, which acts as the internal message bus. This design ensures that the MQTT Handler ([`lib/teslamate/mqtt/handler.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/handler.ex)) never directly invokes vehicle logic. Instead, it publishes normalized events to the `mqtt:incoming` topic, allowing the Vehicle Subscriber ([`lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex)) to consume and route them asynchronously.

## The MQTT Handler: Ingesting and Broadcasting

Located in [`lib/teslamate/mqtt/handler.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/handler.ex), the MQTT Handler serves as the entry point for all external telemetry. When the Tortoise MQTT client receives a packet, the handler parses the JSON payload to extract the vehicle identifier, event type (e.g., `charge_state` or `drive_state`), and associated data.

The handler then broadcasts a structured tuple `{vehicle_id, event, data}` to the internal PubSub topic:

```elixir
defmodule Teslamate.Mqtt.Handler do
  alias Phoenix.PubSub

  @topic "mqtt:incoming"

  def handle_incoming(%{topic: topic, payload: payload}) do
    with {:ok, %{"vehicle_id" => vid, "event" => ev, "data" => data}} <- Jason.decode(payload) do
      PubSub.broadcast(Teslamate.PubSub, @topic, {vid, ev, data})
    end
  end
end

```

This broadcast operation completes the handler's responsibility, leaving downstream processing to subscribers.

## The Vehicle Subscriber: Routing to Domain Modules

The [`lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex) module implements a GenServer that subscribes to the `mqtt:incoming` topic during initialization. Its `handle_info/2` callback receives the tuple broadcast by the handler and performs pattern matching to dispatch commands to the appropriate domain modules.

```elixir
defmodule Teslamate.Mqtt.PubSub.VehicleSubscriber do
  use GenServer
  alias Phoenix.PubSub

  @incoming_topic "mqtt:incoming"

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
  end

  @impl true
  def init(state) do
    PubSub.subscribe(Teslamate.PubSub, @incoming_topic)
    {:ok, state}
  end

  @impl true
  def handle_info({vehicle_id, event, data}, state) do
    case event do
      "charge_state" -> Teslamate.Vehicles.Vehicle.update_charge(vehicle_id, data)
      "drive_state"  -> Teslamate.Vehicles.Vehicle.update_drive(vehicle_id, data)
      _ -> :ignore
    end

    {:noreply, state}
  end
end

```

After processing, the subscriber may trigger additional internal events or publish responses back to the MQTT broker via `Teslamate.Mqtt.Publisher.publish/3`.

## Step-by-Step Coordination Flow

The messaging coordination follows a strict pipeline:

1. **External Broker to Handler**: Raw MQTT packets arrive at `Teslamate.Mqtt.Handler` from the configured broker.
2. **Handler to PubSub**: The handler parses JSON and broadcasts `{vehicle_id, event, data}` to the `mqtt:incoming` topic using `Phoenix.PubSub.broadcast/3`.
3. **PubSub to Vehicle Subscriber**: The `VehicleSubscriber` GenServer, subscribed to `mqtt:incoming`, receives the message via `handle_info/2`.
4. **Subscriber to Domain Logic**: The subscriber routes the event to `Teslamate.Vehicles.Vehicle` functions like `update_charge/2` or `update_drive/2`.
5. **Optional Response**: The subscriber may publish acknowledgments or state updates back through `Teslamate.Mqtt.Publisher`.

This loose coupling allows the handler to remain agnostic about which modules consume specific vehicle events, while the subscriber can be extended with new event types without modifying handler code.

## Integration in the Supervision Tree

To ensure the subscriber is active at runtime, it must be registered in the application supervision tree. In [`lib/teslamate/application.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/application.ex), the `VehicleSubscriber` is started as a child process:

```elixir
defmodule Teslamate.Application do
  use Application

  def start(_type, _args) do
    children = [
      {Teslamate.Mqtt.PubSub.VehicleSubscriber, []},
      # … other workers …

    ]

    Supervisor.start_link(children, strategy: :one_for_one, name: Teslamate.Supervisor)
  end
end

```

## Summary

- **Decoupled Design**: The MQTT Handler and Vehicle Subscriber communicate exclusively through Phoenix PubSub, never calling each other directly.
- **Topic-Based Routing**: All incoming telemetry flows through the `mqtt:incoming` topic, enabling multiple consumers to subscribe without code changes.
- **Clear Separation**: [`lib/teslamate/mqtt/handler.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/handler.ex) handles external protocol concerns (JSON parsing, MQTT topics), while [`lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex) manages internal domain routing.
- **Extensibility**: New vehicle event types can be supported by adding clauses to the subscriber's `handle_info/2` function without touching MQTT ingestion logic.

## Frequently Asked Questions

### What is the purpose of the `mqtt:incoming` topic in TeslaMate?

The `mqtt:incoming` topic serves as the internal Phoenix PubSub channel that bridges the MQTT Handler and Vehicle Subscriber. It standardizes communication by carrying tuples of `{vehicle_id, event, data}`, allowing any number of subscribers to react to incoming telemetry without the handler needing to know about downstream consumers.

### How does the Vehicle Subscriber handle different types of MQTT events?

The subscriber uses Elixir pattern matching in its `handle_info/2` callback to distinguish event types. When it receives a message tuple, it matches the `event` field against strings like `"charge_state"` or `"drive_state"`, then dispatches to the appropriate function in `Teslamate.Vehicles.Vehicle` to update the vehicle's internal state.

### Can the MQTT Handler publish messages back to the external broker?

No, the MQTT Handler is designed exclusively for ingesting incoming messages. For outbound communication, TeslaMate uses `Teslamate.Mqtt.Publisher`, which provides a `publish/3` function. The Vehicle Subscriber or other domain modules may invoke this publisher to send acknowledgments or telemetry updates back to the MQTT broker.

### Where are the MQTT Handler and Vehicle Subscriber defined in the source code?

The MQTT Handler is defined in [`lib/teslamate/mqtt/handler.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/handler.ex), while the Vehicle Subscriber resides in [`lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex). Both modules coordinate through the PubSub system configured in [`lib/teslamate/mqtt/pubsub.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/mqtt/pubsub.ex), with the subscriber typically started under the main application supervisor in [`lib/teslamate/application.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/application.ex).