# How the TeslaMate Streaming Module Receives Real‑Time Vehicle Data

> Discover how the TeslaMate streaming module gets real-time vehicle data via WebSocket connection. Learn about OAuth authentication and JSON parsing for efficient data flow.

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

---

**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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/lib/tesla_api/stream.ex), the function constructs the initial state and determines the appropriate streaming endpoint.

```elixir
{: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`](https://github.com/teslamate-org/teslamate/blob/main/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.

```elixir
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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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).

```elixir
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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/lib/tesla_api/stream/data.ex), defines the `%TeslaApi.Stream.Data{}` struct. The `handle_frame/2` function in [`lib/tesla_api/stream.ex`](https://github.com/teslamate-org/teslamate/blob/main/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.