Stop image entities erroring on every startup, and make the docs match the real entity IDs - #32
Merged
Conversation
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
1. Image entities logged an error on every startup
image.pyadds its entities withasync_add_entities(entities, True). TheTrueisupdate_before_add, so HA callsasync_update()before the entity is added — andentity_idis only assigned after the add. The explicitself.async_write_ha_state()inside each imageasync_update()raisedNoEntitySpecifiedErroron that first call, and the surrounding broadexcept Exceptionturned it into one error line per image entity, every startup.Fix: remove all seven
async_write_ha_state()calls fromimage.py. Home Assistant writes the state itself exactly once, inadd_to_platform_finish()after the entity is added, so the explicit call was redundant then and fatal before then.update_before_add=Trueis 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()inweather.pyis deliberately left alone: it is_handle_forecast_update, the forecast-coordinator listener registered insideasync_added_to_hass(), so it can only fire onceentity_idexists, and there is no platform-side write behind it.2. Documentation entity IDs did not match the integration
Checked every
sensor./binary_sensor./image./weather.reference inREADME.md,CONFIGURATION.mdandinfo.mdagainst the 45 entity IDs produced by instantiating the real entity classes and applying HA'sobject_idrules. 128 references were wrong.sensor.noaa_ilm_temperaturesensor.noaa_ilm_weather_temperaturesensor.noaa_weather_hurricane_activitysensor.noaa_hurricane_activitysensor.noaa_space_kp_indexsensor.noaa_ilm_space_planetary_k_indexkp_indexplanetary_k_indexsensor.noaa_ilm_surf_heightsensor.noaa_ilm_surf_surf_heightsensor.aurora_duration_ilmsensor.noaa_ilm_space_aurora_durationExamples had also drifted across three office codes (
ilm,sgx,dlh), so changing one station id still left broken IDs. Everything now usesilm— 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 aNOAA Surfdevice that does not exist (it isNOAA {OFFICE} Surf) and omittedNOAA 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).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).condition: sunwith bothafter: sunsetandbefore: sunriseis unsatisfiable at every instant, so it never fired. Now asun.sunstate condition.cardinal_directionattribute.notify.mobile_appis never registered bare — the real service name isnotify.mobile_app_followed by your device slug, so the examples now usenotify.mobile_app_your_phone.Available; the text is infull_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.pyHome Assistant keys the entity registry on
unique_id, so an upgraded install keeps whateverentity_ideach entity was first assigned — theunique_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_nameanddevice_idfrom 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 onlyplatform: 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.pygainsTestAsyncUpdateBeforeAdd, which for all seven image entities runsasync_update()with anasync_write_ha_statethat raises and asserts_LOGGER.erroris 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 noasync_update()callsasync_write_ha_stateagain. The new tests run on a private event loop that restores the previous one, becauseasyncio.run()clears the thread's current loop andtests/test_weather.pyreaches forasyncio.get_event_loop().Version bumped to 0.5.2.
Type
Checklist
custom_components/,tests/andscripts/0.5.2