How TeslaMate's MQTT Handler and Vehicle Subscriber Coordinate Messaging

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) 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) to consume and route them asynchronously.

The MQTT Handler: Ingesting and Broadcasting

Located in 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:

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 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.

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, the VehicleSubscriber is started as a child process:

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 handles external protocol concerns (JSON parsing, MQTT topics), while 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, while the Vehicle Subscriber resides in lib/teslamate/mqtt/pubsub/vehicle_subscriber.ex. Both modules coordinate through the PubSub system configured in lib/teslamate/mqtt/pubsub.ex, with the subscriber typically started under the main application supervisor in lib/teslamate/application.ex.

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 →