Skip to content

Show vehicle status on the dashboard (#168) - #176

Merged
wimaha merged 5 commits into
mainfrom
feature/dashboard-status
Oct 19, 2026
Merged

wimaha merged 5 commits into
mainfrom
feature/dashboard-status

Conversation

@wimaha

@wimaha wimaha commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Closes #168.

Why

The dashboard only offered key management. #168 asks for a quick look at what the proxy knows: whether it talks to the car, battery and charging, climate, and when the last contact was. It also asks that the dashboard not wake the car or add BLE load.

What

Vehicle status at the top of /dashboard. Each vehicle the proxy has talked to gets a card with:

  • A connection and sleep badge (Connected / Awake / Asleep, with time).
  • Battery: state of charge bar with the limit mark, limit, range in km (mi).
  • Charging state, power, current × phases, time to limit, cable.
  • Climate on/off, preconditioning, inside and outside temperature.
  • Lock state and sleep status from the last body_controller_state.
  • Last successful contact and last error, with command and time.

Every block shows when its data is from: the absolute time (with the date if it is not today) plus a relative age that keeps counting. Data older than vehicleDataCacheTime is marked outdated instead of being fetched.

No BLE from the page itself. The page polls GET /api/proxy/1/status every 10 s while the tab is visible. That endpoint only reads what is already in memory:

  • the command loop's view of each connection (new tracker in control/status.go);
  • the cached charge_state / climate_state, however old;
  • the last body_controller_state.

Location and drive data stay out even when cached.

Two buttons per vehicle:

  • Refresh fetches charge_state / climate_state through the normal command queue, bypassing the cache, without waking the car. If the car is asleep, the card says so.
  • Wake & refresh asks for confirmation, then wakes the car first.

Both use POST /api/proxy/1/status/refresh (form fields vin, wakeup), which is covered by the cross-origin protection from #170. They share fetchVehicleData with the vehicle_data handler, so the result is cached for evcc too.

Safe rendering. static/js/status.js builds the DOM with textContent only. A test keeps innerHTML and similar out of it. Styles follow the existing look, including dark mode and phone width.

The full VIN is shown, since it is printed on the car anyway.

Testing

  • go vet ./..., go test -race ./...
  • New end-to-end tests with the simulated car:
    • The status never contacts the car.
    • Cached data shows with updated_at, without drive or location data.
    • The body controller state appears.
    • Refresh without wakeup on a sleeping car → "sleeping", no Wakeup.
    • Refresh with wakeup → one Wakeup, data in the status.
    • Refresh bypasses a fresh cache.
    • An invalid VIN, a cross-site POST (403) and a GET (405) are rejected without contacting the car.
  • An internal test shows that expired cache entries remain visible. A tracker test covers connected/disconnected, last success and last error.
  • Browser check with the real router and two simulated cars:
    • Empty state.
    • Data with timestamps and outdated marking.
    • Refresh on a sleeping car shows an info note and does not wake it.
    • Wake & refresh shows the confirmation dialog, wakes the car and shows fresh data, while the older body controller block stays marked outdated.
    • Dark mode and phone width.
    • The only error in the console is the expected 503 from the sleeping refresh.
  • Not tested yet: a real car with evcc. Use the image wimaha/tesla-ble-http-proxy:pr-176 (built by Publish a Docker test image :pr-<number> for each pull request #175) for that.

🤖 Generated with Claude Code

wimaha and others added 4 commits October 19, 2026 10:59
The loop records what it learns anyway: whether a connection is open and
since when, the last successful command, the last error, and the sleep
state from the sleep check and wake-ups. BleControl.Vehicles() returns a
snapshot; QueueLength() the waiting commands. Nothing here contacts the
vehicle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GET /api/proxy/1/status combines the loop's view of each connection with
the cached charge_state, climate_state and the last body_controller_state,
each with the time it was fetched, plus proxy facts (version, uptime,
active key, queue). It never contacts the vehicle and shows expired cache
entries with their age instead of fetching them. Location and drive data
stay out even when cached.

POST /api/proxy/1/status/refresh (vin, wakeup) fetches charge_state and
climate_state through the command queue, bypassing the cache, and wakes
the vehicle only with wakeup=true. It shares fetchVehicleData with the
vehicle_data handler, so the result is cached for evcc too. As a POST it
is covered by the cross-origin protection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One card per vehicle: connection and sleep badge, battery with a state of
charge bar and limit mark, charging, climate, lock state, last contact and
last error. Every block says when its data is from (absolute time and a
relative age that keeps counting) and is marked outdated once older than
vehicleDataCacheTime. Refresh never wakes the vehicle; Wake & refresh asks
for confirmation first.

static/js/status.js polls the status endpoint every 10 s while the tab is
visible and builds the DOM with textContent only; a test keeps innerHTML
and similar out of it. Styles follow the existing look, including dark
mode and phone width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🟡 Merge after the to-dos

@wimaha: The change only adds new routes and a dashboard view. Existing API routes, config and Docker setup are untouched. The new buttons can use BLE and wake the car, so test them on a real car first.

Impact on existing users: None – the change is additive. The evcc/Home Assistant routes keep the same format. One small internal change: vehicle_data now shares code with the new refresh button.

To-do for the author

  • Test "Refresh" and "Wake & refresh" on a real car, with evcc running at the same time (the PR says this is not tested yet).
  • Say in the docs that the new status and refresh endpoints have no login. Anyone on the network can see the full VIN, battery and cabin temperature. They can also wake the car with wakeup=true. The existing API already allows this, but the dashboard makes it easier to find.

Test before merging: Open the dashboard with evcc polling. Click Refresh on a sleeping car and check it stays asleep. Then use Wake & refresh once and check evcc still gets its data.

Technical details
  • internal/api/handlers/status.go (RefreshStatus): POST /api/proxy/1/status/refresh with wakeup=true wakes the car. It is protected only by the cross-origin check from Harden HTTP endpoints against cross-site requests and XSS #170. Scripts and curl are not blocked, the same as for vehicle_data?wakeup=true. I found no new way to cause a wake-up without the user asking.
  • RefreshStatus ignores the cache (cache time 0). Each click opens a BLE connection through the shared queue. This can delay evcc requests for a moment. Not a bug, but worth knowing.
  • GET /api/proxy/1/status only reads memory. I found no BLE calls and no leaks. The trackers hold one entry per VIN.
  • bodyControllerCache is only filled after a successful call, so it stays small.
  • omitzero needs Go 1.24+. go.mod has 1.26, so this is fine.
  • Not verified: I did not run the code or the browser UI. I only read the diff. I did not read static/js/status.js or the CSS in detail.

…inistic

- Remove the leading blank line in fetchVehicleData (golangci-lint
  whitespace).
- README: the dashboard and the status endpoints have no login, like the
  rest of the API; anyone on the network can read the data and wake the
  vehicle. Recommend a trusted network and a VPN or an authenticating
  reverse proxy for remote access.
- TestVehiclesTracksConnection checked "connected" within a 100 ms
  connection timeout, which a busy machine can miss. It now keeps the
  connection open and closes it with RequestClose.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wimaha

wimaha commented Oct 19, 2026

Copy link
Copy Markdown
Owner Author

Test's in real car successful

@wimaha
wimaha merged commit ad6d3d9 into main Oct 19, 2026
8 checks passed
@wimaha wimaha mentioned this pull request Oct 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Enhanced Web Dashboard with Live Vehicle Status

1 participant