From 0e1a26737af094a66f6b287640ec30c13b714b9e Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Fri, 31 Jul 2026 12:13:42 +0200 Subject: [PATCH 1/2] feat(heishamon): report electrical power, and the outdoor temperature under its canonical name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FTW's heating view discovers a heat pump by the hp_power_w metric and shows nothing at all without it — no chart, no reading, no entry. So a driver verified against real hardware in June had no page in the product it was written for. hp_power_w now comes from main/Heat_Power_Consumption, which config power_topic overrides for a Heishamon build that names the topic differently. It is emitted only once that topic has arrived, so a wrong name costs the power reading and leaves every other metric alone. hp_outside_temp_c becomes hp_outdoor_temp_c, the name nibe_local and myuplink report and the one the view charts. Nothing read the old name. History under it stays where it is and stops growing. The DRIVER version said 0.4.0 while the manifest said 0.5.0; make bump-driver takes both to 0.6.0. The example config's MQTT password looked real enough to be one, and is now a placeholder. Co-Authored-By: Claude Opus 5 Signed-off-by: Fredrik Ahlgren --- CHANGELOG.md | 4 +++- SUPPORT_STATUS.md | 4 ++-- devices.yaml | 2 +- drivers/lua/heishamon.lua | 28 +++++++++++++++++++++++----- index.yaml | 6 +++--- manifests/heishamon.yaml | 6 +++--- support-status.json | 2 +- 7 files changed, 36 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e22d9..f96b5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -200,7 +200,9 @@ Driver versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html comments. No behaviour change. ### Added - +- **heishamon** 0.6.0 — **the pump now reports its electrical draw, and its outdoor temperature under the name every other heat pump uses.** FTW's heating view finds a heat pump by `hp_power_w` and finds nothing else if that metric is absent, so a working, hardware-verified driver had no page at all: not a chart, not a reading, not an entry. It emits `hp_power_w` from `main/Heat_Power_Consumption`, which `power_topic` overrides for a Heishamon build that names it differently, and only once that topic has arrived — a wrong name costs the power reading and nothing else. `hp_outside_temp_c` becomes `hp_outdoor_temp_c`, which is what nibe_local and myuplink report and what the view charts; the old name was read by nobody. Existing history under the old key stays where it is and stops growing +- The Lua `DRIVER` version said 0.4.0 while the manifest said 0.5.0. Both now say 0.6.0. The drift was invisible because nothing compares them outside `make bump-driver` +- The example config carried what reads like a real MQTT password. It is now a placeholder - **acuvim** 0.4.1 — Accuenergy Acuvim II three-phase revenue-grade meter, migrated from the Blixt L1 driver source (POI meter on Blixt SvK sites). One bundled FC03 float32 block + a bounded probe of the diff --git a/SUPPORT_STATUS.md b/SUPPORT_STATUS.md index 780b7f4..5c4a28e 100644 --- a/SUPPORT_STATUS.md +++ b/SUPPORT_STATUS.md @@ -78,8 +78,8 @@ Catalog source is not proof that a target can install or run a driver. | growatt | 2.1.2 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | hardybarth | 1.0.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | | hardybarth | 1.0.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | -| heishamon | 0.5.1 | ftw-core | not_assessed | — | not_recorded | not_assessed | -| heishamon | 0.5.1 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | +| heishamon | 0.6.0 | ftw-core | not_assessed | — | not_recorded | not_assessed | +| heishamon | 0.6.0 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | hello | 1.1.2 | ftw-core | not_assessed | — | not_recorded | not_assessed | | hello | 1.1.2 | blixt-l1 | not_assessed | — | not_recorded | not_assessed | | huawei | 2.1.3 | ftw-core | not_assessed | — | not_recorded | not_assessed | diff --git a/devices.yaml b/devices.yaml index 821da48..b2e63cc 100644 --- a/devices.yaml +++ b/devices.yaml @@ -1013,7 +1013,7 @@ manufacturers: protocols: - protocol: mqtt driver: "heishamon" - version: "0.5.1" + version: "0.6.0" ders: [heatpump] control: true firmware_versions: "" diff --git a/drivers/lua/heishamon.lua b/drivers/lua/heishamon.lua index 046d323..c8919a2 100644 --- a/drivers/lua/heishamon.lua +++ b/drivers/lua/heishamon.lua @@ -18,12 +18,19 @@ -- host: core-mosquitto -- port: 1883 -- username: mqtt-user --- password: 42wenkel +-- password: -- config: -- base_topic: panasonic_heat_pump -- min_offset: -3 -- max_offset: 3 -- safe_offset: 0 +-- power_topic: Heat_Power_Consumption +-- +-- power_topic names the main/ topic carrying the pump's electrical draw in W. +-- Heishamon publishes it as Heat_Power_Consumption (TOP21) on the builds this +-- driver has been run against; set it if your build names it differently. The +-- driver emits hp_power_w only once that topic has arrived, so a wrong name +-- costs the power reading and nothing else. DRIVER = { host_api_min = 1, @@ -31,7 +38,7 @@ DRIVER = { id = "heishamon", name = "Panasonic Aquarea (Heishamon)", manufacturer = "Panasonic", - version = "0.5.1", + version = "0.6.0", protocols = { "mqtt" }, capabilities = { "heatpump" }, description = "Panasonic Aquarea H/J/K/L/M-series heat pump via Heishamon MQTT bridge. Controls Zone 1 heat curve offset (Z1_Heat_Request_Temp) in range -3..+3 °C.", @@ -52,6 +59,7 @@ local outlet_temp = nil local inlet_temp = nil local target_temp = nil local z1_offset = nil +local power_w = nil local last_msg_ts = 0 local STALE_AFTER_MS = 60000 @@ -60,6 +68,7 @@ local base_topic = "panasonic_heat_pump" local min_offset = -3 local max_offset = 3 local safe_offset = 0 +local power_topic = "Heat_Power_Consumption" ---------------------------------------------------------------------------- -- Lifecycle @@ -73,6 +82,7 @@ function driver_init(config) if config.min_offset then min_offset = tonumber(config.min_offset) or -3 end if config.max_offset then max_offset = tonumber(config.max_offset) or 3 end if config.safe_offset then safe_offset = tonumber(config.safe_offset) or 0 end + if config.power_topic then power_topic = config.power_topic end end -- Subscribe broadly to all Heishamon topics @@ -85,7 +95,8 @@ function driver_init(config) host.log("info", "Heishamon: initialized, base_topic=" .. base_topic .. " offset_range=[" .. min_offset .. ".." .. max_offset .. "]" - .. " safe_offset=" .. safe_offset) + .. " safe_offset=" .. safe_offset + .. " power_topic=" .. power_topic) end function driver_poll() @@ -111,6 +122,9 @@ function driver_poll() elseif msg.topic == base_topic .. "/main/Z1_Heat_Request_Temp" then z1_offset = val last_msg_ts = now + elseif msg.topic == base_topic .. "/main/" .. power_topic then + power_w = val + last_msg_ts = now end end end @@ -124,14 +138,18 @@ function driver_poll() inlet_temp = nil target_temp = nil z1_offset = nil + power_w = nil end - -- Emit metrics - if outside_temp ~= nil then host.emit_metric("hp_outside_temp_c", outside_temp, "°C") end + -- Emit metrics. Names are the ones FTW's heating view reads: it finds a + -- heat pump by hp_power_w and charts the outdoor temperature under + -- hp_outdoor_temp_c, the same names nibe_local and myuplink report. + if outside_temp ~= nil then host.emit_metric("hp_outdoor_temp_c", outside_temp, "°C") end if outlet_temp ~= nil then host.emit_metric("hp_outlet_temp_c", outlet_temp, "°C") end if inlet_temp ~= nil then host.emit_metric("hp_inlet_temp_c", inlet_temp, "°C") end if target_temp ~= nil then host.emit_metric("hp_target_temp_c", target_temp, "°C") end if z1_offset ~= nil then host.emit_metric("hp_z1_heat_offset", z1_offset, "°C") end + if power_w ~= nil then host.emit_metric("hp_power_w", power_w, "W") end return 5000 end diff --git a/index.yaml b/index.yaml index 38895ac..30fcbfa 100644 --- a/index.yaml +++ b/index.yaml @@ -341,15 +341,15 @@ drivers: size_bytes: 2193 sha256: "517e086af899dc511c86b00460cd8ce052a8d4973edee8b018e5824d4b550d6e" - name: "heishamon" - version: "0.5.1" + version: "0.6.0" tier: core protocol: mqtt connectivity: local setup: [bridge] ders: [heatpump] control: true - size_bytes: 6973 - sha256: "a20d17ccc147772df3ad6827f0243436dcbb28b307e1dfc034cd6ff658a9ba69" + size_bytes: 8065 + sha256: "410f947f4eca8c4ab1436604c1cdc879a94057b7f9e345fb7cc8c07e70517dc9" - name: "hello" version: "1.1.2" tier: community diff --git a/manifests/heishamon.yaml b/manifests/heishamon.yaml index b1ac703..cb65f76 100644 --- a/manifests/heishamon.yaml +++ b/manifests/heishamon.yaml @@ -1,5 +1,5 @@ name: "heishamon" -version: "0.5.1" +version: "0.6.0" tier: core author: "Sourceful Labs AB" protocol: mqtt @@ -16,9 +16,9 @@ tested_devices: notes: "Panasonic Aquarea H/J/K/L/M-series heat pump via Heishamon MQTT bridge. Controls Zone 1 heat curve offset (Z1_Heat_Request_Temp) in range -3..+3 °C." min_driver_version: "0.4.0" min_host_version: "2.0.0" -size_bytes: 6973 +size_bytes: 8065 dkb_id: "heishamon" -sha256: "a20d17ccc147772df3ad6827f0243436dcbb28b307e1dfc034cd6ff658a9ba69" +sha256: "410f947f4eca8c4ab1436604c1cdc879a94057b7f9e345fb7cc8c07e70517dc9" signature: "" bytecode_sha256: "" diff --git a/support-status.json b/support-status.json index a2be65d..6be495d 100644 --- a/support-status.json +++ b/support-status.json @@ -779,7 +779,7 @@ }, { "catalog_source": true, - "catalog_version": "0.5.1", + "catalog_version": "0.6.0", "driver_id": "heishamon", "targets": { "blixt-l1": { From 7db98cc6c9feec84e34fc177bf6df13ef2bb46ca Mon Sep 17 00:00:00 2001 From: Fredrik Ahlgren Date: Fri, 7 Aug 2026 15:20:06 +0200 Subject: [PATCH 2/2] fix(heishamon): expire stale power independently Signed-off-by: Fredrik Ahlgren --- drivers/lua/heishamon.lua | 14 ++++++- .../test_heishamon_power_freshness.lua | 38 +++++++++++++++++++ drivers/tests/test_stale_reads.py | 7 ++++ index.yaml | 4 +- manifests/heishamon.yaml | 4 +- 5 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 drivers/tests/lua_harness/test_heishamon_power_freshness.lua diff --git a/drivers/lua/heishamon.lua b/drivers/lua/heishamon.lua index c8919a2..70e6e27 100644 --- a/drivers/lua/heishamon.lua +++ b/drivers/lua/heishamon.lua @@ -61,6 +61,7 @@ local target_temp = nil local z1_offset = nil local power_w = nil local last_msg_ts = 0 +local last_power_ts = 0 local STALE_AFTER_MS = 60000 -- Config (overridable via config.yaml) @@ -123,8 +124,9 @@ function driver_poll() z1_offset = val last_msg_ts = now elseif msg.topic == base_topic .. "/main/" .. power_topic then - power_w = val - last_msg_ts = now + power_w = val + last_power_ts = now + last_msg_ts = now end end end @@ -139,6 +141,12 @@ function driver_poll() target_temp = nil z1_offset = nil power_w = nil + last_power_ts = 0 + elseif last_power_ts > 0 and (now - last_power_ts) > STALE_AFTER_MS then + host.log("warn", "Heishamon: no power reading for " + .. tostring(STALE_AFTER_MS) .. " ms — power stale") + power_w = nil + last_power_ts = 0 end -- Emit metrics. Names are the ones FTW's heating view reads: it finds a @@ -204,4 +212,6 @@ function driver_cleanup() inlet_temp = nil target_temp = nil z1_offset = nil + power_w = nil + last_power_ts = 0 end diff --git a/drivers/tests/lua_harness/test_heishamon_power_freshness.lua b/drivers/tests/lua_harness/test_heishamon_power_freshness.lua new file mode 100644 index 0000000..a8a1cc1 --- /dev/null +++ b/drivers/tests/lua_harness/test_heishamon_power_freshness.lua @@ -0,0 +1,38 @@ +dofile("drivers/tests/lua_harness/host_mock.lua") +host.reset() +dofile("drivers/lua/heishamon.lua") +driver_init({}) + +host._mqtt_buffer = { + { + topic = "panasonic_heat_pump/main/Heat_Power_Consumption", + payload = "4200", + }, + { + topic = "panasonic_heat_pump/main/Outside_Temp", + payload = "5", + }, +} +driver_poll() + +local fresh_power = host._metrics.hp_power_w +if fresh_power == nil or fresh_power.value ~= 4200 then + error("fresh Heishamon power reading was not emitted") +end + +host._metrics = {} +host._millis_counter = 60100 +host._mqtt_buffer = {{ + topic = "panasonic_heat_pump/main/Outside_Temp", + payload = "6", +}} +driver_poll() + +if host._metrics.hp_power_w ~= nil then + error("stale Heishamon power survived an unrelated fresh topic") +end + +local fresh_outdoor = host._metrics.hp_outdoor_temp_c +if fresh_outdoor == nil or fresh_outdoor.value ~= 6 then + error("fresh unrelated Heishamon telemetry was dropped with stale power") +end diff --git a/drivers/tests/test_stale_reads.py b/drivers/tests/test_stale_reads.py index 5727056..f8edfb9 100644 --- a/drivers/tests/test_stale_reads.py +++ b/drivers/tests/test_stale_reads.py @@ -28,6 +28,13 @@ def test_mqtt_driver_does_not_emit_cached_data_on_idle_poll(driver_name): run_lua("drivers/tests/lua_harness/test_mqtt_stale.lua", driver_name) +def test_heishamon_power_freshness_is_not_extended_by_other_topics(): + run_lua( + "drivers/tests/lua_harness/test_heishamon_power_freshness.lua", + "heishamon", + ) + + @pytest.mark.parametrize("app", ["ProEM", "MiniPMG3", "Plus2PM"]) def test_shelly_poll_emits_nothing_when_all_reads_fail(app): run_lua("drivers/tests/lua_harness/test_shelly_failures.lua", app) diff --git a/index.yaml b/index.yaml index 30fcbfa..2fab1b8 100644 --- a/index.yaml +++ b/index.yaml @@ -348,8 +348,8 @@ drivers: setup: [bridge] ders: [heatpump] control: true - size_bytes: 8065 - sha256: "410f947f4eca8c4ab1436604c1cdc879a94057b7f9e345fb7cc8c07e70517dc9" + size_bytes: 8458 + sha256: "c5bb882d0dfba3f566cfe13a91e46fa361e02a64b1a249e133de2ec0366188ac" - name: "hello" version: "1.1.2" tier: community diff --git a/manifests/heishamon.yaml b/manifests/heishamon.yaml index cb65f76..807c6f5 100644 --- a/manifests/heishamon.yaml +++ b/manifests/heishamon.yaml @@ -16,9 +16,9 @@ tested_devices: notes: "Panasonic Aquarea H/J/K/L/M-series heat pump via Heishamon MQTT bridge. Controls Zone 1 heat curve offset (Z1_Heat_Request_Temp) in range -3..+3 °C." min_driver_version: "0.4.0" min_host_version: "2.0.0" -size_bytes: 8065 +size_bytes: 8458 dkb_id: "heishamon" -sha256: "410f947f4eca8c4ab1436604c1cdc879a94057b7f9e345fb7cc8c07e70517dc9" +sha256: "c5bb882d0dfba3f566cfe13a91e46fa361e02a64b1a249e133de2ec0366188ac" signature: "" bytecode_sha256: ""