Skip to content

Stop image entities erroring on every startup, and make the docs match the real entity IDs - #32

Merged
turbo5000c merged 5 commits into
mainfrom
claude/image-entity-startup-errors-ib3dnn
Aug 23, 2026
Merged

Stop image entities erroring on every startup, and make the docs match the real entity IDs#32
turbo5000c merged 5 commits into
mainfrom
claude/image-entity-startup-errors-ib3dnn

Conversation

@turbo5000c

@turbo5000c turbo5000c commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Description

1. Image entities logged an error on every startup

Error during radar loop image update for PQR: No entity id specified for entity Radar Loop
Error during GOES GeoColor image update: No entity id specified for entity ...

image.py adds its entities with async_add_entities(entities, True). The True is update_before_add, so HA calls async_update() before the entity is added — and entity_id is only assigned after the add. The explicit self.async_write_ha_state() inside each image async_update() raised NoEntitySpecifiedError on that first call, and the surrounding broad except Exception turned it into one error line per image entity, every startup.

Fix: remove all seven async_write_ha_state() calls from image.py. Home Assistant writes the state itself exactly once, in add_to_platform_finish() after the entity is added, so the explicit call was redundant then and fatal before then. update_before_add=True is kept, so the image URL is resolved during setup rather than left empty.

Sites removed, one per image class: GeoelectricFieldImageEntity, AuroraForecastImageEntity, HurricaneOutlookImageEntity, RadarBaseReflectivityImageEntity, RadarLoopImageEntity, GOESAirMassImageEntity, GOESGeoColorImageEntity.

An image entity disabled in the entity registry is never added, so the pre-add update is the only update it ever runs — there the error repeated every time instead of firing once at startup.

The single async_write_ha_state() in weather.py is deliberately left alone: it is _handle_forecast_update, the forecast-coordinator listener registered inside async_added_to_hass(), so it can only fire once entity_id exists, and there is no platform-side write behind it.

Correction carried in this PR (1 of 2). An earlier revision of this description, the CHANGELOG entry and a test docstring all justified the removal with "these are polled entities, so HA writes the state itself once async_update() returns". That is wrong. Image entities are not polled: upstream ImageEntity sets _attr_should_poll = False (home-assistant/core tag 2026.8.1, homeassistant/components/image/__init__.py:207), and entity_platform.async_add_entities only arms a polling timer when some entity reports should_poll. The removal is still correct, for the reason stated above. Corrected in all three places.

The consequence is recorded in the CHANGELOG as a known follow-up, not fixed here: async_update() runs exactly once, so the module-level SCAN_INTERVAL in image.py has no effect, the cache-busting ?t= suffix is fixed for the life of the config entry, and each image entity's state stays unknown because _attr_image_last_updated is never set. Pre-existing behaviour, unchanged by this PR.

2. Documentation entity IDs did not match the integration

Checked every sensor. / binary_sensor. / image. / weather. reference in README.md, CONFIGURATION.md and info.md against the 45 entity IDs produced by instantiating the real entity classes and applying HA's object_id rules. 128 references were wrong.

Problem Documented Actual
Missing device-group segment sensor.noaa_ilm_temperature sensor.noaa_ilm_weather_temperature
Phantom device-group segment sensor.noaa_weather_hurricane_activity sensor.noaa_hurricane_activity
Missing office code sensor.noaa_space_kp_index sensor.noaa_ilm_space_planetary_k_index
Renamed metric kp_index planetary_k_index
Lookalike slug sensor.noaa_ilm_surf_height sensor.noaa_ilm_surf_surf_height
Legacy trailing office sensor.aurora_duration_ilm sensor.noaa_ilm_space_aurora_duration

Examples had also drifted across three office codes (ilm, sgx, dlh), so changing one station id still left broken IDs. Everything now uses ilm — one token.

The naming rules that produced those errors are corrected too. README claimed "All entities use _attr_has_entity_name = True" — three do not. Its device table listed a NOAA Surf device that does not exist (it is NOAA {OFFICE} Surf) and omitted NOAA Hurricane; CONFIGURATION.md's documented pattern omitted the device-group segment entirely. Both documents now carry an Exceptions to the pattern table naming the five global hurricane entity IDs that contain no office code, so a find-and-replace on the office does not corrupt them, plus the two that follow the rule but read oddly (..._surf_surf_height, weather.noaa_{office}_weather).

Correction carried in this PR (2 of 2). Reported against a real install: image.aurora_forecast_image does not exist; the entity is image.noaa_ilm_space_aurora_forecast_image. An earlier revision of this PR documented the geoelectric and aurora images without a device prefix, on the belief that an entity omitting _attr_has_entity_name derives its object_id from slugify(name) alone.

That rule was wrong. Per entity_registry._async_get_full_entity_name (2026.8.1), the device name is always prepended when the entity belongs to a device; has_entity_name only decides whether a redundant device-name prefix is stripped off the entity's own name first (_async_strip_prefix_from_entity_name). Both classes carry device_info for NOAA {OFFICE} Space, so both IDs are office-scoped:

  • image.noaa_{office}_space_geoelectric_field_image
  • image.noaa_{office}_space_aurora_forecast_image

Only these two entities are affected — regenerating the inventory under the corrected rule leaves the other 43 unchanged. They are consequently no longer "exceptions to the pattern", and the stated rule is corrected in both documents rather than just the examples, so the error cannot regenerate from it.

3. Examples that failed even with the right entity ID

  • state_attr() calls naming attributes that do not exist — total_alerts, alert_types, office, issued_time, product_link — now use the real keys (alert_count, alerts, office_code, issue_time).
  • The aurora automation's condition: sun with both after: sunset and before: sunrise is unsatisfiable at every instant, so it never fired. Now a sun.sun state condition.
  • Wind direction read the state, which is degrees; the cardinal point is in the cardinal_direction attribute.
  • notify.mobile_app is never registered bare — the real service name is notify.mobile_app_ followed by your device slug, so the examples now use notify.mobile_app_your_phone.
  • The forecast-discussion sensor's state is the literal Available; the text is in full_text / summary.

Also corrected: the 5-minute refresh claim (coordinators run at DEFAULT_SCAN_INTERVAL = 10, meteor at 30), legacy YAML documented as providing "global sensors only" when it creates no entities and logs an error, info.md's device list, a migration-table row that mapped an ID to itself, and a duplicated example line. All 45 entities are now documented.

4. scripts/rename_noaa_entities.py

Home Assistant keys the entity registry on unique_id, so an upgraded install keeps whatever entity_id each entity was first assigned — the unique_ids did not change across the 0.5.x naming fixes, so existing installs still carry the old IDs while the docs describe what a fresh install produces. Renaming ~45 entities by hand in the UI was the only alternative.

The script recomputes each ID the way HA itself would, reading has_entity_name, original_name and device_id from the user's own registry rather than assuming an office code or coordinates. Dry run by default, backs the registry up before writing, skips targets that are already occupied, and touches only platform: noaa_it_all. It carried the same wrong naming rule as the docs and would have renamed those two image entities to non-existent IDs; it now mirrors HA's real derivation, and re-running it repairs a registry the earlier version renamed.

Tests

tests/test_image.py gains TestAsyncUpdateBeforeAdd, which for all seven image entities runs async_update() with an async_write_ha_state that raises and asserts _LOGGER.error is never called — this reproduces the reported message exactly when the fix is reverted (verified: 3 failed before, 709 passed after). It also guards at source level that no async_update() calls async_write_ha_state again. The new tests run on a private event loop that restores the previous one, because asyncio.run() clears the thread's current loop and tests/test_weather.py reaches for asyncio.get_event_loop().

Version bumped to 0.5.2.

Type

  • Bugfix
  • Documentation
  • Feature
  • Other

Checklist

  • Follows Home Assistant custom integration structure
  • Passes flake8 checks (max-line-length=120) — clean across custom_components/, tests/ and scripts/
  • Manifest is valid and complete — version bumped to 0.5.2
  • README updated if required — README, CONFIGURATION.md and info.md all corrected

claude added 3 commits August 22, 2026 22:20
image.py adds its entities with async_add_entities(entities, True), so
Home Assistant awaits async_update() before the entity is added — and
entity_id is only assigned after the add. The explicit
self.async_write_ha_state() in each image async_update() therefore raised
NoEntitySpecifiedError on that first call, and the surrounding broad
except turned it into an error log line per image entity on every startup.

Remove all seven writes. These are polled entities, so HA writes the state
itself once async_update() returns: the explicit call was redundant once
the entity was added, and fatal before it was. update_before_add=True is
kept so the image URL is still populated eagerly instead of waiting for
the first poll.

An image entity disabled in the entity registry is never added, so the
pre-add update is the only update that ever runs for it — there the error
repeated on every update rather than self-correcting after startup.

The async_write_ha_state() in weather.py is left alone: it is the forecast
coordinator listener registered in async_added_to_hass(), so it can only
fire once entity_id exists, and it has no polled update behind it.

Add regression tests that run async_update() on all seven image entities
with an async_write_ha_state that raises, asserting no error is logged and
the image URL still refreshes, plus a source-level guard against
reintroducing the pattern. The new tests run on a private event loop so
they do not clear the ambient one that test_weather.py relies on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPbMyextfs9VchL52V78Wu
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPbMyextfs9VchL52V78Wu
Every sensor./binary_sensor./image./weather. reference in README.md,
CONFIGURATION.md and info.md was checked against the 45 entity IDs produced
by instantiating the real entity classes and applying Home Assistant's
object_id rules. 128 references were wrong.

Most were missing the device-group segment (sensor.noaa_ilm_temperature for
sensor.noaa_ilm_weather_temperature), carried a phantom one
(sensor.noaa_weather_hurricane_activity for sensor.noaa_hurricane_activity),
used a renamed metric (kp_index for planetary_k_index), or used a legacy
trailing-office form (sensor.aurora_duration_ilm). One is a lookalike that
the docs got wrong everywhere: the surf height sensor is
sensor.noaa_{office}_surf_surf_height, not ..._surf_height.

Examples had also drifted across three office codes (ilm, sgx, dlh), so
changing one station id still left broken IDs. Everything now uses ilm.

The naming rules that generated those errors are corrected too. README
claimed "All entities use _attr_has_entity_name = True" — three do not. Its
device table listed a NOAA Surf device that does not exist (it is
NOAA {OFFICE} Surf) and omitted NOAA Hurricane; CONFIGURATION.md's pattern
omitted the device-group segment entirely. Both now carry an "Exceptions to
the pattern" table naming the seven entity IDs that contain no office code,
so a find-and-replace on the office does not corrupt them, plus the two IDs
that follow the rule but read oddly.

Fixed examples that would fail even with the right entity ID: state_attr()
calls naming attributes that do not exist (total_alerts, alert_types,
office, issued_time, product_link) now use the real keys; the aurora
automation's condition: sun with both after: sunset and before: sunrise —
unsatisfiable at every instant, so it never fired — is now a sun.sun state
condition; wind direction reads cardinal_direction rather than the state,
which is degrees; notify.mobile_app is written notify.mobile_app_your_phone
since the bare service is never registered.

Also corrected: the 5-minute refresh claim (coordinators run at 10 minutes,
meteor at 30), legacy YAML documented as providing "global sensors only"
when it creates no entities and logs an error, info.md's device list, a
migration table row that mapped an ID to itself, and a duplicated example
line. Documented the seven entities that appeared nowhere: the weather
entity, radar loop, dewpoint, hourly forecast, cloud cover, radar timestamp
and the two derived space-weather sensors.

Corrected this release's own rationale in CHANGELOG.md and the matching test
docstring. Both said the seven removed async_write_ha_state() calls were
redundant "because these are polled entities". Image entities are not
polled: upstream ImageEntity sets _attr_should_poll = False (verified
against home-assistant/core 2026.8.1), and entity_platform only arms a
polling timer when some entity reports should_poll. The removal is still
correct — Home Assistant writes the state once in add_to_platform_finish()
after the add — but the stated mechanism was wrong. Recorded the
consequence as a known follow-up: async_update() runs exactly once, so the
cache-busting suffix is fixed for the life of the config entry and each
image entity's state stays unknown because _attr_image_last_updated is
never set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPbMyextfs9VchL52V78Wu
@turbo5000c turbo5000c changed the title Stop image entities erroring on every startup Stop image entities erroring on every startup, and make the docs match the real entity IDs Aug 23, 2026
claude added 2 commits August 23, 2026 03:56
Home Assistant keys the entity registry on unique_id, so an upgraded
install keeps whatever entity_id each entity was first assigned. The
unique_ids did not change across the 0.5.x naming fixes, so existing
installs still carry the old IDs while the documentation now describes
what a fresh install produces. Renaming ~45 entities by hand in the UI is
the only alternative.

The script recomputes each NOAA entity's entity_id exactly the way Home
Assistant would for a fresh install -- slugify(f"{device_name} {name}")
when the entity sets has_entity_name, slugify(name) otherwise -- reading
has_entity_name, original_name and device_id from the user's own registry
rather than assuming an office code or coordinates. It is a dry run by
default, backs the registry up before writing, skips entities whose target
ID is already occupied, and touches only platform == noaa_it_all.

Verified against a synthetic registry seeded with the old IDs: all nine
renames land on the documented IDs, already-correct entities and other
integrations' entities are left untouched, and a second run is a no-op.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPbMyextfs9VchL52V78Wu
Reported from a real install: image.aurora_forecast_image does not exist;
the entity is image.noaa_ilm_space_aurora_forecast_image.

The previous commit documented the geoelectric and aurora images without a
device prefix, on the belief that an entity omitting _attr_has_entity_name
gets its object_id from slugify(name) alone. That is wrong. Per
homeassistant/helpers/entity_registry._async_get_full_entity_name in
2026.8.1, the device name is ALWAYS prepended when the entity belongs to a
device; has_entity_name only decides whether a redundant device-name prefix
is stripped off the entity's own name first (via
_async_strip_prefix_from_entity_name). Both classes do carry device_info for
NOAA {OFFICE} Space, so both IDs are office-scoped:

  image.noaa_{office}_space_geoelectric_field_image
  image.noaa_{office}_space_aurora_forecast_image

The pre-existing docs had this right in one of the three places they spelled
these entities; the bad rule picked the wrong one.

Only these two entities are affected -- regenerating the inventory under the
corrected rule leaves the other 43 unchanged, because every other entity
either sets has_entity_name or already carries its device name.

Consequently these are no longer "exceptions to the pattern": the exceptions
table in README.md and CONFIGURATION.md now lists only the five global
hurricane entities, whose device genuinely has no office code. The stated
naming rule is corrected in both documents rather than just the examples, so
the error cannot regenerate from it.

scripts/rename_noaa_entities.py carried the same wrong rule and would have
renamed both entities to non-existent IDs. It now mirrors HA's real
derivation, and re-running it repairs a registry that the previous version
renamed: verified against a synthetic registry seeded with the bad IDs, both
are restored and the other 43 are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FPbMyextfs9VchL52V78Wu
@turbo5000c
turbo5000c marked this pull request as ready for review August 23, 2026 04:52
@turbo5000c
turbo5000c merged commit fbe0099 into main Aug 23, 2026
1 check passed
@turbo5000c
turbo5000c deleted the claude/image-entity-startup-errors-ib3dnn branch August 23, 2026 04:52
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