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.
This commit is contained in:
@@ -214,12 +214,12 @@ def test_off_interval_not_multiple_of_10(dsmr_db):
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 3. Idempotency — same source_id not re-inserted
|
||||
# 3. Idempotency — keyed on recorded_at (telegram timestamp), NOT the telegram id
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_same_source_id_not_reinserted(dsmr_db):
|
||||
"""Feeding the same telegram twice (same id) must result in exactly one row."""
|
||||
def test_same_timestamp_not_reinserted(dsmr_db):
|
||||
"""Feeding the same telegram twice (same timestamp) must result in one row."""
|
||||
_, SessionLocal = dsmr_db
|
||||
settings = _make_settings(dsmr_sample_interval_s=10)
|
||||
|
||||
@@ -229,8 +229,8 @@ def test_same_source_id_not_reinserted(dsmr_db):
|
||||
assert _count_readings(SessionLocal) == 1
|
||||
|
||||
|
||||
def test_different_source_ids_are_independent(dsmr_db):
|
||||
"""Different id values from different telegrams produce separate rows."""
|
||||
def test_different_timestamps_are_independent(dsmr_db):
|
||||
"""Telegrams with different timestamps produce separate rows."""
|
||||
_, SessionLocal = dsmr_db
|
||||
settings = _make_settings(dsmr_sample_interval_s=10)
|
||||
|
||||
@@ -240,6 +240,24 @@ def test_different_source_ids_are_independent(dsmr_db):
|
||||
assert _count_readings(SessionLocal) == 2
|
||||
|
||||
|
||||
def test_telegram_id_collision_does_not_drop_new_data(dsmr_db):
|
||||
"""Regression: the telegram id overflows / gets reset to zero in DSMR firmware.
|
||||
Two DISTINCT telegrams (different timestamps) that happen to share the SAME
|
||||
telegram id must BOTH be stored — dedup must not depend on the telegram id."""
|
||||
_, SessionLocal = dsmr_db
|
||||
settings = _make_settings(dsmr_sample_interval_s=10)
|
||||
|
||||
first = {**_SAMPLE_TELEGRAM, "id": 0, "timestamp": "2026-06-23T12:16:00Z"}
|
||||
# Later telegram, id reset back to the same value after an overflow.
|
||||
second = {**_SAMPLE_TELEGRAM, "id": 0, "timestamp": "2026-06-23T12:16:10Z"}
|
||||
|
||||
_call_handle_message(first, settings, SessionLocal)
|
||||
_call_handle_message(second, settings, SessionLocal)
|
||||
|
||||
# Both must persist — the colliding telegram id must not cause a drop.
|
||||
assert _count_readings(SessionLocal) == 2
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 4. Missing source_id — still persisted with source_id=None
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user