TeslaMate core roadmap: modular data sources, then Rust #5558
Replies: 7 comments 20 replies
|
@jlestel, @Bre77 — you operate the largest proxy-based setup we know of, so two of the open questions above are ones you have real-world data on:
To be clear on scope: the proxy path keeps working as described above, and nothing here asks you to change anything. We're after your operational experience for the event contract design. |
|
Just my two cents guys, chose Go instead of Rust for this solution. As much as prefer rust, Go is better for this case IMHO. Again, I am not telling what, just adding some advice. |
|
I think we need to rethink our testing strategy. Currently testing occurs by pushing inputs into a Vehicle and looking at the outputs. And maybe the key problem here is that Vehicle is too complicated. The tests are fragile, changes to Vehicle is likely to break the tests, including tests that are not related to the change. And if the tests break it is difficult to find out what went wrong. Even AI models can struggle and go around in circles (seen this happen with my refactoring PR). I consider these tests integration tests just because of the large scope of code being tested. We probably do need to have some integration tests. But we shouldn't rely entirely on them. We should ideally have more unit tests then integration tests. Possible things to think about:
Not sure how practical this will be. I often start new projects with good intentions and then get stuck along the way, e.g. thin API wrappers are not good candidates for unit testing IMHO. If we do encounter problems, we should probably discuss them. |
|
I have had several attempts at writing Tesla owners API clients in rust. old now, but still might have some good ideas: https://github.com/brianmay/fake_luxary_api/blob/main/fla_client/src/lib.rs still current: https://github.com/brianmay/robotica-rust/blob/main/robotica-tokio/src/services/tesla/api.rs I haven't looked at these in a while. If I did I imagine I might want to have words with my past self as to why I did various things :-) The new version has support for logging metrics using opentelemetry (see init code https://github.com/brianmay/robotica-rust/blob/main/robotica-backend/src/logging.rs). Good metrics/remote logging is probably a good idea. Although I do find keeping it up-to-date and working can be painful (all relevant crates have to be updated at the same time; and recently AI incorrectly told me the TLS configuration wasn't required which broke all logging). And I never remember to look at the logged data. |
|
Name? Teslamate V5? Perhaps the first and one of the most important decisions we will need to make... Need to be something distinct so we don't get confused when referencing old and new code. |
|
Hi @JakobLichterfeld, @brianmay — greetings from Augsburg, and from near Stuttgart as of tomorrow. One note upfront: this is written with help from Claude 5, since my English is nowhere near as fluent as my Swabian. I run a self-hosted TeslaMate whose data comes from Fleet Telemetry streaming: my own receiver, no proxy in between, in production since mid-June. Since you asked for actual field delivery, here is one day of measurements: 24 drives, one Supercharger session, ~15.5k records. 57 of 59 configured fields are delivered. Never seen: Configured intervals bound the rate, they are not a delivery promise:
Fields that rarely change simply arrive rarely. Conversely almost anything can burst down to ~1 s. A source can promise "not faster than N", not "a sample every N". Fidelity: the gap to old streaming is categorical, not gradual. Charging is absent from the old format entirely — Backfill: reconciliation has to be idempotent and keyed on something stable, not on "what happened since last time". After an offline gap the car replays buffered data, and a naive scan creates duplicate sessions. Marking reconstructed ranges explicitly was the only thing that made repeated scans safe. Policy placement: treating the feed as active based on freshness rather than on the connection being up removed a whole class of bugs — a connected stream delivering nothing is indistinguishable from a live one at socket level. Happy to post the full 57-field table with observed intervals if that helps the contract. |
|
Thanks for putting this together so clearly — the combination of modular sources + explicit event contract, sequenced properly before the Rust port, feels like the right long-term direction. The emphasis on characterization first and keeping the architecture change decoupled from the language change is especially appreciated; that kind of discipline is rare and valuable on a project that holds people’s historical driving/charging data. I’d like to offer concrete help on the database side and the official Grafana dashboards once the shape of the schema changes becomes clearer. Happy to review migration plans, help keep the shipped dashboards in sync, or assist with any performance-related adjustments that surface. A few design questions that would help me (and probably others who maintain custom panels or care about long-term reporting) understand the intended scope:
No pressure on answers; just trying to understand where the boundary sits so any help I offer stays aligned with the actual goals. Happy to stay quiet on the implementation side and only chime in on design / dashboard / migration topics as they surface. |
Uh oh!
There was an error while loading. Please reload this page.
What this is
This documents the direction @brianmay and I are taking the TeslaMate core, and collects input on the open design questions. It is documentation and brainstorming — not a vote, and not a call for contributions.
Two things came up in a closed PR (#5557) and in the older go-forward thread (#3416), and both belong somewhere visible:
We're implementing both. What is genuinely open — and what this thread is for — clusters in three areas: the event contract, the fidelity gap between Fleet Telemetry and old streaming, and what happens to the web UI. The open questions are marked as such below; the rest is decided and documented here so it can be reasoned about.
Ownership — please read before replying
Review capacity is the bottleneck in this project. A half-agreed contract implemented twice consumes it faster than anything else. That is why this section exists, and why it is strict.
This work touches the heart of TeslaMate: the state machine that decides what a drive is, what a charge is, and what ends up in your database. It cannot be assembled from independent pull requests.
If you want to help concretely:
Data sources: modular sources, one core pipeline
To be precise about where we stand today: TeslaMate already works against the Fleet API and Fleet Telemetry — but only by way of a detour. It speaks exactly one wire protocol, the owner-API shape, and alternative backends are reached by pointing
TESLA_API_HOST,TOKENandTESLA_WSS_HOSTat a third party (MyTeslaMate, Teslemetry) or a self-hosted proxy that translates them back into that shape.That detour works, and it will keep working. But it has a price:
vehicle_dataeven when the source underneath is push-only.The target shape, which @brianmay sketched in #5557: a core process receives a stream of events, splits them into drives, charges and so on, and persists them. Which source runs is a matter of configuration:
To set expectations: sources are an internal structuring device, not a plugin API. Which sources exist, and what the contract between them and the core looks like, is a maintainer decision and will stay one. We're documenting the contract so the design can be reasoned about, not so it can be extended from outside.
Today acquisition and interpretation are entangled in
Vehicle, which is why "streaming only" and "additional API" are hard rather than merely new. The polling loop is not a property of the owner API — it's a property of our architecture.The deliverable of this step is the event contract: what an event is, which guarantees a source must provide, and what the core may assume. The refactor is just how we get there.
Open — input wanted
Language: Rust for the core
The core moves to Rust. This decision is made; this thread is not the place to relitigate the language choice, and replies arguing Elixir vs. Rust in the abstract will not move it. What is genuinely open is listed below — most importantly the web UI question.
The reasons, in the order that matters to us:
Elixir/OTP has served this project well, and its supervision model is a good fit for "many vehicles, many long-lived processes". This is not a complaint about Elixir. It's a decision about where the core is easiest to keep correct over the next years.
What it costs
The Phoenix LiveView web UI is the largest single item. Settings, geofences, import and sign-in have no drop-in Rust equivalent. The realistic options are: keep the UI as a separate Elixir service talking to the same database, rewrite it against an HTTP API exposed by the Rust core, or replace it with something else entirely. This is open, and it's the question we'd most like input on.
Beyond that: migration ownership once Ecto is no longer the writer, and CI build times.
What stays stable for you
For everyone running TeslaMate, these hold across all of it:
What is not a stable interface: the database schema
The PostgreSQL schema is internal, and it is worth being blunt about this. It has never been a documented interface, and this work will not make it one. A restructuring of this size is exactly when the freedom to reshape storage is needed, and we intend to use it: expect substantial schema changes, not cosmetic ones.
Concretely: if you read the tables directly — custom Grafana panels, scripts, third-party or commercial tools built on today's schema — expect your integration to break during this work. Not might: will. There is no compatibility promise, no deprecation window, no advance notice beyond the migrations themselves, and we will not slow down, stage, or route the redesign around anything that depends on the current table shapes. Issues asking us to restore a schema detail or to coordinate changes with external tools will be closed. Your data is safe — migrations carry it forward — but every assumption about how it is stored is up for renegotiation, and nobody outside this repository is part of that negotiation. The supported surfaces are the dashboards we ship and MQTT. Everything else is and always was at your own risk.
If something we haven't listed here would break your setup, say so now, not after the migration.
Sequencing
The safety net comes before the surgery, and the architecture change stays decoupled from the language change — a rewrite that also changes the architecture has no reference implementation to test against.
Phase 1 — a characterization suite at the outer boundary. Before any code moves, the current behaviour gets pinned down: a recorded sequence of Tesla API payloads goes in, and the resulting persisted data and MQTT messages are asserted, with the vehicle state machine taken to ~100% coverage — the point of the number is that the suite must reliably catch behavioural deviations, not that a metric looks good. Crucially, the cases are stored as data rather than as code, and nothing in them refers to an internal function.
That boundary already exists —
TeslaMate.VehicleCase.start_vehicle/3drives exactly such a sequence through the API mock today. What's missing is coverage, the move from Elixir literals to data fixtures, and assertions against real persisted rows and messages instead of mock call assertions.This suite is what makes the following phases safe, and it stays valid across both of them: whatever happens inside, identical API payloads must keep producing identical results.
One caveat we'll handle explicitly: pinning current behaviour also pins current bugs. Cases we believe are wrong get marked as such and fixed in their own commits — never silently, in the middle of a refactor.
Phase 2 — source abstraction in Elixir. Introduce the source/event boundary and make the event contract explicit, incrementally, with the Phase 1 suite green at every step. Delivers native streaming-only support without a rewrite. Event-level fixtures — the ones that can only exist once the contract does — are written here, at the new boundary, alongside the outer suite.
Phase 3 — Rust. Port module by module behind the same contract (strangler pattern). Both fixture sets are the gate: the same files drive both cores and the outputs are diffed. Anything the Rust core cannot reproduce does not replace the Elixir path. How the two runtimes coexist during the port — NIFs, a separate process, or running both cores side by side and diffing offline — is deliberately left open here and will be decided in the Phase 3 issues; the single-binary deployment benefit arrives at the end of this phase, not during it.
Users will not be moved onto the Rust core silently. How the switch is offered — an opt-in image, a beta tag, or both cores running against the same data and diffed before anything ships — will be settled there too, and announced before it happens.
Each phase stands on its own. Phase 1 is worth having whatever happens afterwards, Phase 2 removes the bottleneck regardless of the language, and Phase 3 is gated by both.
Where your input goes, and how this gets tracked
The questions marked Open above — the event contract, the fidelity gap between Fleet Telemetry and old streaming, and what happens to the web UI — are the ones where a good answer changes what we build. Corrections to the "what stays stable" list are equally welcome.
There are no dates attached to any of this, and regular development continues throughout: bug fixes, dashboards and features ship as usual, and none of this work blocks a release. Once the design questions have settled, we'll cut the work into issues that link back to this thread — those issues carry scope and acceptance criteria, not deadlines. Hard commitments made in this document — dashboards shipping together with schema changes, MQTT compatibility asserted by the suite — become acceptance criteria in those issues, so they can't silently slip. The issues are the tracking surface; the reasoning lives here. This thread stays open for the design discussion and will not be used as a task list.
What this thread is not: a place to claim implementation work. See "Ownership" above.
cc for visibility: @adriankumpf, @cwanja, @DrMichael, @Dulanic, @swiffer, @tobiasehlert — no action needed, this is a heads-up so you read it here first.
🤖 Drafted with Claude Code (Opus 5 high and Fable 5 high) — sponsored by Claude for Open Source
All reactions