solarGain: User Guide

How to point it at your Home Assistant, run it, and read every chart it draws. Written to be read cold, with no prior knowledge of the project assumed.

1. What this is

solarGain answers one question with your own sensor data: how much is the sun heating your house, and what is that costing you in air conditioning? It pulls temperature, solar production, wind, and AC power history out of Home Assistant's recorder, then renders it all as a single dashboard.html you can open anywhere. The file carries its data inside it. No server, no chart library, no npm install, no cloud.

It was built for a specific problem: a one-story house with a hot attic, portable AC units fighting that attic every evening, and a homeowner who wanted numbers before buying a radiant barrier, a ridge vent, or blown-in insulation. The dashboard fits a small thermal model to your own history and shows what each of those upgrades would plausibly do, using your roof, not a marketing brochure.

What it does not do: it does not control anything (every request is a read-only GET), it does not update live (each run is a snapshot; run it again for fresh data), and it does not phone home.

The dashboard is one page, top to bottom: a time-range filter, a row of stat tiles, a stack of chart cards (drivers, attic heat load, three what-if scenarios, a wind check, room-by-room panels), and a plain-language "What the data says" list at the end.

2. Words you need first

Recorder
Home Assistant's built-in history database. solarGain reads it over the REST API. You can only chart what the recorder kept, so if your retention (purge_keep_days) is 10 days, "All" means 10 days.
Friendly name
The human label on a Home Assistant entity ("Attic Environment Temperature"), as opposed to its entity ID (sensor.attic_environment_temperature). solarGain finds temperature sensors by friendly name: exact match first, then a unique substring match. AC power sensors are the exception; they are pinned by exact entity ID.
Long-lived access token
The API credential you create once in your Home Assistant profile. It goes in a local .env file and never in git.
Bucket
Every series is downsampled to 10-minute averages before charting. That is the dashboard's native resolution: one point per bucket.
Driver
A series that pushes heat into the house rather than reporting a room: outdoor temperature, attic temperature, and solar production. The solar array doubles as a free irradiance sensor, because panel output tracks the sunshine actually hitting your roof.
Reference zone
A space outside the attic envelope (a garage or porch, say). It shows what the sun does to a room without a hot attic above it, which is the control group for everything else.
Run band
A shaded vertical stripe on a room panel marking when that room's AC was actually running. For units on smart outlets this is derived from power draw, not from what the AC's app claims.
Scenario card
A chart that fits a small physical model to your measured history, then replays the hottest day with one thing changed (a radiant barrier, more venting, more insulation). Modeled lines are always long-dashed with an uncertainty band; measured lines never are.

3. Setup

You need Python 3.9 or newer (for zoneinfo; macOS and any recent Linux qualify) and a Home Assistant instance you can reach on your network. There is nothing to pip install. The whole pipeline is standard library.

  1. Clone the repo and cd into it.
  2. Copy the sensor-map template: cp entities.example.py entities.py (editing it is section 4).
  3. In Home Assistant: Profile → Security → Long-lived access tokens → Create token.
  4. Copy the env template and paste the token in:
    cp .env.example .env
    # then edit .env:
    HASS_TOKEN=eyJhbGciOi...
    HASS_URL=http://homeassistant.local:8123
Set HASS_URL. If you skip it, the fetch script falls back to http://homeassistant.local:8123, which works on many networks but not all; an IP address is more reliable. .env is gitignored; keep the token out of commits.

Want to see the dashboard before creating a token? ./run.sh --mock builds it from synthetic data with the expected physics baked in (a diurnal outdoor sine, a solar bell curve, a lagged attic). It is how the charts were developed before real data existed.

4. Pointing it at your house

entities.py is the single source of truth for what gets fetched. It describes your house, so it lives outside git (like .env): copy the tracked template and edit the copy.

cp entities.example.py entities.py

Three maps matter:

TEMPERATURE_SENSORS

One entry per sensor. Each has a match (the friendly name to resolve), a label (what the chart shows), and a group: driver for outdoor and attic, room for living spaces, reference for no-attic control zones. A room entry may also carry an ac key naming the AC unit that serves it; that is what links run bands to room panels. Two rooms can share one unit (an open floor plan kitchen and living room, say).

Minimum viable set: outdoor, attic, and at least one room. The scenario cards need outdoor, attic, and solar to fit anything.

SOLAR_SENSORS and WIND_SENSORS

Current solar production in kW (any inverter integration that exposes it) and wind speed in mph (a weather station, or drop it if you have none; the wind card just stays empty).

POWER_AC_SENSORS

For AC units on energy-monitoring smart plugs. Point each entry at the plug's cumulative daily energy sensor (the "Energy Today" kind that climbs all day and resets at midnight), by exact entity ID. The fetcher differentiates that curve into watts, then marks the unit "running" wherever sustained draw exceeds threshold_w (150 W by default, comfortably above standby and fans, comfortably below any compressor). Gaps under 30 minutes get bridged so a cycling compressor reads as one run, not twenty.

Fetching prints a resolution report. Any WARN: could not uniquely resolve line means a friendly name matched zero entities or several; fix the match string until the warnings are gone. The dashboard silently skips series it never received, so a clean fetch log is worth thirty seconds of your attention.

Two more edit points live in fetch_history.py: LOCAL_TZ (ships as America/Los_Angeles; set your own or every "Today" filter and daily stat is shifted) and DEFAULT_URL (or just set HASS_URL in .env and ignore it).

5. Running it

./run.sh              # fetch, build, open dashboard.html
./run.sh --days 3     # smaller fetch window
./run.sh --mock       # synthetic data, no token needed
./run.sh: sensor history in, one HTML file out Home Assistant recorder REST API fetch_history.py resolve, bucket history.json 10-min snapshot build_dashboard.py inject into template dashboard.html self-contained Data flows left to right: each script scans for 0.5s, and each file gets a check the moment it is written. The dashboard carries its data inside it, so the finished file works with no server and no network.
Because the data is injected at build time, dashboard.html is a snapshot you can email, airdrop, or archive; it will render identically in ten years.
FlagDefaultWhat it does
--days N10How many days of history to request. Asking for more than your recorder keeps is harmless; you get what exists.
--mockoffSkip Home Assistant entirely and generate synthetic data. Useful for previewing charts and for hacking on the template.

What lands on disk: data/history.json (the snapshot) and dashboard.html (the deliverable). Both are gitignored, because both contain your house's data. Re-running is always safe; each run overwrites both files and touches nothing else.

6. The top strip: filters and tiles

Range filter. Five presets: Today, 24 h (the default), 3 days, 7 days, All. Every chart, tile, and insight on the page recomputes for the selected window. Times are your configured timezone, not UTC, so "Today" starts at your local midnight.

Stat tiles. The headline numbers for the window:

7. The cards, one by one

Every card has a table button in its corner that swaps the chart for the underlying numbers (hourly means). Same data, no chart. Useful for screen readers, spreadsheets, and settling arguments.

Solar drives the attic

Outdoor temperature, attic temperature, and solar production on the same clock. Temperature and kW never share an axis; solar sits in its own aligned panel below, so you can see cause above effect without a dual-axis chart lying to you about scale. Watch a sunny day: solar peaks, then the attic peaks an hour or two later, then radiates into the evening long after the outside air has cooled.

Attic heat load: attic minus outdoor

The same story reduced to one line: how many degrees the roof adds on top of the weather. Zero means the attic is just tracking outside air (typical pre-dawn). The daily hump is pure solar gain. This is the line every upgrade is trying to flatten.

The three what-if cards

Each fits a model to your measured history, then replays the hottest day in the window with one change. Modeled lines are long-dashed with a shaded uncertainty band; the measured attic line stays solid next to them for honest comparison. The fit statistics (R², time constant) print under each chart, so you can judge whether the model earned an opinion.

Does wind flush the attic?

A scatter of model residuals (how much hotter the attic ran than solar predicts) against wind speed, during solar hours. A downhill slope means breeze helps your attic; a flat line means your vents cannot exhale, no matter the wind. Flat is a useful answer: it says buy intake and exhaust area before buying a fan.

Room by room

One small panel per room, all sharing a single y-axis so rooms compare honestly. Reference zones are labeled "(no attic)".

Bedroom 71.4°F Room: thick and solid the only heavy line in the panel Attic: long dashes context, drawn faint Outdoor: fine dots context, drawn faint Shaded band: AC running derived from measured power draw header shows the latest reading
Line identity is carried by dash pattern and weight, not color alone, so the chart still reads for colorblind viewers and in grayscale print. Hovering any chart shows a crosshair with exact values.

What the data says

The last card turns the window's numbers into sentences: how much the roof added on top of the weather, the attic's lag behind the sun, when outdoor air first dropped below your average room temperature each evening (the moment opening windows starts working), and how your AC energy overlapped solar production. It recomputes with the range filter like everything else.

8. Good to know

Is the dashboard live?
No. It shows the snapshot from the last ./run.sh, and the build timestamp is printed in the header. If it looks stale, it is; run it again.
Can I share the dashboard file?
Yes, and it will work offline for whoever you send it to. Just know that it embeds your sensor history: room temperatures, AC usage patterns, and effectively when you are home. Share it like a document about your house, because it is one.
Does it track me?
No. The built file makes zero network requests, and the fetch script talks only to the HASS_URL you configured.
The charts render but a series is missing.
Check the fetch log for WARN lines. A friendly name that matches zero or multiple entities gets skipped, and the dashboard draws what it received without complaint.
My AC tiles show runtime but no kWh.
kWh needs a power-metering plug behind that unit, mapped in POWER_AC_SENSORS. Units known only through a climate entity get runtime from state history instead, which trusts the integration more than a watt meter would.
Why 10-minute buckets?
Thermal mass is slow. Ten minutes is fine-grained enough to see AC cycles and attic lag, and coarse enough that ten days of eight sensors is a small JSON file inside a fast-loading page.
How trustworthy are the what-if cards?
They are fitted first-order models with the fit quality printed under each chart, and they are honest about uncertainty bands. Treat them as "which upgrade deserves a quote," not as engineering. The radiant barrier card exists precisely because the model talked the author out of a purchase.
Is it safe to re-run whenever?
Yes. Reads from Home Assistant, overwrites two gitignored files, nothing else. Cron it if you like.

Companion guide to solarGain. If it behaves differently from this guide, the code is the source of truth.