tliu93
7e61b0a938
HA Discovery fix: energy-cost sensors are always-available
...
The energy-cost device has only sensors (no online/offline heartbeat), but the
discovery config still declared an availability topic that nothing ever publishes
'online' to — so Home Assistant marked every energy-cost entity 'unavailable'
even though the state topic was being published (visible in MQTT Explorer).
- DeviceInfo gains provides_availability (default True, keeps Modbus behavior).
- _energy_cost_provider sets provides_availability=False.
- build_discovery_payload only emits availability/availability_mode for devices
that publish a heartbeat; HA treats the rest as always-available.
- Test asserts energy-cost configs omit availability.
2026-06-24 11:54:49 +02:00
tliu93
e8521351d7
frontend: add DSMR data panel to Energy page
...
- New 'DSMR' tab showing the latest parsed telegram (GET /api/energy/dsmr/latest)
as a key/value table — the source-of-truth view so you can see ingest is
landing data without reading the DB.
- Auto-refresh toggle (~10s); loading/error/empty states (empty explains the
likely cause); null phase values shown as a dash.
- useDsmrLatest extended with optional auto-refresh. Tests added.
2026-06-24 11:02:42 +02:00
tliu93
8d4f496ff4
DSMR hardening: restart-free config + decouple from telegram id
...
Config hot-reload (no restart):
- MqttManager.unsubscribe(); apply_dsmr_subscription(settings) re-applies the
DSMR subscription (subscribe/unsubscribe/topic-change, fresh snapshot so the
sample interval also takes effect).
- Called from lifespan AND PUT /api/config, so toggling DSMR ingest in the UI
takes effect immediately without restarting the app.
Decouple ingest from the DSMR telegram id (overflows / resets to zero):
- Migration 20260624_12: drop UNIQUE(source_id), make recorded_at UNIQUE.
- Idempotency now keys on recorded_at (telegram timestamp); the table's own
autoincrement PK is the stable identity. source_id kept only as a reference.
- Regression test: colliding telegram ids no longer drop new data.
2026-06-24 11:02:32 +02:00
tliu93
78f3cc4776
M6-T11: document M6 (energy pricing + DSMR + Tibber) across README/roadmap/arch
...
- README: M6 section + feature list + new tables/config items.
- roadmap: M6 row (graduated) + detail section; M6 in milestone index.
- architecture-overview: M6 models/services/integrations/routes/jobs/config.
- design/README: index m6, drop stale '三个里程碑' wording.
- OpenAPI already in sync (no diff).
2026-06-23 23:52:07 +02:00
tliu93
96e88861d4
M6-T10: frontend contract management + price/cost views + Tibber test
...
- EnergyPage: Mantine Tabs (Devices kept intact + Contracts/Prices/Costs).
- ContractManager/ContractForm: list/activate/add-version + version history;
form fields rendered dynamically from /api/energy/profiles structure.
- TibberPrices + CostView: Recharts price curve, cost trend/detail/summary,
recompute; window-bounded, currency/units from API, empty/error/loading states.
- ConfigPage: tri-state Tibber test button (token never shown).
- hooks.ts: typed energy hooks; schema.d.ts regenerated via codegen.
2026-06-23 23:32:39 +02:00
tliu93
57f2459f60
M6-T09: add Energy data API (prices/costs/summary/dsmr-latest/recompute/tibber-test)
...
- routes/api/energy.py + schemas/energy.py: GET prices (tibber points or manual
tariff, buy/sell consistent with the pricing strategies), GET costs (time
window + limit, ascending), GET costs/summary (summarize passthrough), GET
dsmr/latest, POST costs/recompute (idempotent, <=366d guard, CSRF), POST
tibber/test (three-state, token never echoed, CSRF).
- main.py registers router; OpenAPI re-exported; tests for all endpoints.
2026-06-23 23:05:45 +02:00
tliu93
c61fc2d4ba
M6-T08: add energy_cost expose provider + post-billing state publish
...
- expose.py: _energy_cost_provider yields buy/sell_price_now + import_cost_total
/export_revenue_total (total_increasing, monetary), value_getters read from
energy_cost_period; 2-element identifiers so the existing HA Discovery builder
(which indexes identifiers[1]) works. Other providers/mechanism untouched.
- main.py: energy-cost job calls publish_states after computing periods (no-op
when MQTT/Discovery off). Tests added.
Note: energy-cost sensors lack an availability heartbeat (ha_discovery mechanism
limitation, out of scope here).
2026-06-23 22:44:41 +02:00
tliu93
d35ac7d752
M6-T07: add metering billing engine + period job + summary + recompute
...
- app/services/energy_cost.py: register_at (register values at a UTC boundary),
compute_period (register deltas x active-version strategy, snapshot price +
contract_version_id, immutable unless overwrite), compute_closed_periods
(bounded lookback), recompute_range (explicit overwrite), summarize
(sum net + standing fees month/30 - heffingskorting year/365).
- Missing tibber price -> skip; missing reading -> degraded (zeroed amounts,
null version), consistently on both create and recompute paths.
- main.py: 1-minute energy-cost job (existing jobs/MQTT untouched). Tests added.
2026-06-23 22:26:56 +02:00
tliu93
ef48032adb
M6-T06: extend MqttManager with subscribe + DSMR ingest
...
- mqtt.py: subscription registry, on_message dispatch (swallows handler
errors), re-subscribe on (re)connect; publish/connect/disconnect/reconnect
unchanged.
- app/services/dsmr_ingest.py: handle_message stores the full telegram frame
(no allowlist; gas/phases/null preserved), 10s downsample by timestamp.second,
source_id idempotency (select + IntegrityError rollback). Net-thread safe.
- main.py registers the DSMR subscription only when dsmr_ingest_enabled.
2026-06-23 21:52:57 +02:00
tliu93
7e266a21eb
M6-T05: add Tibber GraphQL client + price refresh service + schedule
...
- app/integrations/tibber/client.py: fetch_price_range/fetch_current_price
(priceInfoRange QUARTER_HOURLY, startsAt->UTC, no fixed node count assumption),
TibberError/TibberAuthError; token never logged or put in exception text.
- app/services/tibber_prices.py: refresh_prices upserts by starts_at (idempotent),
no-op unless an active kind=tibber contract + token exist.
- main.py: hourly tibber-refresh job (existing jobs/MQTT untouched). Tests added.
2026-06-23 21:35:29 +02:00
tliu93
4284bd40a7
M6-T04: add EnergyContract CRUD + versions + profile validation API
...
- app/services/contracts.py: create_contract, add_version (append-only,
auto-closes prior version), activate_contract (mutex), and
active_contract_version_at (half-open [from, to)) for the billing engine.
- app/schemas/energy_contract.py + routes/api/energy_contracts.py: 6 endpoints
(GET/POST contracts, GET/PATCH contracts/{id}, POST .../versions, GET profiles).
Values validated against pricing profiles (422 on mismatch); no DELETE.
- main.py registers router; OpenAPI re-exported; tests for CRUD/version/activate.
2026-06-23 21:19:25 +02:00
tliu93
bedea196c3
M6-T03: add pricing profile framework + strategy registry
...
- app/integrations/pricing: profiles.py (pydantic ManualProfile/TibberProfile,
load_profile/list_profiles/validate_values), manual.yaml + tibber.yaml
(structure-only, no price values), strategies.py (register/get_strategy,
manual dual-tariff + tibber strategies, all Decimal).
- tibber strategy matches nearest tibber_price with starts_at <= t0.
- tests for profiles + strategies (hand-checked dual-tariff & tibber math).
2026-06-23 20:58:34 +02:00
tliu93
df54f5518b
M6-T02: add DSMR + Tibber flat config fields
...
- Settings: dsmr_ingest_enabled, dsmr_mqtt_topic, dsmr_sample_interval_s,
tibber_api_token (secret), tibber_home_id (all opt-in / off by default).
- CONFIG_FIELDS DSMR + Tibber sections; _settings_payload extended.
- .env.example examples; test_api_config coverage (sections, secret, 422).
2026-06-23 20:43:46 +02:00
tliu93
18fe18281b
M6-T01: add 5 energy tables, models, and migration
...
- app/models/energy.py: DsmrReading, EnergyContract, EnergyContractVersion,
TibberPrice, EnergyCostPeriod (per design §3.5; JSON blobs, RESTRICT FKs).
- alembic_app migration 20260623_11_energy_tables (head), env.py imports.
- app_db_adopt APP_BASELINE_REVISION bumped to new head.
- tests/test_energy_models.py + expose_catalog test fixups for new head.
2026-06-23 20:33:35 +02:00
tliu93
355c8eb8b8
fix: add paho-mqtt and pymodbus to dev-requirements lock (CI install was missing M5 deps)
pytest / test (push) Successful in 5m13s
frontend / frontend (push) Successful in 1m48s
2026-06-23 20:06:13 +02:00
tliu93
1ffdaebbbd
M6: add Tibber API + NL energy pricing + DSMR reference doc
frontend / frontend (push) Successful in 1m54s
pytest / test (push) Failing after 32s
2026-06-23 19:56:02 +02:00
tliu93
c64693b8e5
M6: add design doc — generic pricing layer (manual/tibber) + DSMR cost engine
2026-06-23 19:53:26 +02:00
tliu93
c89d083942
M5: add read-only per-meter readings tabs to Records page; English-only UI strings
2026-06-22 22:16:55 +02:00
tliu93
6090b98ab0
M5: enable SQLite foreign_keys pragma for DB-level FK enforcement
2026-06-22 20:45:47 +02:00
tliu93
489cd7df3d
M5: default Modbus polling to off (opt-in) to avoid unnecessary DB writes
2026-06-22 20:28:12 +02:00
tliu93
35cdc7f22a
M5: use DB-merged runtime settings for MQTT/discovery; add MQTT test button; move Expose panel into config accordion
2026-06-22 20:11:54 +02:00
tliu93
75d685f04d
M5: roll Energy chart window for live auto-refresh (English label); render boolean config fields as switches
2026-06-22 19:40:18 +02:00
tliu93
935e68846c
M5: Energy view — per-metric decimal formatting and chart auto-refresh toggle
2026-06-22 19:18:29 +02:00
tliu93
4767a7af60
M5-T13: document Modbus/Energy/MQTT, mark M5 done, finalize OpenAPI
2026-06-22 16:10:11 +02:00
tliu93
45d87f36f7
M5-T12: add expose toggle API and Home Assistant Expose settings panel
2026-06-22 15:50:49 +02:00
tliu93
35b95f5c88
M5-T11: add HA discovery and state publishing wired to polling
2026-06-22 15:32:43 +02:00
tliu93
9d05f5ea62
M5-T10: add paho MQTT client with lifespan connect/reconnect and mqtt/test endpoint
2026-06-22 15:06:30 +02:00
tliu93
360cddc05b
M5-T09: add exposed_entities table and ExposableEntity provider framework
2026-06-22 14:50:40 +02:00
tliu93
14b846549e
M5-T08: add MQTT, HA Discovery, and Modbus polling config fields
2026-06-22 14:27:21 +02:00
tliu93
68165f0e01
M5-T07: add latest-reading cards and Recharts trend charts; readings return most-recent rows
2026-06-22 14:17:59 +02:00
tliu93
2dc469274a
M5-T06: add Energy device management UI and sidebar entry
2026-06-22 13:52:33 +02:00
tliu93
702a1cec00
M5-T05: add Modbus JSON API (device CRUD, readings, metrics, test)
2026-06-22 13:37:48 +02:00
tliu93
49d15d8ffe
M5-T04: add Modbus poll service and APScheduler polling job
2026-06-22 13:24:11 +02:00
tliu93
c89cea4953
M5-T03: add Modbus TCP driver, YAML profile framework, sdm120, modbus_cli
2026-06-22 13:03:50 +02:00
tliu93
d9c82e39fb
M5-T02: add modbus_device and modbus_reading tables and models
2026-06-22 12:45:50 +02:00
tliu93
eac7860b51
M5-T01B: render ConfigPage sections as Mantine Accordion
2026-06-22 12:34:04 +02:00
tliu93
da7d0f9d62
M5-T01: refactor AppLayout from top bar to sidebar navigation
2026-06-22 12:26:52 +02:00
tliu93
e49a6ad963
M5(design): generic modbus_device + JSON readings; YAML profiles; Accordion config; manual CLI/MQTT testing
...
frontend / frontend (push) Successful in 1m26s
pytest / test (push) Successful in 3m52s
Rework the M5 design doc data model and add manual-testing surface:
- Two-layer model: rename energy_meters/energy_readings -> generic
modbus_device + modbus_reading; readings become a JSON payload blob
instead of a wide fixed-column table (aggregation via SQLite
json_extract, generated-column index as a future lever).
- Protocol knowledge moves to read-only YAML profiles (data+functions,
no OOP inheritance); deployment/config (friendly_name, unit_id, host)
stays on the device row; multiple devices share one profile.
- UUID = stable internal identity, also anchors HA discovery unique_id
(Z2M rename model); device metadata for HA derived from the profile.
- Naming scheme C: modbus_* / /api/modbus/devices at the data+API layer,
'Energy' as the first domain view in the frontend.
- New task M5-T01B: ConfigPage Accordion sectioning (avoids a second
in-page side nav conflicting with the T01 app sidebar).
- Manual testing: read-only modbus_cli 'probe' subcommand (FC03/04 only,
no register writes); mqtt/test now publishes a test message viewable
in MQTT Explorer; no MQTT CLI (manual verification via UI + HA).
2026-06-22 11:55:39 +02:00
tliu93
d70b985eb0
fix(ci): recompile dev-requirements.txt to include pyotp (pytest CI install)
pytest / test (push) Successful in 3m51s
frontend / frontend (push) Successful in 1m26s
2026-06-22 11:08:06 +02:00
tliu93
e3c47b2762
docs(CLAUDE): switch to direct-on-main workflow for this single-user repo
frontend / frontend (push) Successful in 1m35s
pytest / test (push) Failing after 31s
2026-06-22 10:37:34 +02:00
tliu93
dbf17f3592
docs(CLAUDE): pin default sub-agent models (Implementer/Fixer=Sonnet, Reviewer=Opus) with manual-override clause
2026-06-22 09:50:46 +02:00
tliu93
ae75e4582d
M4-T09: document login hardening, finalize OpenAPI and roadmap
2026-06-21 23:01:50 +02:00
tliu93
ee1264b66b
M4-T08: add two-step login and TOTP settings panel (qrcode.react)
2026-06-21 22:42:41 +02:00
tliu93
51da8c5716
M4-T07: add admin_cli disable-totp and reissue-totp escape commands
2026-06-21 22:27:07 +02:00
tliu93
81bd32f613
M4-T06: add TOTP second factor to login (totp_code + recovery codes)
2026-06-21 22:08:55 +02:00
tliu93
ee3031013e
M4-T05: add TOTP service and setup/enable/disable/status API
2026-06-21 21:55:17 +02:00
tliu93
5026967cee
M4-T04: add AuthUser TOTP columns and auth_recovery_code table
2026-06-21 21:38:22 +02:00
tliu93
5481fbc99c
M4-T03: add admin_cli escape hatch (reset-password, unlock, list-admin)
2026-06-21 21:28:41 +02:00
tliu93
e480a84a44
M4-T02: add exponential login back-off service and wire into login endpoint
2026-06-21 21:20:28 +02:00
tliu93
64d3882c16
M4-T01: add auth_login_throttle table + LoginThrottle model
2026-06-21 21:03:20 +02:00