From f91c4104bdf05ed3bfa66f60451dfb59f0635970 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Tue, 11 Apr 2023 10:59:13 -0700 Subject: [PATCH 01/12] feat: add device trackers to GPS location checking --- custom_components/nws_alerts/__init__.py | 35 ++++++--- custom_components/nws_alerts/config_flow.py | 81 ++++++++++++++++++++- custom_components/nws_alerts/const.py | 1 + custom_components/nws_alerts/sensor.py | 6 +- tests/const.py | 2 +- tests/test_config_flow.py | 27 ++++--- tests/test_init.py | 1 + tests/test_sensor.py | 1 - 8 files changed, 126 insertions(+), 28 deletions(-) diff --git a/custom_components/nws_alerts/__init__.py b/custom_components/nws_alerts/__init__.py index 37173a2..9825c0f 100644 --- a/custom_components/nws_alerts/__init__.py +++ b/custom_components/nws_alerts/__init__.py @@ -19,6 +19,7 @@ CONF_GPS_LOC, CONF_INTERVAL, CONF_TIMEOUT, + CONF_TRACKER, CONF_ZONE_ID, COORDINATOR, DEFAULT_INTERVAL, @@ -48,9 +49,11 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b ent_reg = async_get(hass) for entity in async_entries_for_config_entry(ent_reg, config_entry.entry_id): - ent_reg.async_update_entity(entity.entity_id, new_unique_id=config_entry.entry_id) + ent_reg.async_update_entity( + entity.entity_id, new_unique_id=config_entry.entry_id + ) - config_entry.add_update_listener(update_listener) + config_entry.add_update_listener(update_listener) # Setup the data coordinator coordinator = AlertsDataUpdateCoordinator( @@ -89,7 +92,7 @@ async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry): if config_entry.data == config_entry.options: _LOGGER.debug("No changes detected not reloading sensors.") return - + new_data = config_entry.options.copy() hass.config_entries.async_update_entry( entry=config_entry, @@ -139,24 +142,35 @@ def __init__(self, hass, config, the_timeout: int, interval: int): async def _async_update_data(self): """Fetch data""" + coords = None + if CONF_TRACKER in self.config: + coords = await self._get_tracker_gps async with timeout(self.timeout): try: - data = await update_alerts(self.config) + data = await update_alerts(self.config, coords) except Exception as error: raise UpdateFailed(error) from error return data + async def _get_tracker_gps(self): + """Return device tracker GPS data.""" + tracker = self.config[CONF_TRACKER] + entity = self.hass.get(tracker) + if entity and "source_type" in entity.attributes: + return f"{entity.attributes['latitude']},{entity.attributes['longitude']}" + return None -async def update_alerts(config) -> dict: + +async def update_alerts(config, coords) -> dict: """Fetch new state data for the sensor. This is the only method that should fetch new data for Home Assistant. """ - data = await async_get_state(config) + data = await async_get_state(config, coords) return data -async def async_get_state(config) -> dict: +async def async_get_state(config, coords) -> dict: """Query API for status.""" zone_id = "" @@ -169,8 +183,11 @@ async def async_get_state(config) -> dict: if CONF_ZONE_ID in config: zone_id = config[CONF_ZONE_ID] _LOGGER.debug("getting state for %s from %s" % (zone_id, url)) - elif CONF_GPS_LOC in config: - gps_loc = config[CONF_GPS_LOC] + elif CONF_GPS_LOC in config or CONF_TRACKER in config: + if coords: + gps_loc = coords + else: + gps_loc = config[CONF_GPS_LOC] _LOGGER.debug("getting state for %s from %s" % (gps_loc, url)) async with aiohttp.ClientSession() as session: diff --git a/custom_components/nws_alerts/config_flow.py b/custom_components/nws_alerts/config_flow.py index 931d6a0..17b3b4b 100644 --- a/custom_components/nws_alerts/config_flow.py +++ b/custom_components/nws_alerts/config_flow.py @@ -2,13 +2,14 @@ from __future__ import annotations import logging -from typing import Any +from typing import Any, List import aiohttp import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.device_tracker import DOMAIN as TRACKER_DOMAIN from homeassistant.const import CONF_NAME -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.data_entry_flow import FlowResult from .const import ( @@ -16,6 +17,7 @@ CONF_GPS_LOC, CONF_INTERVAL, CONF_TIMEOUT, + CONF_TRACKER, CONF_ZONE_ID, DEFAULT_INTERVAL, DEFAULT_NAME, @@ -29,7 +31,8 @@ JSON_ID = "id" _LOGGER = logging.getLogger(__name__) -MENU_OPTIONS = ["zone", "gps_loc"] +MENU_OPTIONS = ["zone", "gps"] +MENU_GPS = ["gps_loc", "gps_tracker"] def _get_schema_zone(hass: Any, user_input: list, default_dict: list) -> Any: @@ -70,6 +73,48 @@ def _get_default(key): ) +def _get_schema_tracker(hass: Any, user_input: list, default_dict: list) -> Any: + """Gets a schema using the default_dict as a backup.""" + if user_input is None: + user_input = {} + + def _get_default(key): + """Gets default value for key.""" + return user_input.get(key, default_dict.get(key)) + + return vol.Schema( + { + vol.Required(CONF_TRACKER, default=_get_default(CONF_TRACKER)): vol.In( + _get_entities(hass, TRACKER_DOMAIN) + ), + vol.Optional(CONF_NAME, default=_get_default(CONF_NAME)): str, + vol.Optional(CONF_INTERVAL, default=_get_default(CONF_INTERVAL)): int, + vol.Optional(CONF_TIMEOUT, default=_get_default(CONF_TIMEOUT)): int, + } + ) + + +def _get_entities( + hass: HomeAssistant, + domain: str, + search: List[str] = None, + extra_entities: List[str] = None, +) -> List[str]: + data = [] + if domain not in hass.data: + return data + + for entity in hass.data[domain].entities: + if search is not None and not any(map(entity.entity_id.__contains__, search)): + continue + data.append(entity.entity_id) + + if extra_entities: + data.extend(extra_entities) + + return data + + async def _get_zone_list(self) -> list | None: """Return list of zone by lat/lon""" @@ -127,6 +172,36 @@ async def async_step_user( """Handle the flow initialized by the user.""" return self.async_show_menu(step_id="user", menu_options=MENU_OPTIONS) + async def async_step_gps( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the flow initialized by the user.""" + return self.async_show_menu(step_id="gps", menu_options=MENU_GPS) + + async def async_step_gps_tracker(self, user_input={}): + """Handle a flow for device trackers.""" + self._errors = {} + if user_input is not None: + self._data.update(user_input) + return self.async_create_entry(title=self._data[CONF_NAME], data=self._data) + return await self._show_config_gps_tracker(user_input) + + async def _show_config_gps_tracker(self, user_input): + """Show the configuration form to edit location data.""" + + # Defaults + defaults = { + CONF_NAME: DEFAULT_NAME, + CONF_INTERVAL: DEFAULT_INTERVAL, + CONF_TIMEOUT: DEFAULT_TIMEOUT, + } + + return self.async_show_form( + step_id="gps_tracker", + data_schema=_get_schema_tracker(self.hass, user_input, defaults), + errors=self._errors, + ) + async def async_step_gps_loc(self, user_input={}): """Handle a flow initialized by the user.""" lat = self.hass.config.latitude diff --git a/custom_components/nws_alerts/const.py b/custom_components/nws_alerts/const.py index 6086f83..489cb93 100644 --- a/custom_components/nws_alerts/const.py +++ b/custom_components/nws_alerts/const.py @@ -7,6 +7,7 @@ CONF_INTERVAL = "interval" CONF_ZONE_ID = "zone_id" CONF_GPS_LOC = "gps_loc" +CONF_TRACKER = "tracker" # Defaults DEFAULT_ICON = "mdi:alert" diff --git a/custom_components/nws_alerts/sensor.py b/custom_components/nws_alerts/sensor.py index 1461157..b7252ca 100644 --- a/custom_components/nws_alerts/sensor.py +++ b/custom_components/nws_alerts/sensor.py @@ -18,6 +18,7 @@ CONF_GPS_LOC, CONF_INTERVAL, CONF_TIMEOUT, + CONF_TRACKER, CONF_ZONE_ID, COORDINATOR, DEFAULT_ICON, @@ -40,6 +41,7 @@ { vol.Optional(CONF_ZONE_ID): cv.string, vol.Optional(CONF_GPS_LOC): cv.string, + vol.Optional(CONF_TRACKER): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_INTERVAL, default=DEFAULT_INTERVAL): int, vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): int, @@ -63,8 +65,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= config.entry_id = slugify(f"{config.get(CONF_ZONE_ID)}") elif CONF_GPS_LOC in config: config.entry_id = slugify(f"{config.get(CONF_GPS_LOC)}") - elif CONF_GPS_LOC and CONF_ZONE_ID not in config: - raise ValueError("GPS or Zone needs to be configured.") + elif all(key in config for key in [CONF_GPS_LOC, CONF_ZONE_ID, CONF_TRACKER]): + raise ValueError("GPS, Zone or Device Tracker needs to be configured.") config.data = config # Setup the data coordinator diff --git a/tests/const.py b/tests/const.py index cda761e..de4797a 100644 --- a/tests/const.py +++ b/tests/const.py @@ -3,4 +3,4 @@ CONFIG_DATA = {"name": "NWS Alerts", "zone_id": "AZZ540,AZC013"} CONFIG_DATA_2 = {"name": "NWS Alerts YAML", "zone_id": "AZZ540"} CONFIG_DATA_3 = {"name": "NWS Alerts", "gps_loc": "123,-456"} -CONFIG_DATA_BAD = {"name": "NWS Alerts" } \ No newline at end of file +CONFIG_DATA_BAD = {"name": "NWS Alerts"} diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py index f02886e..aa28d24 100644 --- a/tests/test_config_flow.py +++ b/tests/test_config_flow.py @@ -11,6 +11,7 @@ pytestmark = pytest.mark.asyncio + @pytest.mark.parametrize( "input,step_id,title,data", [ @@ -42,9 +43,8 @@ async def test_form_zone( """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "custom_components.nws_alerts.config_flow._get_zone_list", - return_value=None): - + "custom_components.nws_alerts.config_flow._get_zone_list", return_value=None + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -55,9 +55,8 @@ async def test_form_zone( "custom_components.nws_alerts.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "custom_components.nws_alerts.config_flow._get_zone_list", - return_value=None): - + "custom_components.nws_alerts.config_flow._get_zone_list", return_value=None + ): result = await hass.config_entries.flow.async_configure( result["flow_id"], {"next_step_id": "zone"} ) @@ -108,9 +107,8 @@ async def test_form_gps( """Test we get the form.""" await setup.async_setup_component(hass, "persistent_notification", {}) with patch( - "custom_components.nws_alerts.config_flow._get_zone_list", - return_value=None): - + "custom_components.nws_alerts.config_flow._get_zone_list", return_value=None + ): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -121,13 +119,17 @@ async def test_form_gps( "custom_components.nws_alerts.async_setup_entry", return_value=True, ) as mock_setup_entry, patch( - "custom_components.nws_alerts.config_flow._get_zone_list", - return_value=None): + "custom_components.nws_alerts.config_flow._get_zone_list", return_value=None + ): + result = await hass.config_entries.flow.async_configure( + result["flow_id"], {"next_step_id": "gps"} + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.MENU result = await hass.config_entries.flow.async_configure( result["flow_id"], {"next_step_id": "gps_loc"} ) - await hass.async_block_till_done() assert result["type"] == FlowResultType.FORM @@ -142,6 +144,7 @@ async def test_form_gps( await hass.async_block_till_done() assert len(mock_setup_entry.mock_calls) == 1 + # @pytest.mark.parametrize( # "user_input", # [ diff --git a/tests/test_init.py b/tests/test_init.py index 38be362..d40fb74 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -13,6 +13,7 @@ pytestmark = pytest.mark.asyncio + async def test_setup_entry( hass, ): diff --git a/tests/test_sensor.py b/tests/test_sensor.py index e1f66d3..acb412e 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -14,7 +14,6 @@ async def test_sensor(hass): - entry = MockConfigEntry( domain=DOMAIN, title="NWS Alerts", From 61dc76819a4992c214b9a91b6dfbb65dc0996e1b Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 12 Apr 2023 06:38:05 -0700 Subject: [PATCH 02/12] add menu items to config flow --- .../nws_alerts/translations/en.json | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/custom_components/nws_alerts/translations/en.json b/custom_components/nws_alerts/translations/en.json index 00eb0cc..de369cb 100644 --- a/custom_components/nws_alerts/translations/en.json +++ b/custom_components/nws_alerts/translations/en.json @@ -5,9 +5,25 @@ "description": "Please select your NWS lookup method.\n\nFor a brief explanation of the advantages and disadvantages of each method please see [here](https://github.com/finity69x2/nws_alerts/blob/master/lookup_options.md).\n\nIf you don't know which is better for your situation, you should probably select the Zone ID version.", "menu_options": { "zone": "Zone ID (more generalized location - includes County ID if applicable)", - "gps_loc": "GPS Location (precise location)" + "gps": "GPS Location (precise location)" } }, + "gps": { + "description": "Please choose a GPS method.", + "menu_options": { + "gps_loc": "Using your Home Assistant location", + "gps_tracker": "Using a device tracker" + } + }, + "gps_tracker": { + "description": "Select the device you'd like to track for alerts.", + "data": { + "name": "Friendly Name", + "tracker": "Device to track", + "interval": "Update Interval (in minutes)", + "timeout":"Update Timeout (in seconds)" + } + }, "gps_loc": { "description": "Please enter your latitude and longitude, by default your coordinates from Home Assistant are used.", "data": { @@ -34,9 +50,25 @@ "description": "Please select your NWS lookup method.\n\nFor a brief explanation of the advantages and disadvantages of each method please see [here](https://github.com/finity69x2/nws_alerts/blob/master/lookup_options.md).\n\nIf you don't know which is better for your situation, you should probably select the Zone ID version.", "menu_options": { "zone": "Zone ID (more generalized location - includes County ID if applicable)", - "gps_loc": "GPS Location (precise location)" + "gps": "GPS Location (precise location)" } }, + "gps": { + "description": "Please choose a GPS method.", + "menu_options": { + "gps_loc": "Using your Home Assistant location", + "gps_tracker": "Using a device tracker" + } + }, + "gps_tracker": { + "description": "Select the device you'd like to track for alerts.", + "data": { + "name": "Friendly Name", + "tracker": "Device to track", + "interval": "Update Interval (in minutes)", + "timeout":"Update Timeout (in seconds)" + } + }, "gps_loc": { "description": "Please enter your latitude and longitude, by default your coordinates from Home Assistant are used.", "data": { From a8041cb58a68c31e97b6c50f1d961d1dc1e26156 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 12 Apr 2023 06:39:51 -0700 Subject: [PATCH 03/12] sort manifest.json file --- custom_components/nws_alerts/manifest.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/nws_alerts/manifest.json b/custom_components/nws_alerts/manifest.json index 3ee5cc4..384ad50 100644 --- a/custom_components/nws_alerts/manifest.json +++ b/custom_components/nws_alerts/manifest.json @@ -1,12 +1,12 @@ { "domain": "nws_alerts", "name": "NWS Alerts", - "version": "2.8", - "documentation": "https://github.com/finity69x2/nws_alerts/", - "issue_tracker": "https://github.com/finity69x2/nws_alerts/issues", - "dependencies": [], "codeowners": ["@finity69x2"], "config_flow": true, + "documentation": "https://github.com/finity69x2/nws_alerts/", + "dependencies": [], + "iot_class": "cloud_polling", + "issue_tracker": "https://github.com/finity69x2/nws_alerts/issues", "requirements": [], - "iot_class": "cloud_polling" + "version": "2.8" } From 41b9f2b6387eab16b0022a80f54fd81cdb781b52 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Thu, 13 Apr 2023 15:19:36 -0700 Subject: [PATCH 04/12] supply "none" if no trackers are found --- custom_components/nws_alerts/config_flow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/custom_components/nws_alerts/config_flow.py b/custom_components/nws_alerts/config_flow.py index 17b3b4b..3a434ae 100644 --- a/custom_components/nws_alerts/config_flow.py +++ b/custom_components/nws_alerts/config_flow.py @@ -78,13 +78,13 @@ def _get_schema_tracker(hass: Any, user_input: list, default_dict: list) -> Any: if user_input is None: user_input = {} - def _get_default(key): + def _get_default(key: str, fallback_default: Any = None) -> None: """Gets default value for key.""" - return user_input.get(key, default_dict.get(key)) + return user_input.get(key, default_dict.get(key, fallback_default)) return vol.Schema( { - vol.Required(CONF_TRACKER, default=_get_default(CONF_TRACKER)): vol.In( + vol.Required(CONF_TRACKER, default=_get_default(CONF_TRACKER, "(none)")): vol.In( _get_entities(hass, TRACKER_DOMAIN) ), vol.Optional(CONF_NAME, default=_get_default(CONF_NAME)): str, @@ -100,7 +100,7 @@ def _get_entities( search: List[str] = None, extra_entities: List[str] = None, ) -> List[str]: - data = [] + data = ["(none)"] if domain not in hass.data: return data From 8e3d4e535e0fb9364289ce0a81ecd0ba786f77df Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Thu, 13 Apr 2023 15:52:33 -0700 Subject: [PATCH 05/12] finish up config flow for options --- custom_components/nws_alerts/config_flow.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/custom_components/nws_alerts/config_flow.py b/custom_components/nws_alerts/config_flow.py index 3a434ae..e2741cb 100644 --- a/custom_components/nws_alerts/config_flow.py +++ b/custom_components/nws_alerts/config_flow.py @@ -288,6 +288,15 @@ async def async_step_gps_loc(self, user_input={}): self._data.update(user_input) return self.async_create_entry(title="", data=self._data) return await self._show_options_form(user_input) + + async def async_step_gps_tracker(self, user_input={}): + """Handle a flow initialized by the user.""" + self._errors = {} + + if user_input is not None: + self._data.update(user_input) + return self.async_create_entry(title="", data=self._data) + return await self._show_options_form(user_input) async def async_step_zone(self, user_input={}): """Handle a flow initialized by the user.""" @@ -313,3 +322,9 @@ async def _show_options_form(self, user_input): data_schema=_get_schema_zone(self.hass, user_input, self._data), errors=self._errors, ) + elif CONF_TRACKER in self.config.data: + return self.async_show_form( + step_id="gps_tracker", + data_schema=_get_schema_tracker(self.hass, user_input, self._data), + errors=self._errors, + ) From 87c12bbb56dd23626826f1fbce777cfc804e7841 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Fri, 14 Apr 2023 13:53:13 -0700 Subject: [PATCH 06/12] fix call to _get_tracker_gps --- custom_components/nws_alerts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/nws_alerts/__init__.py b/custom_components/nws_alerts/__init__.py index 9825c0f..dc3e1d9 100644 --- a/custom_components/nws_alerts/__init__.py +++ b/custom_components/nws_alerts/__init__.py @@ -144,7 +144,7 @@ async def _async_update_data(self): """Fetch data""" coords = None if CONF_TRACKER in self.config: - coords = await self._get_tracker_gps + coords = await self._get_tracker_gps() async with timeout(self.timeout): try: data = await update_alerts(self.config, coords) From 6d0631d7a53f769fde1330b3f93a94439c6c741f Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Mon, 17 Apr 2023 13:23:45 -0700 Subject: [PATCH 07/12] fix update coordinator for tracker --- custom_components/nws_alerts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/nws_alerts/__init__.py b/custom_components/nws_alerts/__init__.py index dc3e1d9..02f0039 100644 --- a/custom_components/nws_alerts/__init__.py +++ b/custom_components/nws_alerts/__init__.py @@ -155,7 +155,7 @@ async def _async_update_data(self): async def _get_tracker_gps(self): """Return device tracker GPS data.""" tracker = self.config[CONF_TRACKER] - entity = self.hass.get(tracker) + entity = self.hass.states.get(tracker) if entity and "source_type" in entity.attributes: return f"{entity.attributes['latitude']},{entity.attributes['longitude']}" return None From 4192c58cc43b6c94bc83d48967f187fa13a21d44 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Sun, 23 Apr 2023 16:37:57 -0700 Subject: [PATCH 08/12] make sure values are always returned --- custom_components/nws_alerts/__init__.py | 53 +++++++++++---------- custom_components/nws_alerts/config_flow.py | 12 ++--- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/custom_components/nws_alerts/__init__.py b/custom_components/nws_alerts/__init__.py index 02f0039..d00234e 100644 --- a/custom_components/nws_alerts/__init__.py +++ b/custom_components/nws_alerts/__init__.py @@ -176,7 +176,17 @@ async def async_get_state(config, coords) -> dict: zone_id = "" gps_loc = "" url = "%s/alerts/active/count" % API_ENDPOINT - values = {} + values = { + "state": 0, + "event": None, + "event_id": None, + "message_type": None, + "event_status": None, + "event_severity": None, + "event_expires": None, + "display_desc": None, + "spoken_desc": None, + } headers = {"User-Agent": USER_AGENT, "Accept": "application/ld+json"} data = None @@ -184,7 +194,7 @@ async def async_get_state(config, coords) -> dict: zone_id = config[CONF_ZONE_ID] _LOGGER.debug("getting state for %s from %s" % (zone_id, url)) elif CONF_GPS_LOC in config or CONF_TRACKER in config: - if coords: + if coords is not None: gps_loc = coords else: gps_loc = config[CONF_GPS_LOC] @@ -194,20 +204,11 @@ async def async_get_state(config, coords) -> dict: async with session.get(url, headers=headers) as r: if r.status == 200: data = await r.json() + else: + _LOGGER.error("Problem updating NWS data: (%s) - %s", r.status, r.body) if data is not None: # Reset values before reassigning - values = { - "state": 0, - "event": None, - "event_id": None, - "message_type": None, - "event_status": None, - "event_severity": None, - "event_expires": None, - "display_desc": None, - "spoken_desc": None, - } if "zones" in data and zone_id != "": for zone in zone_id.split(","): if zone in data["zones"]: @@ -223,7 +224,17 @@ async def async_get_alerts(zone_id: str = "", gps_loc: str = "") -> dict: """Query API for Alerts.""" url = "" - values = {} + values = { + "state": 0, + "event": None, + "event_id": None, + "message_type": None, + "event_status": None, + "event_severity": None, + "event_expires": None, + "display_desc": None, + "spoken_desc": None, + } headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"} data = None @@ -238,6 +249,8 @@ async def async_get_alerts(zone_id: str = "", gps_loc: str = "") -> dict: async with session.get(url, headers=headers) as r: if r.status == 200: data = await r.json() + else: + _LOGGER.error("Problem updating NWS data: (%s) - %s", r.status, r.body) if data is not None: events = [] @@ -343,17 +356,5 @@ async def async_get_alerts(zone_id: str = "", gps_loc: str = "") -> dict: values["event_expires"] = event_expires values["display_desc"] = display_desc values["spoken_desc"] = spoken_desc - else: - values = { - "state": 0, - "event": None, - "event_id": None, - "message_type": None, - "event_status": None, - "event_severity": None, - "event_expires": None, - "display_desc": None, - "spoken_desc": None, - } return values diff --git a/custom_components/nws_alerts/config_flow.py b/custom_components/nws_alerts/config_flow.py index e2741cb..eb70103 100644 --- a/custom_components/nws_alerts/config_flow.py +++ b/custom_components/nws_alerts/config_flow.py @@ -84,9 +84,9 @@ def _get_default(key: str, fallback_default: Any = None) -> None: return vol.Schema( { - vol.Required(CONF_TRACKER, default=_get_default(CONF_TRACKER, "(none)")): vol.In( - _get_entities(hass, TRACKER_DOMAIN) - ), + vol.Required( + CONF_TRACKER, default=_get_default(CONF_TRACKER, "(none)") + ): vol.In(_get_entities(hass, TRACKER_DOMAIN)), vol.Optional(CONF_NAME, default=_get_default(CONF_NAME)): str, vol.Optional(CONF_INTERVAL, default=_get_default(CONF_INTERVAL)): int, vol.Optional(CONF_TIMEOUT, default=_get_default(CONF_TIMEOUT)): int, @@ -288,7 +288,7 @@ async def async_step_gps_loc(self, user_input={}): self._data.update(user_input) return self.async_create_entry(title="", data=self._data) return await self._show_options_form(user_input) - + async def async_step_gps_tracker(self, user_input={}): """Handle a flow initialized by the user.""" self._errors = {} @@ -296,7 +296,7 @@ async def async_step_gps_tracker(self, user_input={}): if user_input is not None: self._data.update(user_input) return self.async_create_entry(title="", data=self._data) - return await self._show_options_form(user_input) + return await self._show_options_form(user_input) async def async_step_zone(self, user_input={}): """Handle a flow initialized by the user.""" @@ -327,4 +327,4 @@ async def _show_options_form(self, user_input): step_id="gps_tracker", data_schema=_get_schema_tracker(self.hass, user_input, self._data), errors=self._errors, - ) + ) From 257518af2b823147a3d6d93f4f1fd7724374d1bb Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Mon, 24 Apr 2023 06:28:13 -0700 Subject: [PATCH 09/12] sanitize manual gps coords --- custom_components/nws_alerts/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/nws_alerts/__init__.py b/custom_components/nws_alerts/__init__.py index d00234e..246de3e 100644 --- a/custom_components/nws_alerts/__init__.py +++ b/custom_components/nws_alerts/__init__.py @@ -197,7 +197,7 @@ async def async_get_state(config, coords) -> dict: if coords is not None: gps_loc = coords else: - gps_loc = config[CONF_GPS_LOC] + gps_loc = config[CONF_GPS_LOC].replace(" ", "") _LOGGER.debug("getting state for %s from %s" % (gps_loc, url)) async with aiohttp.ClientSession() as session: From fb1db3851896ee969af9e4aecf5d1d441496f076 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Mon, 24 Apr 2023 06:33:27 -0700 Subject: [PATCH 10/12] sanatize configuration --- custom_components/nws_alerts/__init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/custom_components/nws_alerts/__init__.py b/custom_components/nws_alerts/__init__.py index 246de3e..4d8514e 100644 --- a/custom_components/nws_alerts/__init__.py +++ b/custom_components/nws_alerts/__init__.py @@ -53,6 +53,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b entity.entity_id, new_unique_id=config_entry.entry_id ) + updated_config = config_entry.data.copy() + + # Strip spaces from manually entered GPS locations + if CONF_GPS_LOC in updated_config: + updated_config[CONF_GPS_LOC].replace(" ", "") + + if updated_config != config_entry.data: + hass.config_entries.async_update_entry(config_entry, data=updated_config) + config_entry.add_update_listener(update_listener) # Setup the data coordinator From 795f4d768182bc5e05e132b55418ea0358f6ecf4 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 26 Apr 2023 06:56:34 -0700 Subject: [PATCH 11/12] missed setup for CONF_TRACKER for yaml config --- custom_components/nws_alerts/sensor.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/custom_components/nws_alerts/sensor.py b/custom_components/nws_alerts/sensor.py index b7252ca..f3ba705 100644 --- a/custom_components/nws_alerts/sensor.py +++ b/custom_components/nws_alerts/sensor.py @@ -57,14 +57,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= config.entry_id = slugify(f"{config.get(CONF_ZONE_ID)}") elif CONF_GPS_LOC in config: config.entry_id = slugify(f"{config.get(CONF_GPS_LOC)}") - elif CONF_GPS_LOC and CONF_ZONE_ID not in config: - raise ValueError("GPS or Zone needs to be configured.") + elif CONF_TRACKER in config: + config.entry_id = slugify(f"{config.get(CONF_TRACKER)}") + elif all(key in config for key in [CONF_GPS_LOC, CONF_ZONE_ID, CONF_TRACKER]): + raise ValueError("GPS, Zone or Device Tracker needs to be configured.") config.data = config else: if CONF_ZONE_ID in config: config.entry_id = slugify(f"{config.get(CONF_ZONE_ID)}") elif CONF_GPS_LOC in config: config.entry_id = slugify(f"{config.get(CONF_GPS_LOC)}") + elif CONF_TRACKER in config: + config.entry_id = slugify(f"{config.get(CONF_TRACKER)}") elif all(key in config for key in [CONF_GPS_LOC, CONF_ZONE_ID, CONF_TRACKER]): raise ValueError("GPS, Zone or Device Tracker needs to be configured.") config.data = config From 826b44bc73a820745da62ea9648055b19b8ce672 Mon Sep 17 00:00:00 2001 From: "firstof9@gmail.com" Date: Wed, 26 Apr 2023 14:49:30 -0700 Subject: [PATCH 12/12] clean up logic --- custom_components/nws_alerts/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/nws_alerts/sensor.py b/custom_components/nws_alerts/sensor.py index f3ba705..c38916d 100644 --- a/custom_components/nws_alerts/sensor.py +++ b/custom_components/nws_alerts/sensor.py @@ -59,7 +59,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= config.entry_id = slugify(f"{config.get(CONF_GPS_LOC)}") elif CONF_TRACKER in config: config.entry_id = slugify(f"{config.get(CONF_TRACKER)}") - elif all(key in config for key in [CONF_GPS_LOC, CONF_ZONE_ID, CONF_TRACKER]): + else: raise ValueError("GPS, Zone or Device Tracker needs to be configured.") config.data = config else: @@ -69,7 +69,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= config.entry_id = slugify(f"{config.get(CONF_GPS_LOC)}") elif CONF_TRACKER in config: config.entry_id = slugify(f"{config.get(CONF_TRACKER)}") - elif all(key in config for key in [CONF_GPS_LOC, CONF_ZONE_ID, CONF_TRACKER]): + else: raise ValueError("GPS, Zone or Device Tracker needs to be configured.") config.data = config