Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Toyota: trigger soc refresh when charging#27697

Open
thomasbecker wants to merge 10 commits intoevcc-io:masterfrom
thomasbecker:toyota-realtime-soc-refresh
Open

Toyota: trigger soc refresh when charging#27697
thomasbecker wants to merge 10 commits intoevcc-io:masterfrom
thomasbecker:toyota-realtime-soc-refresh

Conversation

Copy link
Contributor

thomasbecker commented Feb 25, 2026

Summary

  • Periodically POST to Toyota's /v1/global/remote/electric/realtime-status endpoint during charging to trigger the car's TCU to push fresh battery data to the cloud
  • Implement api.Resurrector (WakeUp()) for on-demand refresh
  • Reduce poll interval from 15min to 5min so evcc picks up fresh data promptly after a TCU push

Without this change, Toyota's GET /electric/status returns cached data that can go stale for hours during charging. The car does not proactively push SoC updates while charging -- an explicit POST to the realtime-status endpoint is required. This is a known issue in the Toyota ecosystem (pytoyoda/ha_toyota#150, DurgNomis-drol/ha_toyota#140).

How it works

  1. On each status fetch, if chargingStatus == "charging" and >15min since last refresh, fire the POST (side-effect inside the cached getter)
  2. Always return the current GET response (stale data is better than no data)
  3. The next poll (5min) picks up the fresher cloud data after the TCU push propagates

Rate limiting

  • POST refresh: minimum 15 minutes between calls (only while charging)
  • GET poll: every 5 minutes (lightweight, reads cached cloud data)
  • WakeUp() bypasses the 15-min guard (evcc's wakeup logic has its own rate limiting)
  • 12V battery drain is not a concern while charging (DC-DC converter is active)

Test data from a live charging session (bZ4X)

14:29 38% not charging
14:34 37% charging detected - 1st refresh POST
14:40 38% cloud updated (~5min after POST)
14:45 38% cached, waiting for next refresh
14:51 38% 2nd refresh POST (15min after 1st)
14:56 39% cloud updated (~5min after POST)
15:01 40% another update came through

Before this change, SoC was stuck at the same value for the entire charging session (hours).

Test plan

  • CGO_ENABLED=0 go test -tags=release ./vehicle/toyota/... -- 4 new unit tests
  • CGO_ENABLED=0 go vet -tags=release ./vehicle/toyota/... ./vehicle/
  • Live tested on bZ4X during two charging sessions

sourcery-ai bot reviewed Feb 25, 2026
Copy link
Contributor

sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • If Provider methods can be called from multiple goroutines (e.g. polling plus manual wakeups), consider protecting lastRefresh (and any coupled state) with a mutex or atomic to avoid data races around the refresh rate limiting.
  • WakeUp currently triggers refresh but does not reset statusCache, so callers may still see stale SoC until the cache expires; consider calling statusCache.Reset() (or otherwise invalidating the cache) inside WakeUp or explicitly documenting that behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- If `Provider` methods can be called from multiple goroutines (e.g. polling plus manual wakeups), consider protecting `lastRefresh` (and any coupled state) with a mutex or atomic to avoid data races around the refresh rate limiting.
- `WakeUp` currently triggers `refresh` but does not reset `statusCache`, so callers may still see stale SoC until the cache expires; consider calling `statusCache.Reset()` (or otherwise invalidating the cache) inside `WakeUp` or explicitly documenting that behavior.

Sourcery is free for open source - if you like our reviews please consider sharing them
Help me be more useful! Please click or on each comment and I'll use the feedback to improve your reviews.

andig reviewed Feb 25, 2026
thomasbecker force-pushed the toyota-realtime-soc-refresh branch from 70183bd to 430a851 Compare February 25, 2026 15:30
andig added the vehicles Specific vehicle support label Feb 25, 2026
andig marked this pull request as draft February 26, 2026 14:33
thomasbecker force-pushed the toyota-realtime-soc-refresh branch 2 times, most recently from d6f5d80 to 5b04332 Compare February 27, 2026 12:27
Add periodic POST to Toyota's realtime-status endpoint during
charging to trigger the car's TCU to push fresh battery data to
the cloud. Poll every 5min to pick up updated data promptly.
Also implement api.Resurrector (WakeUp) for on-demand refresh.
thomasbecker force-pushed the toyota-realtime-soc-refresh branch from 5b04332 to 0e9a0f5 Compare February 27, 2026 12:28
andig changed the title Toyota: trigger realtime SoC refresh during charging Toyota: trigger soc refresh when charging Feb 27, 2026
andig reviewed Feb 27, 2026
andig reviewed Feb 27, 2026
andig reviewed Feb 27, 2026
andig reviewed Feb 27, 2026
thomasbecker and others added 3 commits February 27, 2026 16:07
thomasbecker marked this pull request as ready for review February 27, 2026 15:16
sourcery-ai bot reviewed Feb 27, 2026
Copy link
Contributor

sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The comment in NewProvider says "Poll at most every 5 min" but the cache duration is set to min(cache, pollInterval), which allows polling more frequently than 5 minutes if cache is smaller; consider using max(cache, pollInterval) (or equivalent) to enforce the intended upper bound.
  • The mutation of impl.lastRefresh inside the status cached closure is not synchronized and may cause a data race if Soc/status is called concurrently; consider protecting lastRefresh with a mutex or using an atomic type.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The comment in `NewProvider` says "Poll at most every 5 min" but the cache duration is set to `min(cache, pollInterval)`, which allows polling more frequently than 5 minutes if `cache` is smaller; consider using `max(cache, pollInterval)` (or equivalent) to enforce the intended upper bound.
- The mutation of `impl.lastRefresh` inside the `status` cached closure is not synchronized and may cause a data race if `Soc`/`status` is called concurrently; consider protecting `lastRefresh` with a mutex or using an atomic type.

## Individual Comments

### Comment 1
<location path="vehicle/toyota/provider.go" line_range="52-54" />


+var _ api.Resurrector = (*Provider)(nil)
+
+func (v *Provider) WakeUp() error {
+ return v.refresh()
+}
+


**suggestion (bug_risk):** Consider updating lastRefresh in WakeUp to avoid an immediate redundant refresh

`WakeUp` only calls `v.refresh()` and leaves `lastRefresh` unchanged, so a subsequent `Status` call will see an old/zero `lastRefresh` and likely trigger another POST immediately. If the goal is to avoid this extra POST after a wake-up, consider updating `lastRefresh` on successful `WakeUp`:

```go
func (v *Provider) WakeUp() error {
if err := v.refresh(); err != nil {
return err
}
v.lastRefresh = time.Now()
return nil
}
```

If the extra POST is intentional, it may be worth checking API rate limits and side effects.

```suggestion
func (v *Provider) WakeUp() error {
if err := v.refresh(); err != nil {
return err
}

v.lastRefresh = time.Now()
return nil
}
```

Sourcery is free for open source - if you like our reviews please consider sharing them
Help me be more useful! Please click or on each comment and I'll use the feedback to improve your reviews.

andig reviewed Feb 27, 2026
andig reviewed Feb 27, 2026
andig reviewed Feb 27, 2026
andig reviewed Feb 27, 2026
andig reviewed Mar 1, 2026
}
}
return res, err
}, min(cache, pollInterval))
Copy link
Member

andig Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will now refresh every 5min instead of 15min. Are we sure that the api survives this?

andig reviewed Mar 1, 2026
impl.status = util.Cached(func() (Status, error) {
res, err := api.Status(vin)
if err == nil && strings.EqualFold(res.Payload.ChargingStatus, "charging") && time.Since(impl.lastRefresh) >= refreshInterval {
impl.lastRefresh = time.Now()
Copy link
Member

andig Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the lastRefresh is already handled inside refresh()- seems the logic is unclear where this really needs to happen?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

andig andig left review comments

sourcery-ai[bot] sourcery-ai[bot] left review comments

Assignees

No one assigned

Labels

vehicles Specific vehicle support

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

2 participants