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.StreamusesWebSockexto maintain a persistent connection to Tesla's regional streaming endpoints, configured via theAuthstruct andTOKENenvironment 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_disconnectedto ensure continuous data availability. -
Callback Integration: Parsed data is delivered via a configurable
receiverfunction, enabling the vehicle process atlib/teslamate/vehicles/vehicle.exto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →