8000
Skip to content

Repository files navigation

CO₂ Investigation

Reproducible pipeline for investigating bedroom CO₂ levels in a single-family home, pulling sensor data from Home Assistant and generating a self-contained HTML report.

See CO2_PROJECT_PLAN.md for the full investigation context and findings.

Quickstart

# 1. Install Python deps (one time)
python3 -m pip install -r requirements.txt --break-system-packages

# 2. Configure HA credentials
cp .env.example .env
# Edit .env — paste your HA long-lived access token

# 3. Pull data from Home Assistant
./pull-data.sh
# → writes data/raw/history-YYYYMMDD-HHMMSS.json

# 4. Analyze and build the report
python3 analyze.py
python3 build_report.py
# → writes report/co2_report.html

# 5. Serve locally (accessible from phone on same WiFi)
./serve.sh

Live Mode

For a dashboard that refreshes itself on a timer:

# One-off: pull rolling 7-day window, re-analyze, rebuild report
./live.sh

To run it automatically every 5 minutes on macOS:

# Install the LaunchAgent (one time)
cp com.samgutentag.co2.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.samgutentag.co2.plist

# Check it ran
tail data/processed/live.log

# To stop:
launchctl unload ~/Library/LaunchAgents/com.samgutentag.co2.plist

The generated HTML includes a meta refresh so an open browser tab will reload every 5 minutes to pick up the new build. The top of the report shows a "Now" strip with each room's current ppm, an air-quality band (low / med / high), and a 30-minute trend arrow.

For a Raspberry Pi or other Linux host, swap the LaunchAgent for a systemd timer pointing at the same live.sh.

Pulling Different Windows

pull-data.sh reads START and END from the environment (ISO 8601):

START=2026-01-15T00:00:00-08:00 \
END=2026-01-21T23:59:59-08:00 \
./pull-data.sh

For a vacation-week pull (only environmental sensors, skips motion/AC/appliances):

MODE=vacation ./pull-data.sh

Project Structure

co2-investigation/
├── README.md
├── CO2_PROJECT_PLAN.md         # original spec + findings
├── findings.md                 # standalone writeup of conclusions
├── pull-data.sh                # HA REST API export
├── .env.example                # HA_TOKEN, HA_URL template
├── .env                        # (gitignored) your real credentials
├── requirements.txt
├── data/
│   ├── raw/                    # JSON exports, timestamped (gitignored)
│   └── processed/              # pickled DataFrames (gitignored)
├── analyze.py                  # parse + derive metrics
├── build_report.py             # render HTML report
├── report/
│   ├── template.html           # Jinja2 template
│   └── co2_report.html         # generated (gitignored)
└── serve.sh                    # local hosting one-liner

Adding New Sensors

When the office CO₂ and main-room CO₂ sensors come online:

  1. Add the new entity IDs to the ENTITIES list in pull-data.sh (and bump the EXPECTED count).
  2. Add aliases for them in the ALIAS dict in analyze.py.
  3. Re-run ./pull-data.sh && python3 analyze.py && python3 build_report.py.

The pipeline is built to make this painless — no other code should need to change.

Vacation-Week Analysis

When the house is empty for a few days, the CO₂ decay curve gives a measured natural air-exchange rate (ACH) for the envelope. That single number decides whether mechanical ventilation (ERV/HRV) is necessary in the HVAC renovation.

# Pull hourly long-term statistics for the empty-house window
START=2026-03-29T00:00:00-07:00 \
END=2026-04-03T23:59:59-07:00 \
OUT=data/raw/vacation-2026-03-29.json \
python3 pull-vacation.py

# Fit exponential decay per room, print ACH + equilibrium summary
python3 analyze_vacation.py

# Rebuild the report — picks up data/processed/vacation.pkl automatically
# and adds a §07 Vacation Week section. (The LaunchAgent does this every 5 min.)
python3 build_report.py

pull-vacation.py uses the WebSocket recorder/statistics_during_period API because the REST /api/history/period endpoint only reads from the short-term recorder (default 10-day retention). Long-term hourly aggregates are kept indefinitely for any sensor with state_class: measurement, which is what the SwitchBot CO₂ / temperature / humidity sensors all use.

HA Recorder Retention (bump this once)

To capture future vacation weeks at full 5-minute resolution instead of falling back to hourly aggregates, increase the recorder retention in HA's configuration.yaml:

recorder:
  purge_keep_days: 90    # keep full state history for 90 days (default is 10)

A 90-day window keeps the recorder DB under ~1 GB for a typical sensor inventory and covers seasonal experiments (summer vs winter behavior). For year-over-year comparisons, set to 365 and consider moving the recorder to PostgreSQL or pairing with an InfluxDB integration.

Restart HA after the change. Existing older data won't come back, but everything captured from now on persists at full resolution.

Notes

  • The HA history API returns the last known state at start_time as the first event for each entity, even if it predates the window. Don't be surprised by opening events with timestamps earlier than START.
  • minimal_response&no_attributes is set on the API call. Cuts response size ~3× without losing anything we need.
  • Plotly is loaded from cdn.jsdelivr.net/npm/plotly.js-dist-min@2.35.2/plotly.min.js in the report. The cdn.plot.ly host is unreliable for specific minor versions.

About

Reproducible CO₂ investigation pipeline: pulls Home Assistant sensor data, computes derived metrics, and generates a self-contained HTML dashboard with live-update mode.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0