From 2ef5b5c0218bdb168a9778d522708aee72c9a33b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:44:26 +0000 Subject: [PATCH 1/4] Initial plan From 37aaf42355e513ff023e7be185395f6e37edd844 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:50:30 +0000 Subject: [PATCH 2/4] fix(sensor): merge events into latest_events, remove redundant events attribute (v1.2.5) Co-authored-by: Geek-MD <25725990+Geek-MD@users.noreply.github.com> --- CHANGELOG.md | 9 ++++++ .../usgs_earthquakes_feed/diagnostics.py | 2 +- .../usgs_earthquakes_feed/manifest.json | 2 +- .../usgs_earthquakes_feed/sensor.py | 32 ++++++------------- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2fab77..10bcc01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. +## [1.2.5] - 2026-03-16 + +### Fixed +- **`latest_events` was always empty after the first update cycle**: The sensor previously maintained two separate lists — `events` (cumulative) and `latest_events` (new events only per cycle). Because `latest_events` was reset to only the newly-detected IDs on every update, it became empty whenever no brand-new earthquakes arrived, causing the `format_events` service to return an empty result. + +### Changed +- **Removed `events` attribute from the sensor**: Seismic events are now exposed solely through the `latest_events` attribute, which accumulates all events (up to 50) ordered from most recent to oldest — mirroring the previous behaviour of the `events` attribute. +- **Diagnostics now report `latest_events`**: The diagnostics payload has been updated to expose `latest_events` instead of the removed `events` key. + ## [1.2.4] - 2026-03-16 ### Fixed diff --git a/custom_components/usgs_earthquakes_feed/diagnostics.py b/custom_components/usgs_earthquakes_feed/diagnostics.py index 4c50204..81a3275 100644 --- a/custom_components/usgs_earthquakes_feed/diagnostics.py +++ b/custom_components/usgs_earthquakes_feed/diagnostics.py @@ -15,6 +15,6 @@ async def async_get_config_entry_diagnostics( diagnostics = { "config": entry.data, "options": entry.options, - "events": data.get("events", []), + "latest_events": data.get("latest_events", []), } return diagnostics diff --git a/custom_components/usgs_earthquakes_feed/manifest.json b/custom_components/usgs_earthquakes_feed/manifest.json index c33cf9f..02d7b1d 100644 --- a/custom_components/usgs_earthquakes_feed/manifest.json +++ b/custom_components/usgs_earthquakes_feed/manifest.json @@ -13,5 +13,5 @@ "aio-geojson-usgs-earthquakes==0.3", "aio-geojson-client==0.12" ], - "version": "1.2.4" + "version": "1.2.5" } diff --git a/custom_components/usgs_earthquakes_feed/sensor.py b/custom_components/usgs_earthquakes_feed/sensor.py index db0b2a1..98b0c71 100644 --- a/custom_components/usgs_earthquakes_feed/sensor.py +++ b/custom_components/usgs_earthquakes_feed/sensor.py @@ -44,7 +44,6 @@ def __init__(self, hass: HomeAssistant, entry_id: str, device_info: DeviceInfo) self.hass = hass self._entry_id = entry_id self._attr_device_info = device_info - self._events: list[dict[str, Any]] = [] self._latest_events: list[dict[str, Any]] = [] self._unsub_dispatcher: Any = None self._attr_native_value: datetime | None = None @@ -66,41 +65,31 @@ async def _async_update_events(self) -> None: """Update sensor state from the shared event list.""" new_events = self.hass.data[DOMAIN][self._entry_id].get("events", []) - # Crear conjunto con las ids ya almacenadas - existing_ids = {e["id"] for e in self._events} - - # Determinar si es primera ejecución (sin eventos guardados) - if not self._events: - filtered_events = new_events - else: - filtered_events = [e for e in new_events if e["id"] not in existing_ids] + # Filtrar eventos nuevos no vistos antes + existing_ids = {e["id"] for e in self._latest_events} + filtered_events = [e for e in new_events if e["id"] not in existing_ids] # Agregar nuevos eventos y reordenar - self._events.extend(filtered_events) - self._events = sorted( - self._events, key=parse_event_time, reverse=True - )[:MAX_EVENTS] - - # latest_events: eventos nuevos de esta actualización, ordenados del más reciente al más antiguo + self._latest_events.extend(filtered_events) self._latest_events = sorted( - filtered_events, key=parse_event_time, reverse=True - ) + self._latest_events, key=parse_event_time, reverse=True + )[:MAX_EVENTS] # Publicar latest_events en hass.data para que el servicio format_events pueda leerlos entry_data = self.hass.data[DOMAIN].setdefault(self._entry_id, {}) entry_data["latest_events"] = self._latest_events # Actualizar valor del sensor (fecha del más reciente) - if self._events: + if self._latest_events: try: - time_val = self._events[0]["time"] + time_val = self._latest_events[0]["time"] if isinstance(time_val, datetime): dt = time_val if time_val.tzinfo else time_val.replace(tzinfo=timezone.utc) else: dt = datetime.fromisoformat(str(time_val).replace("Z", "+00:00")) self._attr_native_value = as_local(dt) except (ValueError, AttributeError): - _LOGGER.debug("Could not parse native value from event time: %s", self._events[0].get("time")) + _LOGGER.debug("Could not parse native value from event time: %s", self._latest_events[0].get("time")) self._attr_native_value = None else: self._attr_native_value = None @@ -108,14 +97,13 @@ async def _async_update_events(self) -> None: _LOGGER.debug( "USGS Quakes Sensor actualizado. Nuevos eventos: %d. Total almacenados: %d.", len(filtered_events), - len(self._events), + len(self._latest_events), ) self.async_write_ha_state() @property def extra_state_attributes(self) -> dict[str, Any]: return { - "events": self._events, "latest_events": self._latest_events, } From ca8dd21c03cc4acba5aabc2ef4a0ba77f5f6dfe0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:19:57 +0000 Subject: [PATCH 3/4] fix(sensor): latest_events holds only new events per cycle, tracked via _seen_ids (v1.2.6) Co-authored-by: Geek-MD <25725990+Geek-MD@users.noreply.github.com> --- CHANGELOG.md | 13 ++++++++++ .../usgs_earthquakes_feed/manifest.json | 2 +- .../usgs_earthquakes_feed/sensor.py | 26 +++++++------------ 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10bcc01..423bd8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ All notable changes to this project will be documented in this file. +## [1.2.6] - 2026-03-16 + +### Fixed +- **Conceptual error in v1.2.5**: `latest_events` was incorrectly made a cumulative list that grew on every update, always containing all historical events. The correct behaviour is: + - `latest_events` contains **only the new events** detected in the current update cycle (events whose IDs have not been seen before). + - On the **first run** (or after HA restarts), all events returned by the feed are considered new, so `latest_events` is populated with all of them. + - On **subsequent runs** where no new earthquakes have been reported, `latest_events` is empty (`[]`), the sensor state does not change, and automations that trigger on state change are not fired. + - When a **new earthquake** is detected, `latest_events` contains only that event (or those events), the sensor state updates to the most recent event's timestamp, and the automation is triggered. + +### Changed +- Replaced the cumulative `_latest_events` accumulator with an internal `_seen_ids: set[str]` that tracks which event IDs have already been reported. This is not exposed as a sensor attribute. +- Removed the now-unused `MAX_EVENTS` constant from `sensor.py`. + ## [1.2.5] - 2026-03-16 ### Fixed diff --git a/custom_components/usgs_earthquakes_feed/manifest.json b/custom_components/usgs_earthquakes_feed/manifest.json index 02d7b1d..e6533b2 100644 --- a/custom_components/usgs_earthquakes_feed/manifest.json +++ b/custom_components/usgs_earthquakes_feed/manifest.json @@ -13,5 +13,5 @@ "aio-geojson-usgs-earthquakes==0.3", "aio-geojson-client==0.12" ], - "version": "1.2.5" + "version": "1.2.6" } diff --git a/custom_components/usgs_earthquakes_feed/sensor.py b/custom_components/usgs_earthquakes_feed/sensor.py index 98b0c71..3f6c9bb 100644 --- a/custom_components/usgs_earthquakes_feed/sensor.py +++ b/custom_components/usgs_earthquakes_feed/sensor.py @@ -26,8 +26,6 @@ SIGNAL_EVENTS_UPDATED = f"{DOMAIN}_events_updated_{{}}" -MAX_EVENTS = 50 # Máximo de eventos a almacenar - class UsgsQuakesLatestSensor(SensorEntity): """Sensor to store the latest USGS quake events.""" @@ -44,6 +42,7 @@ def __init__(self, hass: HomeAssistant, entry_id: str, device_info: DeviceInfo) self.hass = hass self._entry_id = entry_id self._attr_device_info = device_info + self._seen_ids: set[str] = set() self._latest_events: list[dict[str, Any]] = [] self._unsub_dispatcher: Any = None self._attr_native_value: datetime | None = None @@ -65,21 +64,20 @@ async def _async_update_events(self) -> None: """Update sensor state from the shared event list.""" new_events = self.hass.data[DOMAIN][self._entry_id].get("events", []) - # Filtrar eventos nuevos no vistos antes - existing_ids = {e["id"] for e in self._latest_events} - filtered_events = [e for e in new_events if e["id"] not in existing_ids] + # Determinar qué eventos son nuevos (no vistos antes) + filtered_events = [e for e in new_events if e["id"] not in self._seen_ids] + + # Registrar los nuevos IDs como vistos + self._seen_ids.update(e["id"] for e in filtered_events) - # Agregar nuevos eventos y reordenar - self._latest_events.extend(filtered_events) - self._latest_events = sorted( - self._latest_events, key=parse_event_time, reverse=True - )[:MAX_EVENTS] + # latest_events: solo los eventos nuevos de este ciclo, del más reciente al más antiguo + self._latest_events = sorted(filtered_events, key=parse_event_time, reverse=True) # Publicar latest_events en hass.data para que el servicio format_events pueda leerlos entry_data = self.hass.data[DOMAIN].setdefault(self._entry_id, {}) entry_data["latest_events"] = self._latest_events - # Actualizar valor del sensor (fecha del más reciente) + # Actualizar el estado del sensor solo cuando lleguen eventos nuevos if self._latest_events: try: time_val = self._latest_events[0]["time"] @@ -90,14 +88,10 @@ async def _async_update_events(self) -> None: self._attr_native_value = as_local(dt) except (ValueError, AttributeError): _LOGGER.debug("Could not parse native value from event time: %s", self._latest_events[0].get("time")) - self._attr_native_value = None - else: - self._attr_native_value = None _LOGGER.debug( - "USGS Quakes Sensor actualizado. Nuevos eventos: %d. Total almacenados: %d.", + "USGS Quakes Sensor actualizado. Nuevos eventos: %d.", len(filtered_events), - len(self._latest_events), ) self.async_write_ha_state() From 0fc851284279aeea5373b67676f50fae0e003bf3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 16 Mar 2026 23:27:18 +0000 Subject: [PATCH 4/4] feat(sensor): fire HA event on new earthquakes, fix README (v1.2.7) Co-authored-by: Geek-MD <25725990+Geek-MD@users.noreply.github.com> --- CHANGELOG.md | 10 +++- README.md | 59 ++++++++++++++++--- .../usgs_earthquakes_feed/const.py | 3 + .../usgs_earthquakes_feed/manifest.json | 2 +- .../usgs_earthquakes_feed/sensor.py | 12 +++- 5 files changed, 75 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 423bd8c..7bc8860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,15 @@ All notable changes to this project will be documented in this file. -## [1.2.6] - 2026-03-16 +## [1.2.7] - 2026-03-16 + +### Added +- **`usgs_earthquakes_feed_new_events` HA event**: The sensor now fires this event on the HA event bus every time new earthquake events are detected. Automations can use `trigger: platform: event / event_type: usgs_earthquakes_feed_new_events` to react instantly. The event payload contains `entry_id`, `count` (number of new events), and `events` (the list of new event dicts). +- **`EVENT_NEW_QUAKES` constant** added to `const.py` to hold the event name. + +### Fixed +- **README**: Updated sensor description to reflect the `latest_events` delta semantics introduced in v1.2.6 (removed references to the old `events` attribute and the "last 50 events" cap). Added full documentation for the new HA event and an example automation. + ### Fixed - **Conceptual error in v1.2.5**: `latest_events` was incorrectly made a cumulative list that grew on every update, always containing all historical events. The correct behaviour is: diff --git a/README.md b/README.md index 8031c3e..dc3d4c8 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,9 @@ - **Maximum Distance** from your location (Radius) - Creates `geo_location` entities for each event. - Includes a special sensor `sensor.usgs_earthquakes_feed_latest` that: - - Stores the last **50** new earthquake events (based on their unique `id`) - - State is the timestamp of the most recent event - - Exposes the full list of stored events via the `events` attribute + - State is the timestamp of the most recent **new** earthquake event + - Exposes new events via the `latest_events` attribute (only events detected in the current update cycle; empty when no new earthquakes have arrived) + - Fires a `usgs_earthquakes_feed_new_events` event on the HA event bus whenever new earthquakes are detected - Includes a `format_events` action that returns earthquake events as formatted text via a response variable --- @@ -112,8 +112,50 @@ Full list: [USGS GeoJSON Feed Documentation](https://earthquake.usgs.gov/earthqu This sensor exposes: -- `state`: Timestamp of the latest event -- `events`: List of the last 50 new earthquakes (stored across restarts) +- `state`: Timestamp of the most recent **new** earthquake event (only changes when new earthquakes are detected) +- `latest_events`: List of new earthquakes detected in the current update cycle, ordered from newest to oldest. Empty when no new earthquakes have arrived since the last update. + +> **How it works:** +> - **First run** (or after HA restart): `latest_events` contains all earthquakes that match your filter criteria. +> - **Subsequent updates with no new earthquakes**: `latest_events` is empty (`[]`) and the sensor state does not change. +> - **New earthquake detected**: `latest_events` contains only the new event(s), the sensor state updates to the newest event's timestamp. + +--- + +## 📣 Event: `usgs_earthquakes_feed_new_events` + +Every time new earthquakes are detected the integration fires this event on the HA event bus. You can use it as an automation trigger: + +```yaml +trigger: + - platform: event + event_type: usgs_earthquakes_feed_new_events +``` + +The event data contains: + +| Field | Description | +|---|---| +| `entry_id` | Config-entry ID of the integration instance | +| `count` | Number of new events detected | +| `events` | List of new earthquake event dicts | + +### Example automation using the event: + +```yaml +automation: + - alias: "Notify on new earthquake" + trigger: + - platform: event + event_type: usgs_earthquakes_feed_new_events + action: + - service: notify.mobile_app_my_phone + data: + title: "🌍 New Earthquake" + message: > + {{ trigger.event.data.count }} new earthquake(s) detected. + Latest: {{ trigger.event.data.events[0].title }} +``` --- @@ -168,9 +210,10 @@ The variable `quake_report.formatted_events` will contain a multiline string wit ## 📓 Notes -- On first setup, **all events** matching the filters are included. -- On updates, only **new events** (based on USGS `id`) are added. -- Sensor shows events in reverse chronological order (newest first). +- On first setup (or after HA restart), **all events** matching the filters are treated as new and included in `latest_events`. +- On subsequent updates, only **new events** (based on USGS `id`) are included in `latest_events`. +- When no new earthquakes are detected, `latest_events` is empty (`[]`) and the sensor state does not change. +- Events in `latest_events` are ordered from newest to oldest. - All magnitude and distance values follow standard units (Mw, km). --- diff --git a/custom_components/usgs_earthquakes_feed/const.py b/custom_components/usgs_earthquakes_feed/const.py index 9187a9d..88e946e 100644 --- a/custom_components/usgs_earthquakes_feed/const.py +++ b/custom_components/usgs_earthquakes_feed/const.py @@ -1,5 +1,8 @@ DOMAIN = "usgs_earthquakes_feed" +# Event fired on the HA event bus whenever new earthquake events are detected +EVENT_NEW_QUAKES = f"{DOMAIN}_new_events" + CONF_RADIUS = "radius" CONF_MINIMUM_MAGNITUDE = "minimum_magnitude" CONF_FEED_TYPE = "feed_type" diff --git a/custom_components/usgs_earthquakes_feed/manifest.json b/custom_components/usgs_earthquakes_feed/manifest.json index e6533b2..b2ca3d4 100644 --- a/custom_components/usgs_earthquakes_feed/manifest.json +++ b/custom_components/usgs_earthquakes_feed/manifest.json @@ -13,5 +13,5 @@ "aio-geojson-usgs-earthquakes==0.3", "aio-geojson-client==0.12" ], - "version": "1.2.6" + "version": "1.2.7" } diff --git a/custom_components/usgs_earthquakes_feed/sensor.py b/custom_components/usgs_earthquakes_feed/sensor.py index 3f6c9bb..2c92ed1 100644 --- a/custom_components/usgs_earthquakes_feed/sensor.py +++ b/custom_components/usgs_earthquakes_feed/sensor.py @@ -14,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.util.dt import as_local -from .const import DOMAIN +from .const import DOMAIN, EVENT_NEW_QUAKES from .helpers import parse_event_time import logging @@ -89,6 +89,16 @@ async def _async_update_events(self) -> None: except (ValueError, AttributeError): _LOGGER.debug("Could not parse native value from event time: %s", self._latest_events[0].get("time")) + # Disparar evento en el bus de HA para que las automatizaciones puedan reaccionar + self.hass.bus.async_fire( + EVENT_NEW_QUAKES, + { + "entry_id": self._entry_id, + "count": len(self._latest_events), + "events": self._latest_events, + }, + ) + _LOGGER.debug( "USGS Quakes Sensor actualizado. Nuevos eventos: %d.", len(filtered_events),