A self-hosted, single-container web dashboard for SunPower PVS5 / PVS6 solar gateways. Talks to the gateway over your LAN, records its time-series data to SQLite, and serves a real-time dashboard with charts, per-panel detail, SunVault battery status, and a live energy-flow diagram. No cloud dependency — your data stays on your network.
![]() |
![]() |
| Live energy-flow diagram (LIVE tab) | Responsive mobile layout |
In 2024, SunPower filed for bankruptcy and was acquired by SunStrong Management (Complete Solaria), which now operates the residential fleet. As part of that transition, the legacy MySunPower web dashboard was retired and the official mobile app became the only sanctioned interface for homeowners.
The PVS gateway under your stairs/garage still produces excellent local telemetry — it just no longer has a vendor-supported way to view it on a computer. This project fills that gap.
The PVS6 historically exposed a local HTTP API on a hidden installer access point. SunPower progressively restricted local API access in firmware updates throughout 2024, and after the SunStrong acquisition there is no official path to downgrade firmware. This monitor only works if your gateway's current firmware still serves the local API.
Known-working range: Firmware versions through approximately 2024.04. Firmware ~2024.06 and later has been reported to lock down or remove the endpoints this project depends on.
Test before deploying — from any machine on the same LAN as the PVS:
# Replace LAST5 with the last 5 chars of your PVS *internal* serial.
curl -u "ssm_owner:LAST5" http://<PVS_IP>/cgi-bin/dl_cgi/devices/list- ✅ Returns a JSON device list after 30–60 seconds → you're good
- ❌ Returns 401/403/404, hangs, or refuses connection → your firmware likely no longer exposes the local API and this project will not work for you
- Real-time hero KPIs — Lifetime solar, plus Solar / Home / Battery / Grid for the selected window (LIVE / 1H / 6H / 24H / 7D / 30D / 90D / 1Y / ALL)
- Energy independence % —
solar_kwh ÷ home_kwhfor the selected window - Live power-flow diagram — Home Assistant-style 4-node SVG with animated dots showing where energy is moving right now
- POWER ↔ ENERGY chart toggle — line chart of kW over time, or stacked bar chart of kWh per period
- SunVault battery section — state of charge, charge/discharge power, backup-time-remaining
- Per-panel inverter status — click a panel for a drill-down chart of its production over day/week/month
- Savings card — dollars saved, lbs CO₂ avoided, trees-equivalent, miles-not-driven, gallons-not-used (configurable rate / emissions factor)
- Nighttime detection — when DC powerline comms drop after dark, panels report
STATE=erroreven though nothing is wrong; the dashboard recognizes this pattern and shows "🌙 Nighttime idle" instead of a red banner - Optional Tailscale sidecar — secure HTTPS access from anywhere on your tailnet without opening a router port
Browser ──► (optional Tailscale HTTPS) ──► Flask proxy (5001) ──► PVS gateway (HTTPS)
│
SQLite history DB
proxy.py— Flask app: handles HTTP Basic auth against the PVS, caches/devicesdata, runs a background poll thread that records to SQLite, exposes the JSON API the dashboard consumes, and serves the dashboard HTMLsolar_dashboard.html— Single-file HTML/CSS/JS dashboard (Chart.js for the time-series chart, vanilla SVG for the flow diagram)docker-compose.yml— The monitor container plus an optional Tailscale sidecar- SQLite —
solar_history.dbin the data volume; one row per refresh inreadings, plus per-panel rows inpanel_readings
A background thread inside proxy.py polls the PVS every REFRESH_SECS (default 300s = 5 min). The PVS scan cycle is ~40–60 min so meter values change at most that often, but the /sys/livedata/ endpoint refreshes every few seconds and is the freshest source for kW readings.
- A SunPower PVS5 or PVS6 with firmware that still exposes the local HTTP API (see compatibility section above)
- Network reachability from the host running this container to the PVS gateway IP
- Docker + Docker Compose
- The PVS internal serial number (see below)
The PVS auth password is not the serial printed on the unit's label — it's the last 5 characters of the internal serial. Find it from any LAN-connected machine:
curl http://<PVS_IP>/cgi-bin/dl_cgi/getvarserver?vars=/sys/info/serialnum
# or:
curl 'http://<PVS_IP>/vars?name=/sys/info/serialnum'The response will look like ZTxxxxxxxxxxxxxxxxx. Take the last 5 characters — that's your PVS_PASS.
cp .env.example .env
# Edit .env and set at minimum:
# PVS_IP = your PVS LAN IP (e.g. 192.168.1.50)
# PVS_PASS = the last 5 chars of the internal serialOption A — convenience wrapper (recommended):
./pvswatch.sh start # builds and starts
./pvswatch.sh logs # follow logs
./pvswatch.sh open # open the dashboard in your browserOption B — Docker Compose directly:
docker compose up -d --buildOption C — pull the prebuilt image (skip the local build):
A multi-arch (linux/amd64, linux/arm64) image is published to GitHub Container Registry on every release tag. Edit docker-compose.yml to replace the build: . line on the pvswatch service with:
image: ghcr.io/timkatz/pvswatch:latestthen:
docker compose pull && docker compose up -dPin to a specific release if you'd rather not auto-roll: ghcr.io/timkatz/pvswatch:2026.04.25.3 (full version), :2026.04.25 (latest release of that day), or :2026 (latest of that year).
The dashboard is now at http://localhost:5001/ (or whatever DASHBOARD_PORT you set).
The first request can take 60–90 seconds — the PVS /devices/list endpoint is famously slow, and the proxy waits for the first poll to complete before serving data.
The included tailscale sidecar in docker-compose.yml will expose the dashboard at https://<TS_HOSTNAME>.<your-tailnet>.ts.net/ from any device on your tailnet. On first run, check the sidecar logs for the auth URL:
docker logs pvswatch-tailscaleIf you don't want Tailscale, simply remove or comment out the tailscale: service in docker-compose.yml.
All settings come from .env. Required fields are marked.
| Variable | Default | Description |
|---|---|---|
PVS_IP |
— | Required. PVS gateway IP on your LAN |
PVS_USER |
ssm_owner |
PVS auth username (always this for SunPower) |
PVS_PASS |
— | Required. Last 5 chars of internal serial |
DASHBOARD_PORT |
5001 |
Host port for the dashboard |
TIMEOUT_SECS |
120 |
PVS request timeout (/devices/list is slow) |
REFRESH_SECS |
300 |
Background poll interval (5 min recommended) |
DATA_DIR |
./data |
Bind-mount path for SQLite + Tailscale state |
LOG_LEVEL |
INFO |
DEBUG / INFO / WARNING / ERROR |
COST_PER_KWH |
0.30 |
Utility rate ($/kWh) for the Savings card |
CO2_LBS_PER_KWH |
0.85 |
Grid emissions factor for the Savings card |
TS_HOSTNAME |
pvswatch |
Tailscale device name (sidecar only) |
TS_AUTHKEY |
— | Optional Tailscale auth key for unattended startup |
REFRESH_SECS lower than 300 (5 min). Each poll involves the slow /devices/list call (~30–45 s); going faster causes overlapping requests and added load on the gateway.
The Flask app exposes these JSON endpoints — useful for Home Assistant, Grafana, scripts, etc.
| Endpoint | Description |
|---|---|
GET / |
Dashboard HTML |
GET /devices |
Latest cached PVS device data; includes a top-level battery object (p_kw, soc, backup_min, lifetime_kwh) and grid object (p_kw, lifetime_kwh). Sign convention: battery.p_kw > 0 = discharging; grid.p_kw > 0 = importing |
GET /history?range=24h |
Bucketed time-series + a period_totals block (kWh per source, savings, CO₂, independence %, etc.). Ranges: 1h, 6h, 24h, 7d, 30d, 90d, 1y, all |
GET /history/panels?range=24h |
Per-panel raw readings for the range (large response) |
GET /panel/<serial>/history?range=24h |
Per-panel drilldown: bucketed kWh, latest snapshot, total — used by the dashboard panel modal |
GET /health |
Cache freshness, history row count, and history_earliest timestamp |
SQLite, two tables, schema created on first start:
readings— one row per poll:production_kw,consumption_kw,net_kw,lifetime_kwh, voltage / frequency / power-factor, panel health counts, and battery columns (battery_kw,battery_soc,backup_min,battery_lifetime_kwh,home_lifetime_kwh,grid_lifetime_kwh)panel_readings— one row per inverter per poll:serial,model,state,watts, DC/AC voltage, current, temperature,lifetime_kwh
Schema migrations are idempotent ALTER TABLE calls in _init_db() — safe to run on existing databases.
Sign conventions (verified empirically):
net_kw > 0= importing from grid;< 0= exporting (matches PVSnet_p)battery_kw > 0= discharging;< 0= charging
pvswatch.sh is a thin wrapper around docker compose:
build Build (or rebuild) the Docker image
start Start the container (builds first if needed)
stop Stop and remove the container
restart Stop and start the container
status Show container status and recent logs
logs Follow container logs
url Print the dashboard URL
open Open the dashboard in your browser
update git pull origin and rebuild the container
help Show this help
The first poll takes 30–90 s because /devices/list is slow. Wait, then refresh.
- Confirm
PVS_PASSis the last 5 of the internal serial, not the label serial - Confirm you can reach the PVS at
PVS_IPfrom the host running the container (ping,curl) - Confirm your firmware version still allows local API access (see compatibility section)
- Verify the PVS is on the same network and the IP hasn't changed (set a DHCP reservation)
- The container runs on Docker's default bridge — confirm the host (and its bridge NAT) can reach the PVS at
PVS_IP - Some PVS units only expose the API on the installer port (an Ethernet jack inside the unit) rather than the customer-facing LAN; in that case you may need a small Linux box (Raspberry Pi works) attached to that port
This is normal. SunPower micro-inverters communicate over the DC powerline; once panels stop producing, comms drop and every panel reports STATE=error. The dashboard detects this pattern and treats it as expected nighttime behavior.
Increase REFRESH_SECS in .env and restart. The default is 5 min, which is comparable to what the SunPower app does. Don't go below 300.
If PVS Watch's standalone dashboard isn't quite what you're after, the PVS community has built other tools that talk to the same local API and may be a better fit:
- smcneece/ha-esunpower — A native Home Assistant integration. If you already run HA, this surfaces production / consumption / battery / per-panel data as HA sensors directly, no proxy in between. PVS Watch and ha-esunpower can run side-by-side against the same PVS without conflict.
- thomastech/SunPower-Web-Monitor — The original lightweight web monitor that this project descended from.
- scott1howard-cba/SunPower-PVS-Exporter — Prometheus exporter for the PVS local API; pair with Grafana for time-series dashboards.
The original PVS dashboard concept was popularized by thomastech/SunPower-Web-Monitor — early versions of this project started from that codebase. PVS Watch has since diverged significantly (rewritten Flask proxy, SQLite history, SunVault battery support, energy-flow diagram, period-totals API, per-panel drilldown, etc.) but the lineage is acknowledged.
Reverse-engineering of PVS endpoints draws on years of community work in the SunPower local-API discussions and various forum threads.
Issues and PRs welcome. If you've successfully tested with a specific firmware version (working or not), please open an issue noting your firmware build so we can keep the compatibility section accurate.
This project is not affiliated with, endorsed by, or sponsored by SunPower Corporation, SunStrong Management, Complete Solaria, or any of their subsidiaries. "SunPower", "PVS", "PVS5", "PVS6", and "SunVault" are trademarks of their respective owners. This is an independent, community-developed monitoring tool. Use at your own risk; the authors accept no liability for any damage to your equipment or warranty implications of accessing the local API.
MIT — see LICENSE.


