refactor(vehicle): simplify the state machine: plain atom states, DB records moved into the state data - #5259
refactor(vehicle): simplify the state machine: plain atom states, DB records moved into the state data#5259brianmay wants to merge 11 commits into
Conversation
|
This is only the first phase so far, we haven't seen the simplification to the compound state tuples yet. That will come later. |
|
As before, this was done with AI. It was going to make one big pull request, then we agreed that breaking it up might be better. |
c7b2015 to
5ccde37
Compare
5ccde37 to
ba33348
Compare
|
I decided to do the full refactor in one pull request. While this the PR more complicated, don't think you can really review this probably unless you can see the entire picture. The steps are still there, but as separate commits. Which probably should stay as separate commits. Key questions before merging:
I think by making the state an enum only and putting state data into the data variable we have made it easier to understand. If anyone disagrees, feel free to speak out :-)
It looks OK to me... Although certainly is scope for errors to creep in. Guess next step will be to test this. |
f2d2ed5 to
2f1f3fe
Compare
|
I spotted a place where the logic had been changed and was more complicated then required. Fixed now. |
96e4597 to
5317d27
Compare
|
All tests are expected to pass. Still need to manually review the "Fix compound state tuple handling after dce479c refactoring" commit - haven't done that yet. |
|
Might make sense to merge the last three commits into the "Migrate compound state reads to struct field guards" commit, seeing as all they do is fix problems caused by this commit. Will decide when more awake :-) |
5317d27 to
089956b
Compare
089956b to
d1e6599
Compare
|
I rebased this against the main branch, now that mcu2-upgraded-cars has been merged into main. |
d1e6599 to
b334df0
Compare
✅ Deploy Preview for teslamate ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
b334df0 to
c3e626b
Compare
|
I got Claude Fable 5 to review this (while I still had access to Fable5!). I haven't had a chance to seriously evaluate its feedback yet, so will just post it here in its entirety so I don't accidentally lose it.
|
This PR migrates all pattern-match reads of compound state tuples
({:driving, status, drive}, {:charging, cproc}, {:updating, update})
to use the corresponding Data struct fields instead.
Changes:
- vehicle_in_service driving case: guard on data.current_drive != nil,
read drive from data.current_drive, clear both current_drive and
driving_status on exit from driving state
- remaining_distance interval: use {current_drive, current_charging_process}
tuple instead of compound state pattern match
- asleep handler: guard on driving_status != nil && driving_status != :available
to detect offline sub-states, clear driving_status on :start exit
- fetch can_fall_asleep?: add guards on current_drive/update/charging_process
- fetch reachable: same guards added
- All 6 transitions to :start from driving state now clear driving_status: nil
The compound state tuples remain in next_state positions (handled in prior
PR), so this is purely a read migration. No behaviour changes expected.
No behaviour changes - all guards are guaranteed true at runtime given
correct dual-write population from prior PR.
The dce479c commit converted :driving, :charging, :updating states to simple atoms but left :asleep, :offline, :suspended as compound tuples, causing Summary.format_state and various handlers to fail. Fixes: - Summary.format_state: Handle compound tuples like {:asleep, interval}, {:offline, interval} - driving: Add handler for {:asleep, _} in :driving state (was only matching {:offline, _}) - driving offline: Fix offline handler to not overwrite {:offline, _} without checking timeout - charging: Convert {:charging, cproc} to simple :charging atom - updating: Convert {:updating, _update_id} to simple :updating atom - :summary call: Include driving_status in attrs map for Summary.into - vehicle_in_service handler: Use :driving instead of {:driving, _, _} All 91 vehicle tests and 333 total tests pass.
The interval calculation for API errors was unnecessarily restructured during the read-migration phase. The original case on state was still correct at that point and should have remained unchanged. With simple atoms now in place, the correct form is restored: :driving -> 10 :charging -> 15 :online -> 20 _ -> 30 This is a 4-line case expression instead of the 14-line restructure that was added during read migration.
The guards 'when data.current_drive != nil' etc. on the simple atom patterns ':driving', ':updating', ':charging' are unnecessary. The state atom itself is the source of truth - if we're in ':driving', the dual-write invariant guarantees current_drive is populated. The guards added nothing over the atom pattern match.
The store_position timeout guard still matched the old compound state tuple ({:charging, cproc}); with the plain :charging atom it never matched, so no positions were stored during a charging session and the timer chain stayed dead until the next :online entry.
Match the state atoms directly and make the storing interval configurable via the :store_position_interval start option so the regression test can exercise the timer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
c3e626b to
474786f
Compare
✅ Deploy Preview for teslamate ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
The driving stream handler only matches driving_status: :available after the atom-state refactor. Stream frames received while {:unavailable, n} or {:offline, last} fell through to the generic catch-all, which logged every frame at info level and no longer triggered an immediate fetch — recovery had to wait for the 5s/30s polling cycle.
Restore the pre-refactor behavior: any stream frame while :driving in a non-available sub-state schedules an immediate fetch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Data fields added for the atom-state refactor (current_drive, current_charging_process, current_update, driving_status) were cleared on some :start transitions but not others, so stale records could outlive their state. Route every transition to :start through a reset_activity/1 helper so the invariant "field mirrors state" always holds, and make nil the single "not driving" sentinel for driving_status (the struct default was :available while every exit set nil). Also drop a no-op self-assignment of current_charging_process and lock the lifecycle with assertions in the drive/charge/update flow tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Update: I rebased this onto current main and pushed commits addressing all points from the review above. Rebase: The conflicts were the expected ones — main moved in exactly this area. The semantics of #5535 (stream reconnect on drive resume), #5515 (geofence updates while driving) and #5417 (streaming-aware interval) are preserved in the converted clauses. Main's new Fixes on top:
Full suite is green (461 tests). @brianmay from my side this is now ready — please give it a look, and shout if you disagree with any of the calls above (especially restoring the immediate-fetch behavior rather than dropping it). 🤖 Fixes drafted with Claude Code (Fable 5 extra) — sponsored by Claude for Open Source |
|
Review by opencode/Big Pickle:
|
brianmay
left a comment
There was a problem hiding this comment.
Looks good to me.
Looks like I can't approve my own pull request :-)
I think this needs to be carefully tested, simply because of the invasive changes into the state machine logic.
|
So move it to 4.2 to have a surprise 4.1 release these hours? :P |
|
Thanks for running another review over it! The verification section matches my own checks. On the minor issues: 1 & 3 rest on a wrong premise: there is no second store-position timer. Generic timeouts in 2 is fair: Nothing blocking from my side either. 🤖 Reply drafted with Claude Code (Fable 5 extra) — sponsored by Claude for Open Source |
{:online, nil} was never a state the machine produces; since the atom-state refactor the online state is the plain :online atom.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Agreed on careful testing — the state machine is the heart of the logger. When running this branch for a few days, specifically watching: positions being stored every 5 min during charging (the regression fixed here), recovery behavior on mid-drive connectivity loss, clean field resets after drive/charge/update ( 🤖 Drafted with Claude Code (Fable 5 extra) — sponsored by Claude for Open Source |
Ok, as requested :-) https://github.com/teslamate-org/teslamate/releases/tag/v4.1.0 and https://github.com/teslamate-org/teslamate/releases/tag/v4.1.1 |
haha ty, I was mid trip when I tried to update my docker and I bricked everything xd |
Quoting myself:
Same for you 😀 |
|
Ok, i think it got extremele fricked up. I've read the stuff about backup but at this point I feel like i can't do anything with it. Do you know the simple way to backup the database, as I'd want to delete my entire docker, reinstall and then reuse my backup. I can either use teslamate without database now OR sync database but then teslamate doesn't work. No idea what got so ruined and I'm tired of so much debugging nonsense on termux |
|
@d1stru3t0r This probably deserves a new discussion item. |
Small noobie question, my 3000 work now but reports a very old status, do i have to regenerate tesla tokens? Or what tf is happening xd |
|
Please open a discussion as mentioned by Brian or an Issue, we do not know anything about what you did, what your logs show etc. This PR is not the right place to discuss your issues. Thank you. |
Summary
This PR completes the state machine simplification by replacing compound state tuples like
{:driving, status, %Log.Drive{}},{:charging, %Log.ChargingProcess{}}, and{:updating, %Log.Update{}}with simple atoms (:driving,:charging,:updating). DB records and sub-states are stored inDatastruct fields.Changes (4 commits)
Commit 1: Add preparatory fields to Data struct
Adds
current_drive,current_charging_process,current_update, anddriving_statusfields.Commit 2: Populate Data struct fields in parallel with compound state tuples
Every transition into a compound state now also updates the corresponding struct fields.
Commit 3: Migrate compound state reads to struct field guards
Replaces pattern-match reads with guards on the struct fields.
Commit 4: Replace compound state tuples with simple atoms throughout state machine
This is the main simplification commit. After this:
{:driving, status, drive}→:driving(status indata.driving_status, drive indata.current_drive){:charging, cproc}→:charging(cproc indata.current_charging_process){:updating, update}→:updating(update indata.current_update)Key changes in this commit:
next_statepositions now use simple atomsdata.driving_statusinstead of the state tuple's second element:drivingwith guards ondriving_statussuspend_logging,fetch/can_fall_asleep, andfetch/reachableall use simple atom matchingSummarymodule updated to handle the new state formatRefactoring sequence (complete!)
fake_online_stateintegers withpre_online_checkatomsBefore vs After
Before (hard to read):
After (clean):
Testing
All commits are no-behaviour-change mechanical refactors. Existing tests pass.