Skip to content

Conversation

nlindn
Copy link
Contributor

@nlindn nlindn commented Sep 8, 2025

This PR fixes an issue in the configuration flow where newly added charge points were not being appended to the "cpids" list in core.config_entries. As a result, when Home Assistant was restarted, previously added chargers were rediscovered, again as not added.

This resolves the bug reported in #1698 and ensures that charge points are correctly tracked across restarts.

Changes:

  • Update config flow to append new charge points to the existing cpids list in core.config_entries

Testing:

  • Verified that after adding a new charge point, it is correctly stored in core.config_entries.
  • Confirmed that previously added charge points are not rediscovered on restart but are instead recognized as reconnected.

Summary by CodeRabbit

  • Refactor

    • Streamlined the configuration flow for adding/updating charge points to avoid unnecessary reloads, improving responsiveness and reliability during setup.
    • Adopted safer, immutable updates for configuration data to reduce potential inconsistencies.
  • Bug Fixes

    • Standardized the confirmation message to “Added/Updated charge point” after configuring a charge point, providing clearer and more consistent feedback to users.
    • Ensured configuration changes are applied immediately without disruptive reloads, resulting in a smoother setup experience.

@nlindn nlindn temporarily deployed to continuous-integration September 8, 2025 19:43 — with GitHub Actions Inactive
Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

Walkthrough

Updates config flow logic in custom_components/ocpp/config_flow.py to use immutable updates for CPIDs, replace async_update_reload_and_abort with hass.config_entries.async_update_entry followed by async_abort, standardize abort reasons, and apply these changes in async_step_cp_user and async_step_measurands, including conditional updates when CONF_MONITORED_VARIABLES_AUTOCONFIG is true.

Changes

Cohort / File(s) Summary
Config flow updates
custom_components/ocpp/config_flow.py
- Build CPIDs via immutable list update, then reassign self._data with CONF_CPIDS.
- Replace async_update_reload_and_abort with hass.config_entries.async_update_entry(...); async_abort(reason="Added/Updated charge point").
- Apply in async_step_cp_user (conditional on CONF_MONITORED_VARIABLES_AUTOCONFIG) and async_step_measurands.
- Standardize abort reasons to "Added/Updated charge point".

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant CF as OCPP ConfigFlow
  participant HA as hass.config_entries

  U->>CF: Start CP user/measurands step
  CF->>CF: Build cp_data / update _measurands
  alt Auto-config enabled or measurands updated
    CF->>HA: async_update_entry(entry, data=self._data)
    HA-->>CF: Updated
    CF-->>U: async_abort("Added/Updated charge point")
  else No update condition
    CF-->>U: async_abort("Added/Updated charge point")
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit taps keys with a gentle thump,
Swaps reload spells for a lighter jump.
Lists now copied, no in-place creep,
Entries updated—then off to sleep.
“Added/Updated,” nibble, cheer—
Hops through config, crystal clear. 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  - Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.
  - Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

codecov bot commented Sep 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.26%. Comparing base (9bf50dd) to head (e39927d).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1712   +/-   ##
=======================================
  Coverage   94.25%   94.26%           
=======================================
  Files          12       12           
  Lines        2751     2755    +4     
=======================================
+ Hits         2593     2597    +4     
  Misses        158      158           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
custom_components/ocpp/config_flow.py (1)

193-213: Validate at least one measurand and update the correct CP; reload after update.

  • Currently, selecting none still passes (empty set is a subset), resulting in an empty string measurands value. Validate non-empty.
  • Avoid [-1]; target the CP by self._cp_id.
  • Same reload concern as above.
         if user_input is not None:
             selected_measurands = [m for m, value in user_input.items() if value]
-            if not set(selected_measurands).issubset(set(MEASURANDS)):
+            if not selected_measurands or not set(selected_measurands).issubset(set(MEASURANDS)):
                 errors["base"] = "no_measurands_selected"
                 return self.async_show_form(
                     step_id="measurands",
                     data_schema=STEP_USER_MEASURANDS_SCHEMA,
                     errors=errors,
                 )
             else:
                 self._measurands = ",".join(selected_measurands)
-                self._data[CONF_CPIDS][-1][self._cp_id][CONF_MONITORED_VARIABLES] = (
-                    self._measurands
-                )
-
-                self.hass.config_entries.async_update_entry(
-                    self._entry, data=self._data
-                )
-                return self.async_abort(reason="Added/Updated charge point")
+                # Update the targeted CP entry by id
+                for d in self._data.get(CONF_CPIDS, []):
+                    if self._cp_id in d:
+                        d[self._cp_id][CONF_MONITORED_VARIABLES] = self._measurands
+                        break
+                # Option A:
+                return self.async_update_reload_and_abort(
+                    entry=self._entry,
+                    data=self._data,
+                    reason="cp_added_or_updated",
+                )
+                # Option B:
+                # self.hass.config_entries.async_update_entry(self._entry, data=self._data)
+                # await self.hass.config_entries.async_reload(self._entry.entry_id)
+                # return self.async_abort(reason="cp_added_or_updated")
🧹 Nitpick comments (1)
custom_components/ocpp/config_flow.py (1)

181-182: Use a translation key-style abort reason.

Use a slug (e.g., cp_added_or_updated) and add it to translations/*.json for localization instead of a sentence with spaces and slash.

-                return self.async_abort(reason="Added/Updated charge point")
+                return self.async_abort(reason="cp_added_or_updated")

Also applies to: 212-213

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bf50dd and e39927d.

📒 Files selected for processing (1)
  • custom_components/ocpp/config_flow.py (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: drc38
PR: lbbrhzn/ocpp#1498
File: tests/test_config_flow.py:124-124
Timestamp: 2025-01-28T09:21:24.150Z
Learning: The OCPP integration does not use options flow for configuration updates. Error handling in the config flow is primarily focused on measurand selection validation with the "no_measurands_selected" error.
🧬 Code graph analysis (1)
custom_components/ocpp/config_flow.py (1)
tests/test_config_flow.py (1)
  • test_successful_discovery_flow (74-150)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests

Comment on lines +170 to +173
cpids_list = self._data.get(CONF_CPIDS, []).copy()
cpids_list.append({self._cp_id: cp_data})
self._data = {**self._data, CONF_CPIDS: cpids_list}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Prevent duplicate CPIDs and fix CPID mismatch (discovery vs. user input).

Good switch to immutable update, but we always append and we key by self._cp_id while duplicate-checking uses user_input[CONF_CPID] (nested field not covered by _async_abort_entries_match). This can create duplicate CPIDs and ignores a potential mismatch between discovered cp_id and the value entered in the form.

  • Update-or-insert the CPID (not always append).
  • Optionally enforce that user_input[CONF_CPID] == self._cp_id or ignore the form’s CPID to avoid drift.

Apply:

-            cpids_list = self._data.get(CONF_CPIDS, []).copy()
-            cpids_list.append({self._cp_id: cp_data})
-            self._data = {**self._data, CONF_CPIDS: cpids_list}
+            cpids_list = list(self._data.get(CONF_CPIDS, []))  # shallow copy
+            # Update if CP already exists, else append
+            existing_idx = next((i for i, d in enumerate(cpids_list) if self._cp_id in d), -1)
+            if existing_idx >= 0:
+                cpids_list[existing_idx][self._cp_id] = cp_data
+                cp_idx = existing_idx
+            else:
+                cpids_list.append({self._cp_id: cp_data})
+                cp_idx = len(cpids_list) - 1
+            self._data = {**self._data, CONF_CPIDS: cpids_list}

Optional (enforce discovery CPID):

-            # Don't allow duplicate cpids to be used
-            self._async_abort_entries_match({CONF_CPID: user_input[CONF_CPID]})
+            # Enforce the discovered CPID; reject mismatches early
+            if user_input[CONF_CPID] != self._cp_id:
+                return self.async_abort(reason="cp_id_mismatch")

Also applies to: 162-169

🤖 Prompt for AI Agents
In custom_components/ocpp/config_flow.py around lines 162-173, the current code
always appends a new CPID entry keyed by self._cp_id and can create duplicates
or drift from the CPID supplied in user_input; change this to update-or-insert
the cpids list instead of always appending, by searching existing entries for a
matching CPID key and replacing its data if found, otherwise append; also
resolve the potential mismatch by either enforcing user_input[CONF_CPID] ==
self._cp_id (raise/abort if different) or ignore the form CPID and always use
self._cp_id as the canonical key/value before updating the list; apply the same
fix to the earlier block at lines 162-169.

Comment on lines 174 to +182
if user_input[CONF_MONITORED_VARIABLES_AUTOCONFIG]:
self._data[CONF_CPIDS][-1][self._cp_id][CONF_MONITORED_VARIABLES] = (
DEFAULT_MONITORED_VARIABLES
)
return self.async_update_reload_and_abort(
self._entry,
data_updates=self._data,
self.hass.config_entries.async_update_entry(
self._entry, data=self._data
)
return self.async_abort(reason="Added/Updated charge point")

Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Write to the correct CP entry and ensure runtime picks up changes (reload).

  • When autoconfig is true, we currently update [-1]. If we updated an existing CP above, [-1] may point to the wrong CP. Use the computed cp_idx.
  • After updating entry data, reload the entry or use the HA helper to ensure the running integration observes the new CP immediately.
-            if user_input[CONF_MONITORED_VARIABLES_AUTOCONFIG]:
-                self._data[CONF_CPIDS][-1][self._cp_id][CONF_MONITORED_VARIABLES] = (
-                    DEFAULT_MONITORED_VARIABLES
-                )
-                self.hass.config_entries.async_update_entry(
-                    self._entry, data=self._data
-                )
-                return self.async_abort(reason="Added/Updated charge point")
+            if user_input[CONF_MONITORED_VARIABLES_AUTOCONFIG]:
+                self._data[CONF_CPIDS][cp_idx][self._cp_id][CONF_MONITORED_VARIABLES] = (
+                    DEFAULT_MONITORED_VARIABLES
+                )
+                # Option A (preferred if available):
+                return self.async_update_reload_and_abort(
+                    entry=self._entry,
+                    data=self._data,
+                    reason="cp_added_or_updated",
+                )
+                # Option B (fallback):
+                # self.hass.config_entries.async_update_entry(self._entry, data=self._data)
+                # await self.hass.config_entries.async_reload(self._entry.entry_id)
+                # return self.async_abort(reason="cp_added_or_updated")

To confirm whether async_update_reload_and_abort is available in your HA base and used elsewhere in the repo:


🏁 Script executed:

#!/bin/bash
rg -n "async_update_reload_and_abort|async_reload\(" -g '!**/venv/**' -C2

Length of output: 148


🏁 Script executed:

#!/bin/bash
# Search for async_update_reload_and_abort and async_reload usages in the codebase
rg -n "async_update_reload_and_abort" -C2 .
rg -n "async_reload\(" -C2 .

Length of output: 2445


🏁 Script executed:

#!/bin/bash
# Search for cp_idx occurrences and context in config_flow.py
rg -n "cp_idx" -C3 custom_components/ocpp/config_flow.py

Length of output: 0


Compute correct CP index and reload entry after update (config_flow.py 174–182)

  • Don’t use [-1]; locate the CP’s position in self._data[CONF_CPIDS], e.g.:
    cp_list = self._data[CONF_CPIDS]
    cp_idx = next(i for i, cp in enumerate(cp_list) if self._cp_id in cp)
    self._data[CONF_CPIDS][cp_idx][self._cp_id][CONF_MONITORED_VARIABLES] = DEFAULT_MONITORED_VARIABLES
  • After calling self.hass.config_entries.async_update_entry(self._entry, data=self._data), invoke the existing reload helper to apply changes immediately:
    await async_reload_entry(self.hass, self._entry)
🤖 Prompt for AI Agents
In custom_components/ocpp/config_flow.py around lines 174 to 182, the code uses
[-1] to set monitored variables for a charge point which can target the wrong
CP; find the actual index of the CP in self._data[CONF_CPIDS] by locating the
dict that contains self._cp_id, set CONF_MONITORED_VARIABLES on that computed
index instead of using [-1], call
self.hass.config_entries.async_update_entry(self._entry, data=self._data) as
before, and then await the existing async_reload_entry helper for this entry to
apply the changes immediately.

@drc38 drc38 merged commit 3fe4b64 into lbbrhzn:main Sep 8, 2025
9 checks passed
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.

2 participants