Re-create entitities which were remove via OptionsFlow

Hi developers,

I am working on a custom integration.

As described here,

If entities are disabled via a config options flow, remove them from the device and entity registry.

I developed an option flow which allows the user to remove certain entities (entities use multiple platforms. Namely: Sensor, Select and Number)
I have implemented this using the entity_registry API:

entity_id = entity_registry.async_get_entity_id(domain, platform, unique_id)
entity_registry.async_remove(entity_id)

I hope this is usage as intended. It works fine.

Now assume the user will re-enable the options again. How can I re-create new sensor, switch and number entities?

I have to create an Entity object, which allows me to implement properties like native_value() and current_option(). However, the only API I can find in entity_registry is entity_registry.async_get_or_create() which only allows to specify parameters that will impact the entity which is generated internally.

Am I on the right track, at all? If yes, how can implement the required callback methods as normally done in the derived Entity class?

So, when you change options via UI config flow, upon submit, it will reload the integration. As such, the way you create the entitites in the first place can be used to add these entities back.

Without a link to your code, its hard to tell you exactly as would need to see how you are stopping these deleted entities being recreated on restart. But however you are doing that, just remove these re-enabled ones from that list and they will recreate when saving the config change.

If you want more directed help, post a link to your code.

Thank you for your resonse.

I refer to config options flow, not the regular config flow. Upon submit, the main config entry is not changed or recreated. Instead, the callback is triggered via an update_listener() method after registering it as described in the docs:

entry.async_on_unload(entry.add_update_listener(update_listener))

Sorry, I should have explained better.

I found a solution by browsing the code of the a few core integrations. What seems to work is to store the async_add_entities: AddEntitiesCallback which is handed over in each platform initialization method async_setup_entry(). Instead of using the entity registry, it can be used to initialize new entities at any point in time, not only when the ConfigEntry gets created.

I will try that out first.
If I still cannot get it to work, I will post more of my code and a link to the repo.

Yes, that is what I was talking about. That update_listener should cause the config entry (ie your integration instance) to reload when updating config options.

Yes you can store a ref to async_add_entities as you say but that is more designed to add entities at runtime. As the way you describe you are doing this, i think you are overcomplicating it by doing that. Let me know if you need some help.

Interesting. The only point in time when I need need to remove or create entities is when the options are changed by the user.
If there is a simpler solution then storing the callback options, I would defintely prefer that.

How do I do that? Initially I went into this direction. I tried the following:

async def update_listener(hass: HomeAssistant, entry):
  for component in entry.runtime_data.components:
    component.set_enabled(entry.options.get(component.name(), False)

  await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)
  await hass.config_entries.async_forward_entry_setups(entry, _PLATFORMS)

But it turned out, that following this approach, Home Assistant did not properly remove entities and even devices.

Furthermore, I cannot find any other core integration which calls these two methods outside of the regular async_setup_entry() and async_unload_entry() methods.

Is this the right approach?

So, your update listener should just be the standard…

async def _async_update_listener(hass: HomeAssistant, config_entry):
    """Handle options update."""
    await hass.config_entries.async_reload(config_entry.entry_id)

In your entity component (ie sensor.py) async_setup_entry is where I would manage the adding / not adding and (maybe) removing of entities (although that maybe better in async_setup_entry in init.py.

These are both run at HA start and when your integration is reloaded on saving of config options if using the above update function.

Yes, you are right. This is how nearly all core integrations do it. No clue how I could miss this. Thank you!

Final doubts for me are on this part

What I currently have is the following (in update_listener() in init.py):

for sensor in component.sensors():
  if entity_id := entity_registry.async_get_entity_id(
    domain=Platform.SENSOR,
    platform=DOMAIN,
    unique_id=sensor.unique_id(),
  ):
    entity_registry.async_remove(entity_id)

This has the disadvantages:

  1. Its platform specific, so would also have to be moved to e.g. sensor.py. How could this be done for all platforms in init.py?
  2. All sensors are already grouped below one Device. However, the code above
    firstly still needs to iterate over each sensor of the device and secondly does not remove the device. I guess there is a more central API which allows to remove all entities of a device (irrespective which platform they are of) and the device itself?

It would really help to see your code to understand how you are flagging entities to not create.

However, what ever this list is, it should be referenced in async_setup_entry in the component to skip adding on load/reload.

You can then also use it to iterate over and if matching the list of entities for your config entry, remove it.

So to get all entity ids for your config entry, you can do.

entity_registry = er.async_get(hass)
integration_entities = er.async_entries_for_config_entry(
        entity_registry, config_entry.entry_id
    )

Iterate this and if it matches your dont create entity list then remove it with

for entity in integration_entities:
    if entity.entity_id in [my list of entities]:
   
     entity_registry.async_remove(entity.entity_id)

Given this is at the integration level, you can run it in init.py. to run it in the component async_setup_entry, add some filter to only check for entities that match the platform, ie Platform.SENSOR.

I have a working solution now which makes me happy.

For removing entries, I decided to do it in init.py, exactly as you suggested. Main reason: Only doing it here, will really remove the entities.

Otherwise the config entry gets removed and re-added with less entitities, leading to the (to me undesired) behavior that home assistant will report these entities as no longer supplied by the integration and the user has to manually remove them.

For adding new entities, I decided to use the async_add_entities: AddEntitiesCallback approach which I had linked in post #3.
Reason: I want to avoid that my config entry gets fully removed and re-added right afterwards to avoid the overhead (which is quite relevant for my integration) it causes. Also the additional code footprint is really minimal.

I am still finishing the code adjustments for some remaining platforms. Once its done and the code is committed, I can post a link here and share it.

Edit: Here the link to the corresponding code: ha_openems/custom_components/openems/__init__.py at 3a0b0aecd36560f1e6c1dd6df1caa60cc03e3d16 · Lamarqe/ha_openems · GitHub

Thank you once again for your support!