Skip to content

BREAKING CHANGE: Fix: use uniqueId for battery HA auto-discovery#2442

Open
AndreasBoehm wants to merge 1 commit intodevelopmentfrom
andreasboehm/feature/ha-auto-discovery-multiple-batteries
Open

BREAKING CHANGE: Fix: use uniqueId for battery HA auto-discovery#2442
AndreasBoehm wants to merge 1 commit intodevelopmentfrom
andreasboehm/feature/ha-auto-discovery-multiple-batteries

Conversation

@AndreasBoehm
Copy link
Copy Markdown
Member

@AndreasBoehm AndreasBoehm commented Mar 30, 2026

Fixes #1587

Previously the battery topics where based on the hardcoded serial '0001' which leads to overlaps when multiple DTUs are connected to the same HA instance.

Screenshot 2026-03-30 at 17 03 07

TODO

  • Figure out if this is actually a breaking change

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

@AndreasBoehm Sure, I'll review the changes in this PR now!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@AndreasBoehm AndreasBoehm force-pushed the andreasboehm/feature/ha-auto-discovery-multiple-batteries branch from 700f3ff to c928bcc Compare March 30, 2026 14:53
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 30, 2026

Build Artifacts

Firmware built from this pull request's code:

Notice

  • These artifacts are ZIP files containing the factory update binary as well as the OTA update binary.
    Extract the binaries from the ZIP files first. Do not use the ZIP files themselves to perform an update.
  • These links point to artifacts of the latest successful build run.
  • The linked artifacts were built from 4d0e0fc.

@AndreasBoehm
Copy link
Copy Markdown
Member Author

@Manos1966 probiers mal mit dem build von diesem PR. Das sollte jetzt klappen.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 30, 2026

Walkthrough

Modified battery ID generation in MQTT Home Assistant integration by replacing hardcoded pseudo-serial field with a dynamic createBatteryId() method that incorporates the DTU's unique identifier, enabling multiple OpenDTU-OnBattery instances to coexist without topic conflicts.

Changes

Cohort / File(s) Summary
Home Assistant Integration Header
include/battery/HassIntegration.h
Removed private String _serial member variable and added new private static method createBatteryId() for dynamic battery ID generation.
Home Assistant Integration Implementation
src/battery/HassIntegration.cpp
Implemented createBatteryId() method returning DTU unique ID appended with "_battery". Updated MQTT config topic construction, entity uniq_id generation, and device identity in createDeviceInfo() to use the new method instead of hardcoded _serial value.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A serial no more, hardcoded and old,
Now each battery gets its own tale to be told!
With DTU IDs dancing, no conflicts will rise,
Multiple homes bloom—hooray for the skies! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly relates to the changeset, explaining the issue with hardcoded serial '0001' causing topic overlaps with multiple DTUs and the fix implemented.
Linked Issues check ✅ Passed The code changes successfully address issue #1587 by replacing hardcoded serial '0001' with unique DTU identifiers in battery MQTT topics and Home Assistant discovery.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the battery MQTT topic collision issue; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch andreasboehm/feature/ha-auto-discovery-multiple-batteries

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Manos1966
Copy link
Copy Markdown

Habe ich dir schon mal erzählt, wie frustrierend es ist, stundenlang zu versuchen, etwas zu verstehen (ohne Erfolg), und dann kommt jemand anderes und liefert die Lösung innerhalb von vier Stunden?

SEHR FRUSTIEREND! 🤣

Es funktioniert! 🥇
Vielen Dank, Andreas – das ist schon die zweite Kiste Bier, die ich dir schulde 🙈

Zwei Batterien (zwei verschiedene OpenDTU-OnBattery sofort erkannt:
image

image image

@Manos1966
Copy link
Copy Markdown

Perfekt! ❤️

image

@Manos1966
Copy link
Copy Markdown

Among the four, one uses the old Firmware.
Despite this, I can separate the SOC % without problems 👍
I have the impression however, that the other battery details which are in the OpenDTU-OnBattery/src/battery/pylontech/Stats.cpp
are been overwritten by the one DTU with the old Firmware.
I have to find time to go to that installation and update it, too (I can not do an update by remote for that installation).
After all DTUs have the same Firmware I can say with more confidence.

image

@Manos1966
Copy link
Copy Markdown

Manos1966 commented Apr 9, 2026

UPDATE @AndreasBoehm

ich habe heute die Firmware der vierten DTU mit der neuen Firmware upgedated.
Alles funktioniert im HA wie vorher 👍

Also, KEIN "Breaking change"

image

Ich hatte bis jetzt keine Zeit gehabt zu pruefen, ob die anderen Daten getroffen sind
die, die in der OpenDTU-OnBattery/src/battery/pylontech/Stats.cpp sind.

UPDATE2: es funktioniert wunderbar!

  1. Es gibt weiterhin die "Standard" Information (roter Kreis). Es sind die Daten der letzten Batterie die aktiviert wurde.
  2. Zusaetzlich, jede Batterie wird auch separat representiert.
image

Vielen Dank Andreas! 🥇

Previously the battery topics where based on the hardcoded serial '0001' which leads to overlaps when multiple DTUs are connected to the same HA instance.
@AndreasBoehm AndreasBoehm force-pushed the andreasboehm/feature/ha-auto-discovery-multiple-batteries branch from c928bcc to dcb983d Compare April 10, 2026 12:21
@AndreasBoehm AndreasBoehm marked this pull request as ready for review April 10, 2026 12:25
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/battery/HassIntegration.cpp (1)

54-55: Cache createBatteryId() once per method to reduce temporary String churn.

Use a local batteryId in each publish method and reuse it for both configTopic and uniq_id.

♻️ Proposed refactor
 void HassIntegration::publishSensor(const char* caption, const char* icon,
         const char* subTopic, const char* deviceClass,
         const char* stateClass, const char* unitOfMeasurement,
         const bool enabled) const
 {
     String sensorId = sanitizeUniqueId(caption);
+    const String batteryId = createBatteryId();

-    String configTopic = "sensor/" + createBatteryId()
+    String configTopic = "sensor/" + batteryId
         + "/" + sensorId
         + "/config";
@@
-    root["uniq_id"] = createBatteryId() + "_" + sensorId;
+    root["uniq_id"] = batteryId + "_" + sensorId;
 }
@@
 void HassIntegration::publishBinarySensor(const char* caption,
         const char* icon, const char* subTopic,
         const char* payload_on, const char* payload_off,
         const bool enabled) const
 {
     String sensorId = sanitizeUniqueId(caption);
+    const String batteryId = createBatteryId();

-    String configTopic = "binary_sensor/" + createBatteryId()
+    String configTopic = "binary_sensor/" + batteryId
         + "/" + sensorId
         + "/config";
@@
-    root["uniq_id"] = createBatteryId() + "_" + sensorId;
+    root["uniq_id"] = batteryId + "_" + sensorId;
 }

Also applies to: 64-64, 108-110, 118-118

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/battery/HassIntegration.cpp` around lines 54 - 55, Cache the result of
createBatteryId() into a local variable (e.g., batteryId) at the start of each
publish method and reuse that variable when building configTopic and when
setting uniq_id instead of calling createBatteryId() multiple times; update
places that construct configTopic (currently using "sensor/" + createBatteryId()
+ "/" + sensorId) and any lines that set uniq_id to reference batteryId, and
apply the same change to the other occurrences noted (around the other publish
calls at the indicated ranges).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/battery/HassIntegration.cpp`:
- Around line 54-55: The Home Assistant discovery IDs were changed to use
createBatteryId(), which will make HA treat entities as new and leave old ones
orphaned; add migration documentation to release notes explaining the reason
(fixing hardcoded '0001' collisions), expected upgrade behavior (new entities,
old ones unavailable), and explicit cleanup steps (publish an empty retained
payload to legacy discovery topics such as
homeassistant/sensor/0001/<sensorId>/config to remove orphaned entities). Also
implement an optional one-time migration routine in HassIntegration (on first
startup after upgrade) that iterates legacy discovery topic patterns (e.g.,
sensor/0001/<sensorId>/config) and publishes empty retained payloads to remove
old entities, ensuring it only runs once and records completion to avoid
repeated runs; reference createBatteryId(), configTopic, uniq_id and device ids
when locating the related code to update.

---

Nitpick comments:
In `@src/battery/HassIntegration.cpp`:
- Around line 54-55: Cache the result of createBatteryId() into a local variable
(e.g., batteryId) at the start of each publish method and reuse that variable
when building configTopic and when setting uniq_id instead of calling
createBatteryId() multiple times; update places that construct configTopic
(currently using "sensor/" + createBatteryId() + "/" + sensorId) and any lines
that set uniq_id to reference batteryId, and apply the same change to the other
occurrences noted (around the other publish calls at the indicated ranges).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5a4a55bf-a431-4186-8e81-ba42383af4c2

📥 Commits

Reviewing files that changed from the base of the PR and between 905d58b and dcb983d.

📒 Files selected for processing (2)
  • include/battery/HassIntegration.h
  • src/battery/HassIntegration.cpp

Comment on lines +54 to 55
String configTopic = "sensor/" + createBatteryId()
+ "/" + sensorId
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Home Assistant MQTT discovery, what happens when an existing entity's uniq_id changes after a firmware update? Are old entities/devices automatically migrated or left as stale registry entries?

💡 Result:

Web search failed: Server error: no LLM provider could handle the message


🏁 Script executed:

# First, find and examine the HassIntegration.cpp file
fd -t f "HassIntegration.cpp" --exec wc -l {} \;

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 517


🏁 Script executed:

# Get the specific lines mentioned in the review
cat -n src/battery/HassIntegration.cpp | sed -n '50,170p'

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 4165


🌐 Web query:

Home Assistant MQTT discovery uniq_id change migration behavior

💡 Result:

  • unique_id is the identity key. Home Assistant uses an entity’s unique_id to tie MQTT-discovered entities to the Entity Registry (so you can later rename the entity_id and keep that rename). (home-assistant.io)
  • If you change the unique_id, Home Assistant will treat it as a different entity. Practically, that means you’ll get a new entity in HA, and the old one will remain as an orphan (often unavailable) until you remove it; HA will not automatically “migrate” the old entity’s entity_id, area, history settings, automations, etc., to the new unique_id. (This follows from how discovery creates/updates entities keyed by unique_id, and that removals are explicit.) (home-assistant.io)
  • Changing the discovery topic’s <object_id> is not the same as changing unique_id. The <object_id> segment in the discovery topic does not control the resulting entity_id; best practice is to set that <object_id> to the unique_id (so you don’t accidentally create parallel entities when you change topics). (home-assistant.io)
  • How to clean up the old entity (recommended “migration” pattern):
    1. Publish the new discovery config (with the new unique_id) so the new entity appears.
    2. Publish an empty (retained) payload to the old discovery .../config topic to delete the old discovered entity and clear the retained config. (home-assistant.io)
  • Related but different: {"migrate_discovery": true} is a special mechanism for migrating single-component discovery topics to device discovery while retaining settings; it’s not a general “unique_id change migration.” (home-assistant.io)

Citations:


🌐 Web query:

Home Assistant MQTT entity unique ID change upgrade retained topics

💡 Result:

Web search failed: Server error: no LLM provider could handle the message


🏁 Script executed:

# Search for migration or cleanup related code
rg -i "migration|cleanup|legacy|orphan" src/battery/ --type cpp --type h

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 182


🏁 Script executed:

# Check for CHANGELOG or release notes mentioning battery IDs or migration
fd -t f "CHANGELOG|RELEASE|NEWS|MIGRATION" --iglob "*" | head -20

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 271


🏁 Script executed:

# Search for any cleanup of old discovery topics in the codebase
rg "publish.*config.*empty\|empty.*payload\|retained" src/ --type cpp

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 51


🏁 Script executed:

# Check git history for when this change was made and if there were migration notes
git log --oneline -20 src/battery/HassIntegration.cpp 2>/dev/null || echo "Git history not available"

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 125


🏁 Script executed:

# Get the full commit details to see what changed
git show dcb983d8 --no-patch

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 413


🏁 Script executed:

# Check what the old battery ID format was
git show dcb983d8:src/battery/HassIntegration.cpp 2>/dev/null | head -100

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 2954


🏁 Script executed:

# Verify cpplint compliance for the affected file
# Check if cpplint is available
which cpplint || echo "cpplint not available"

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 151


🏁 Script executed:

# At minimum, check for obvious style issues in the affected lines
cat -n src/battery/HassIntegration.cpp | sed -n '54,56p; 64p; 108,110p; 118p; 146p'

Repository: hoylabs/OpenDTU-OnBattery

Length of output: 495


Add migration documentation for Home Assistant discovery identity changes.

The battery discovery uniq_id (lines 64, 118) and device ids (line 146) now use createBatteryId() to fix multi-DTU collisions. However, on upgrades, Home Assistant will treat these as new entities and leave old ones orphaned—automations and dashboards bound to the old IDs will break, and HA does not auto-migrate them.

To mitigate this for users, add release notes documenting:

  1. The change reason (fixing hardcoded '0001' collisions in multi-DTU setups)
  2. Expected behavior on upgrade (new entities, old ones become unavailable)
  3. Cleanup procedure: publish an empty retained payload to the old discovery topics (e.g., homeassistant/sensor/0001/<sensorId>/config) to explicitly remove orphaned entities from the HA registry

Alternatively, consider a one-time cleanup routine on first startup after the ID changes to publish empty payloads for any legacy discovery topics.

Also applies to: 54–55, 108–110

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/battery/HassIntegration.cpp` around lines 54 - 55, The Home Assistant
discovery IDs were changed to use createBatteryId(), which will make HA treat
entities as new and leave old ones orphaned; add migration documentation to
release notes explaining the reason (fixing hardcoded '0001' collisions),
expected upgrade behavior (new entities, old ones unavailable), and explicit
cleanup steps (publish an empty retained payload to legacy discovery topics such
as homeassistant/sensor/0001/<sensorId>/config to remove orphaned entities).
Also implement an optional one-time migration routine in HassIntegration (on
first startup after upgrade) that iterates legacy discovery topic patterns
(e.g., sensor/0001/<sensorId>/config) and publishes empty retained payloads to
remove old entities, ensuring it only runs once and records completion to avoid
repeated runs; reference createBatteryId(), configTopic, uniq_id and device ids
when locating the related code to update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Home Assistent auto-discovery broken for multiple batteries

2 participants