expose: buy/sell_price_now follow the live dual-tariff slot
Subscribe the separate DSMR tariff topic (dsmr/meter-stats/electricity_tariff, 1=dal/off-peak, 2=normal/peak), keep the latest slot in memory, and make the manual-contract buy/sell_price_now sensors show the matching tariff's price (tariff 1 -> dal, 2/unknown -> normal). Tibber (single 15-min price) unchanged. Billing is untouched (register-split already handles tariffs correctly). - new config dsmr_tariff_topic; subscribe managed by apply_dsmr_subscription (restart-free on config save). In-memory tariff is lock-guarded. - import_cost_total / export_revenue_total cumulative getters unchanged.
This commit is contained in:
@@ -732,7 +732,7 @@ def test_put_config_mqtt_reconnect_uses_db_merged_settings(
|
||||
|
||||
|
||||
def test_get_config_includes_dsmr_section(client: TestClient) -> None:
|
||||
"""GET /api/config must include a DSMR section with expected fields."""
|
||||
"""GET /api/config must include a DSMR section with expected fields including DSMR_TARIFF_TOPIC."""
|
||||
_login(client)
|
||||
|
||||
response = client.get("/api/config")
|
||||
@@ -746,6 +746,9 @@ def test_get_config_includes_dsmr_section(client: TestClient) -> None:
|
||||
assert "DSMR_INGEST_ENABLED" in env_names
|
||||
assert "DSMR_MQTT_TOPIC" in env_names
|
||||
assert "DSMR_SAMPLE_INTERVAL_S" in env_names
|
||||
assert "DSMR_TARIFF_TOPIC" in env_names, (
|
||||
f"DSMR_TARIFF_TOPIC must be present in DSMR section; got {env_names}"
|
||||
)
|
||||
|
||||
|
||||
def test_get_config_includes_tibber_section(client: TestClient) -> None:
|
||||
@@ -903,6 +906,51 @@ def test_put_config_invalid_dsmr_sample_interval_returns_422_and_does_not_write(
|
||||
assert rows.get("DSMR_SAMPLE_INTERVAL_S") != "not-a-number"
|
||||
|
||||
|
||||
def test_put_config_dsmr_tariff_topic_persists_and_reflects_in_get(
|
||||
client: TestClient, test_database_urls
|
||||
) -> None:
|
||||
"""DSMR_TARIFF_TOPIC must persist via PUT and be readable via GET /api/config."""
|
||||
_login(client)
|
||||
|
||||
new_topic = "meter/tariff/slot"
|
||||
payload = _full_config_payload({"DSMR_TARIFF_TOPIC": new_topic})
|
||||
with patch("app.services.dsmr_ingest.apply_dsmr_subscription"):
|
||||
response = client.put(
|
||||
"/api/config",
|
||||
json={"updates": payload},
|
||||
headers={"X-CSRF-Token": "token"},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# The updated value must appear in the GET response.
|
||||
get_resp = client.get("/api/config")
|
||||
body = get_resp.json()
|
||||
dsmr_section = next(s for s in body["sections"] if s["name"] == "DSMR")
|
||||
tariff_field = next(f for f in dsmr_section["fields"] if f["env_name"] == "DSMR_TARIFF_TOPIC")
|
||||
assert tariff_field["value"] == new_topic, (
|
||||
f"Expected DSMR_TARIFF_TOPIC to be {new_topic!r}, got {tariff_field['value']!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_put_config_dsmr_tariff_topic_in_settings_payload(client: TestClient) -> None:
|
||||
"""DSMR_TARIFF_TOPIC must appear in _settings_payload (GET /api/config returns it)."""
|
||||
_login(client)
|
||||
|
||||
# The default value from Settings must appear in the DSMR section.
|
||||
response = client.get("/api/config")
|
||||
body = response.json()
|
||||
|
||||
dsmr_section = next(s for s in body["sections"] if s["name"] == "DSMR")
|
||||
tariff_field = next(
|
||||
(f for f in dsmr_section["fields"] if f["env_name"] == "DSMR_TARIFF_TOPIC"), None
|
||||
)
|
||||
assert tariff_field is not None, "DSMR_TARIFF_TOPIC must appear in DSMR config section"
|
||||
# Default value should be the DSMR reader meter-stats topic.
|
||||
assert tariff_field["value"] == "dsmr/meter-stats/electricity_tariff", (
|
||||
f"Unexpected default for DSMR_TARIFF_TOPIC: {tariff_field['value']!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_get_config_tibber_api_token_value_masked_after_save(
|
||||
client: TestClient, test_database_urls
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user