How the TeslaMate Streaming Module Receives Real‑Time Vehicle Data

The TeslaMate streaming module receives real-time vehicle data by establishing a persistent WebSocket connection to Tesla's streaming API, subscribing to specific telemetry columns with OAuth authentication, and parsing incoming JSON frames into typed structs that are forwarded to a receiver callback.

The teslamate-org/teslamate repository implements a robust streaming architecture that captures live telemetry from Tesla vehicles with minimal latency. Understanding how the streaming module receives real-time vehicle data reveals the system's use of persistent WebSocket connections, structured data parsing, and resilient error handling to maintain continuous data flow. The core implementation resides in lib/tesla_api/stream.ex, which orchestrates the end-to-end streaming lifecycle.

Establishing the WebSocket Connection

The streaming lifecycle begins when the vehicle process initializes a connection to Tesla's servers. The module uses the WebSockex library to maintain a persistent WebSocket connection, ensuring low-latency bidirectional communication.

Initializing the Stream Process

The entry point is TeslaApi.Stream.start_link/1, which accepts a keyword list containing the vehicle_id, an authenticated TeslaApi.Auth struct, and a receiver callback function. According to the source code at lines 26‑44 of lib/tesla_api/stream.ex, the function constructs the initial state and determines the appropriate streaming endpoint.

{:ok, pid} = TeslaApi.Stream.start_link(
  vehicle_id: car.id,
  auth: auth,
  receiver: &MyApp.Vehicle.handle_stream/1
)

The function then invokes WebSockex.start_link/4 at lines 46‑53, passing the constructed URL, the module reference (__MODULE__), the initial state, and TLS options to establish the connection.

Regional Endpoint Configuration

The WebSocket URL is dynamically built based on the authentication region. As implemented in lib/tesla_api/stream.ex, the code differentiates between the Chinese region (:chinese) and global endpoints, optionally incorporating a custom TOKEN environment variable for the streaming path. This ensures the connection targets the correct geographical API infrastructure while respecting Tesla's regional partitioning requirements.

Subscribing to Telemetry Data

Once the WebSocket connection is established, the module must explicitly subscribe to specific data columns to receive updates.

The Subscription Message

Upon connection success, handle_connect/2 (lines 66‑71) immediately sends the process a :subscribe message. The corresponding handler constructs a JSON payload with msg_type: "data:subscribe_oauth", embedding the OAuth token from the auth struct and the vehicle ID as the tag. This subscription request is transmitted via frame!/1 at lines 60‑62.

connect_message = %{
  msg_type: "data:subscribe_oauth",
  token: auth.token,
  value: Enum.join(@columns, ","),
  tag: "#{vehicle_id}"
}
WebSockex.cast(pid, {:send, frame!(connect_message)})

Configurable Data Columns

The module defines a module attribute @columns at lines 18‑20 of lib/tesla_api/stream.ex, specifying which telemetry fields to receive. This includes critical metrics such as speed, soc (state of charge), est_lat, est_lng, power, odometer, and elevation. The subscription value is a comma-separated string of these column names, ensuring the streaming server only transmits requested data points, optimizing bandwidth and processing overhead.

Receiving and Parsing Real‑Time Frames

Incoming WebSocket frames contain telemetry updates that must be decoded and transformed into usable data structures.

Handling Incoming Data Updates

The handle_frame/2 function processes all incoming text frames. As shown at lines 23‑28 of lib/tesla_api/stream.ex, it uses Jason.decode/1 to parse the JSON payload. The code specifically matches messages where "msg_type" equals "data:update" and the "tag" matches the vehicle ID, ensuring the process only handles relevant telemetry for its assigned vehicle.

Struct Conversion and Callback Delivery

The raw value from Tesla arrives as a comma-separated string (e.g., "0,0,62,..."). The implementation splits this string, zips it with the column list (prepended with :time), converts it into a map, and finally casts it into a %TeslaApi.Stream.Data{} struct via Data.into!/1 (lines 23‑28).

def handle_frame({_type, msg}, %State{vehicle_id: vid} = state) do
  case Jason.decode(msg) do
    {:ok, %{"msg_type" => "data:update", "tag" => ^vid, "value" => data}} ->
      parsed = data
               |> String.split(",")
               |> Enum.zip([:time | @columns])
               |> Enum.into(%{})
               |> Data.into!()

      state.receiver.(parsed)
      {:ok, %{state | last_data: parsed, timeouts: 0}}
    # … other clauses omitted …

  end
end

The resulting struct is immediately passed to the receiver callback (state.receiver.(data) at lines 30‑32), allowing the vehicle process to update state, persist records, or broadcast via Phoenix PubSub without blocking the streaming loop.

Resilience and Error Handling

Production streaming requires robust handling of network instability and API errors. The TeslaApi.Stream module implements multiple layers of fault tolerance.

Timeout Management

If no frame arrives within 30 seconds, the handle_info(:timeout, ...) callback at lines 91‑99 closes the socket and triggers a reconnection. The module implements exponential back-off through a timeouts counter in the state, preventing aggressive reconnection attempts that could trigger rate limiting.

Disconnection and Back‑off Strategies

When the WebSocket disconnects, handle_disconnect/2 at lines 102‑131 logs the disconnection reason, calculates an appropriate back-off period, and requests a reconnect. This ensures temporary network blips do not permanently sever the telemetry pipeline while avoiding thundering herd problems during regional API outages.

Interpreting Stream Errors

The module handles semantic error messages from Tesla's streaming infrastructure, including "vehicle_disconnected" and "client_error" (lines 134‑188). These messages are interpreted, logged, and may trigger specific actions such as notifying the receiver to refresh authentication tokens or temporarily suspending connection attempts until the vehicle becomes available again.

Summary

  • WebSocket Persistence: TeslaApi.Stream uses WebSockex to maintain a persistent connection to Tesla's regional streaming endpoints, configured via the Auth struct and TOKEN environment variable.

  • Explicit Subscription: Real-time data reception requires an explicit OAuth-authenticated subscription message specifying desired telemetry columns such as speed, SOC, and location coordinates.

  • Structured Parsing: Incoming JSON frames are decoded, split on comma delimiters, and transformed into %TeslaApi.Stream.Data{} structs before delivery to the receiver callback.

  • Resilient Architecture: The implementation handles 30-second timeouts, exponential back-off reconnection, and semantic error messages like vehicle_disconnected to ensure continuous data availability.

  • Callback Integration: Parsed data is delivered via a configurable receiver function, enabling the vehicle process at lib/teslamate/vehicles/vehicle.ex to update state and broadcast telemetry throughout the TeslaMate system.

Frequently Asked Questions

What protocol does TeslaMate use to receive real-time vehicle data?

TeslaMate uses the WebSocket protocol via the WebSockex Elixir library. The TeslaApi.Stream module establishes a persistent WebSocket connection to Tesla's streaming endpoint (wss://streaming.vn.teslamotors.com/streaming/<TOKEN> or regional equivalents) to receive push-based telemetry updates rather than polling.

How does the streaming module authenticate with Tesla's API?

The module authenticates using OAuth tokens passed in the initial subscription message. When handle_connect/2 triggers the subscription, it includes the token field from the TeslaApi.Auth struct in the JSON payload with msg_type: "data:subscribe_oauth", allowing Tesla's servers to validate the session before transmitting vehicle data.

What happens when the streaming connection times out?

If no data frame is received within 30 seconds, the handle_info(:timeout, ...) function closes the WebSocket connection and initiates a reconnection with exponential back-off. The timeouts counter in the process state increments with each successive timeout, increasing the wait period between reconnection attempts to prevent overwhelming the API.

Which Elixir module handles the conversion of raw streaming data to typed structs?

The TeslaApi.Stream.Data module, referenced in lib/tesla_api/stream/data.ex, defines the %TeslaApi.Stream.Data{} struct. The handle_frame/2 function in lib/tesla_api/stream.ex uses Data.into!/1 to convert the parsed map of telemetry values into this typed struct before passing it to the receiver callback.

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 →