-
released this
2026-07-27 20:31:13 +02:00 | 0 commits to main since this releaseFixes
- prices: the chart keyed its X axis on "HH:mm" labels, which repeat across a
today+tomorrow range. Recharts resolves axis tooltips by value, so hovering a
slot after midnight matched today's identically labelled point — the tooltip
showed today's prices and the marker jumped back to today's position. The axis
is now keyed on the ISO instant; ticks still read HH:mm and the tooltip label
carries the date.
Features
- prices: the price slot currently in effect is marked by default with a dot on
the buy and sell lines, plus a caption with its prices; re-evaluated every 30s.
Chores
- frontend: regenerate src/api/schema.d.ts, which had drifted from
openapi/openapi.json sinced07a083and was failing CI's codegen check. - docs: the validation gates now spell out that a route/schema/docstring change
needs bothscripts/export_openapi.pyandnpm run codegencommitted.
Downloads
- prices: the chart keyed its X axis on "HH:mm" labels, which repeat across a
-
released this
2026-07-17 18:53:20 +02:00 | 5 commits to main since this releaseBug fix release.
- fix(tibber): priceInfoRange now uses last:192 instead of first:96, so the
hourly refresh fetches the newest quarter-hourly slots (today + tomorrow)
instead of being stuck on the oldest slots anchored at the subscription start
date. Fixes the empty "No Tibber price points available" state on the energy
prices page for contracts added mid-period. - test(energy-cost): pin local_now in the future-window summarize test so it is
deterministic regardless of the wall-clock date.
Downloads
- fix(tibber): priceInfoRange now uses last:192 instead of first:96, so the
-
released this
2026-06-30 17:20:55 +02:00 | 7 commits to main since this releasev1.4.0 — DDSU666 Modbus meter support
Adds support for the CHINT DDSU666 single-phase smart meter alongside the
existing Eastron SDM120, and generalizes the Modbus read driver so the read
function code is chosen per device profile instead of being hardcoded.
Validated end-to-end against real DDSU666 hardware.Highlights
- 🆕 DDSU666 support — a second supported energy meter model.
- 🔧 Per-profile read function code — the driver now selects FC03 (holding) vs FC04 (input) registers based on the device profile.
- ✅ Hardware-validated — confirmed on a live DDSU666 (big-endian word order, FC03 reads, energy accumulation).
Added
- DDSU666 device profile (
ddsu666.yaml): reads via FC03 holding registers — voltage, current, active power (kW), reactive power (kvar), power factor, frequency, import/export active energy. Big-endian word and byte order. - DDSU666 Modbus protocol reference (
docs/references/DDSU666-Modbus-Protocol.md), extracted from the official CHINT manual: register map, frame format, function codes, CRC/exception handling, an SDM120 comparison table, and field notes from real-hardware validation.
Changed
driver.read_blocksnow selects the read function code per profile (function_code: 4 = FC04 input registers — default, SDM120; 3 = FC03 holding registers — DDSU666), validated before connecting. Previously the read path was hardcoded to FC04 and the profile'sfunction_codefield was declared but never honored.function_codeis now threaded through every read path: themodbus_cli readcommand, the background poller, and the device/testendpoint. The CLIreadoutput additionally prints the active function code.
Compatibility
- SDM120 behavior is unchanged (still FC04).
- No API or OpenAPI schema changes.
- Read-only — no meter configuration registers are ever written.
Verification
- Full test suite green (1037 passed), ruff clean, OpenAPI unchanged; multi-stage Docker image builds, migrates, and boots cleanly.
- Confirmed on a live DDSU666: big-endian word order correct, FC03 reads decode to sensible values, cumulative energy (kWh) accumulates correctly.
- Note: at load currents below the meter's measuring range (Imin ≈ 0.25 A), the instantaneous active power and power factor read 0 while energy still accumulates — expected meter behavior at very low current, not a decoding issue.
Docs
- Roadmap updated to queue Authentication next-steps (sliding session renewal + long-lived token targets).
Downloads
-
released this
2026-06-27 11:18:24 +02:00 | 9 commits to main since this releasev1.3.0
A large release covering everything since v1.2.0 — it takes the backend from
single-user auth hardening through a full IoT + energy-metering stack with deep
Home Assistant integration.Highlights
- Home Assistant integration via MQTT Discovery — Modbus devices and energy-cost sensors auto-appear in HA.
- Energy metering & cost engine — DSMR smart-meter ingest, manual/dynamic pricing (incl. Tibber), per-15-minute cost billing with standing charges and tax credits.
- Meter epochs (meter-swap archival) — swapping a physical meter spins up a brand-new HA sensor and never mixes readings across meters.
- Login hardening — TOTP two-factor auth plus exponential login back-off.
Authentication & security (M4)
- Two-factor authentication (TOTP): QR setup, recovery codes, and a two-step login flow.
- Exponential back-off throttling on failed logins.
admin_cliescape hatches: reset password, unlock account, disable/reissue TOTP, list admins.
IoT devices & Home Assistant (M5)
- Generic Modbus TCP polling: device CRUD, JSON readings, YAML device profiles (e.g. SDM120), opt-in scheduled polling, cascade delete.
- MQTT client with lifespan connect / auto-reconnect and a config "test" button.
- Home Assistant MQTT Discovery: an entity-exposure framework with per-entity enable/disable toggles; publishes discovery configs, state, and availability under the
home_automationtopic prefix. - UI: sidebar navigation, Energy/device management views, latest-reading cards + trend charts, accordion config, and a Home Assistant expose panel.
- DB-level foreign-key enforcement (SQLite
foreign_keyspragma).
Energy pricing, DSMR & cost engine (M6)
- Pluggable pricing layer with manual and Tibber strategies plus profile validation.
- Energy contracts with versioned tariffs.
- DSMR P1 telegram ingest over MQTT.
- Billing engine: per-15-minute period cost computation, dual-tariff buy/sell prices, standing fees folded into import cost and the energy-tax credit into export revenue; summary + retroactive recompute.
- Tibber GraphQL client with hourly price refresh (today + tomorrow).
- Energy API (prices / costs / summary / latest DSMR / recompute / Tibber test) plus frontend contract management, price/cost views, and a DSMR data panel.
Meter epochs / meter-swap archival (M7)
- New
metertable modelling each physical meter's install window[started_at, ended_at); cost periods now carry ameter_id. - Meter-aware billing: never computes a register delta across two meters; periods straddling a swap boundary are marked degraded; added a delta sanity guard.
- Cumulative cost sensors are anchored to the active meter's start, so a swap cleanly resets the running totals for the new meter.
- Meter CRUD API with retroactive recompute, plus a meter-management UI (timeline, declare swap, edit).
- Each meter has a stable UUID used as its Home Assistant identity, and the energy-cost HA device is named after the meter label — so swapping a meter produces a brand-new HA device/sensor and never contaminates old data. Discovery is re-published automatically on meter changes.
Energy time & Home Assistant midnight handling (follow-ups)
- All energy time math localized to the server timezone; the UI shows energy/DSMR/meter times in local time (24h).
- Fixed fees and the energy-tax credit accrue per local calendar day; any day with recording counts its full-day credit (symmetric start/end-day handling).
- Daily-resetting "today" cost/revenue sensors in addition to lifetime totals.
- Midnight handling tuned for Home Assistant statistics: the daily "today" reset is shifted to just past local midnight (robust to a few seconds of clock skew between hosts), and each day's fixed fee + tax credit are settled at ~01:05 local — off the midnight hour-boundary — so HA attributes them to the correct day and the energy dashboard's daily costs stay accurate.
- Billing robustness: ignore stale / out-of-range DSMR readings; energy-cost sensors marked always-available.
Upgrade notes
- Database migrations run automatically. The new
metertable is created and backfilled — existing electricity history is attributed to an initial meter with a generated UUID. No data is deleted. - After upgrading, declare your meter (set its label) so the Home Assistant energy-cost device is named correctly. The old generic "energy-cost" HA device is superseded by a per-meter device; you may delete the stale one in HA.
- (Optional) To include standing charges and the tax credit in the Home Assistant Energy dashboard, set the grid cost source to "Use an entity tracking the total costs" pointing at
import_cost_total(andexport_revenue_totalfor return-to-grid).
Downloads
-
v1.2.0
Stablereleased this
2026-06-13 17:21:26 +02:00 | 88 commits to main since this releaseReplaces the server-side Jinja pages with a same-origin React single-page app,
backed by a new JSON API. Same container, same origin (no CORS); auth still
rides the existing Argon2 + server-side session cookie.Added
- React SPA (Vite + TypeScript + Mantine + TanStack Query + React Router),
served same-origin from FastAPI as static build output. - JSON API for the SPA:
GET/PUT /api/config— config read/writePOST /api/config/smtp/test— SMTP test action- read-only data query endpoints (location / poo)
- single-record CRUD (
PATCH/DELETE) - session/auth endpoints (
/api/session, login, logout, forced password change)
- Data visualization: Leaflet heatmap home page (viewport-normalized
intensity, dark basemap) + records-management UI (paginated lists, edit/delete
modals) — takes over what Grafana used to do. - Typed OpenAPI client generated from
scripts/export_openapi.pyoutput. - Multi-stage Dockerfile (node build → python runtime) + frontend CI workflow.
- Explicit
docker-compose.dev.ymldev stack (port 8001,-devcontainer
names, prod-copy./dataDB). - Dark-mode toggle, Feather-icon nav, Grafana-style quick time-range presets.
Changed
- Browser endpoints reuse the existing session-cookie auth with presence-only
CSRF (X-CSRF-Token). - README + architecture-overview updated for the SPA reality; retire the
"no frontend/backend split" and "Grafana as visualization" constraints.
Removed
- Jinja templates (
base/config/home/login.html) and the oldauth.py/
pages.pypage routers. docker-compose.override.yml(renamed to the explicitdocker-compose.dev.yml).
Fixed
- Logout no longer floods
GET /api/sessionwith 401s — the session probe and
the login endpoint own their 401s, breaking the redirect→refetch loop. - Heat-layer white-screen crash on first load after login.
Notes
- The device ingestion endpoint stays on bare API until M3 (token auth).
- TOTP 2FA for the now public-facing dashboard is recorded under
Future Ideas — not scheduled.
Downloads
- React SPA (Vite + TypeScript + Mantine + TanStack Query + React Router),
-
released this
2026-06-12 20:53:09 +02:00 | 120 commits to main since this releaseConsolidates the project's three separate SQLite databases (app / location / poo) into a single app.db: one SQLAlchemy Base, one engine, one Alembic chain. Cleans up the data-access code that had accumulated across the codebase and removes Grafana. No historical data is lost — the location/poo rows are moved by a one-time, idempotent, reconciled migration script.
▎ First step of the post-v1.0.3 roadmap (M1 → M2 frontend → M3 mobile): lay a clean foundation first. This is a structural change with a breaking configuration change and a mandatory one-time data migration after deploy — read the Upgrade Guide before rolling out.
✨ Highlights
- Single database. location and poo_records are merged into app.db. The data layer is unified onto one Base + a cached engine bound to app_database_url with SQLite WAL, behind a single get_db. app/auth_db.py, app/poo_db.py, app/models/base.py removed.
- One Alembic chain. A new alembic_app revision creates location / poo_records (column types match the legacy schema exactly — REAL/TEXT, nullability, PK order). The alembic_location / alembic_poo chains and adoption scripts are deleted.
- One-time migration script scripts/migrate_legacy_data.py: ATTACH + INSERT OR IGNORE (idempotent), explicit columns, full-row NULL-safe reconciliation that aborts non-zero on any missing/differing row. Never deletes or overwrites files; not in the Alembic chain or startup path.
- Config cleanup (⚠️ breaking). Settings drops location_database_url / poo_database_url and their *_sqlite_path; only APP_DATABASE_URL remains. Config page and .env.example updated.
- Grafana removed. Service, the homeautomation_grafana_storage volume declaration, and the grafana/ directory deleted. Visualization moves to the M2 React frontend (intentional interim gap).
- Docs & OpenAPI. README / architecture-overview / roadmap updated to single-DB; openapi/ re-exported (also catching up /config/smtp/test, /public-ip/check, PublicIPCheckResponse).
⚠️ Upgrade guide (required after deploy, in order)
The new code only opens app.db at runtime; deploying does not move historical data by itself — it only creates the empty tables.
- Back up production app.db, locationRecorder.db, pooRecorder.db.
- Edit .env: remove LOCATION_DATABASE_URL / POO_DATABASE_URL (now ignored); keep APP_DATABASE_URL.
- Deploy the new image → the migration job (python -m scripts.run_migrations) upgrades app.db and creates the empty location / poo_records.
- Run the one-time data migration (otherwise historical location/poo data is absent):
python -m scripts.migrate_legacy_data
--app-db sqlite:///./data/app.db
--location-db sqlite:///./data/locationRecorder.db
--poo-db sqlite:///./data/pooRecorder.db
exit code must be 0; add --dry-run first to preview
- Prefer a brief quiet/maintenance window (SQLite single-writer; avoids a new write colliding with a historical PK and tripping reconciliation).
- Verify: app.db location / poo_records row counts equal the legacy DB counts.
- Retire the old DBs (manual, after verification, keep an archive): archive the legacy files (don't delete blindly); decommission the Grafana container and the homeautomation_grafana_storage volume.
▎ No automated path ever deletes the old files — retiring them is always a manual, reversible step.
🔒 Data safety
The migration reads the legacy DBs read-only (no os.remove/unlink/shutil/truncate/DROP/DELETE); idempotency comes from INSERT OR IGNORE. Verified end-to-end against copies of the real production databases: dry-run wrote nothing, the real run migrated every row with matching per-row checksums, a second run was idempotent, a constraint-violating row aborted reconciliation non-zero, and the legacy files were md5-identical throughout.
🧹 Internal cleanup
Startup converged (lifespan validates only the app DB, still fail-closed; run_migrations returns {"app": ...}); routes/deps/models on the single data layer; test suite converged (legacy adoption tests removed, migration-script tests added); pytest green (97 passed); openapi/ re-exported.
Changelog
PR #7 (feature/m1-db-consolidation → main, merge
1e0b235): T01 table revision · T02 migration script · T03 unified data layer (WAL) · T04 startup convergence · T05 config cleanup · T06 remove Grafana · T07 docs + OpenAPI · review rework (full-row reconciliation). Full diff: v1.0.3...v1.1.0Downloads
-
v1.0.3
Stablereleased this
2026-04-29 13:18:03 +02:00 | 132 commits to main since this releaseThis release adds Grafana provisioning, public IPv4 monitoring, SMTP support, and automatic email notifications for public IP changes.
Added
• Grafana provisioning
• Public IPv4 detection and persistence
• Public IP state/history storage in the app DB
• Manual endpoint to trigger a public IP check
• Scheduled public IP checks
• SMTP configuration and test email support
• Automatic email notifications when the public IP changesChanged
• Refined sender display for notification emails
Notes
• Public IP change notification emails are sent in English
• Notifications are only sent when an actual IP change is detected
• SMTP settings can be configured and persisted from the config pageDownloads
-
v1.0.2
Stablereleased this
2026-04-22 13:35:57 +02:00 | 138 commits to main since this releaseDownloads
-
released this
2026-04-22 13:28:51 +02:00 | 140 commits to main since this releaseDownloads
-
1.0.0
Stablereleased this
2026-04-20 23:34:44 +02:00 | 142 commits to main since this releaseDownloads