Skip to content

Commit

Permalink
Merge pull request #84 from finity69x2/dev
Browse files Browse the repository at this point in the history
Complete rewrite of the attributes structure of the alerts.
  • Loading branch information
finity69x2 authored Aug 4, 2024
2 parents 3a0b041 + 52ce031 commit e3c6497
Show file tree
Hide file tree
Showing 9 changed files with 733 additions and 623 deletions.
57 changes: 11 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Alerts from the US National Weather Service (nws_alerts)

## BREAKING CHANGES IN V5.0

This is a pretty much complete rewrite of the integration to better organize the data for the alerts. All of the data provided by the older versions is still included but it's laid out very differently and as such none of the associated automations package or dashboard examples will continue to function as there currently are.

I have done extensive testing to ensure that the new updated package examplews work as desired but of course I caouldn't test every situation.

<b>Use at your own risk!</b>

## Description:

An updated version of the nws_alerts custom integration for Home Assistant originally found at github.com/eracknaphobia/nws_custom_component

This integration retrieves updated weather alerts every minute from the US NWS API (by default but it can be changed in the config options).
Expand All @@ -12,8 +22,6 @@ The sensor that is created is used in my "NWS Alerts" package: https://github.co

You can also display the generated alerts in your frontend. For example usage see: https://github.com/finity69x2/nws_alerts/blob/master/lovelace/alerts_tab

(note: this frontend example uses a custom card but it's not necessary for it's use in the frontend. it's only an example and how I currently use it in my HA)

## Installation:

<b>Manually:</b>
Expand All @@ -38,8 +46,6 @@ After installing the integration you can then configure it using the instruction

<b>NOTE: As of HA versoin 2024.5.x the yaml configuration option is broken. I don't know if it will ever be fixed so the only viable config option is via the UI</b>

<s>There are two ways to configure this integration.</s>

<b>You can configure the integration via the "Configuration->Integrations" section of the Home Assistant UI:</b>

Click on "+ Add Integration" buuton in the bottom right corner.
Expand All @@ -56,45 +62,4 @@ https://github.com/finity69x2/nws_alerts/blob/master/lookup_options.md

If you select the "Using a device tracker" option under the "GPS Location" option then HA will use the GPS coordinates provided by that tracker to query for alerts so you should follow the same recommendations for using GPS coordinates when using that option.

<s><b>Or manually via an entry in your configuration.yaml file:</b>

To create a sensor instance add the following configuration to your sensor definitions using the zone_id found above:

```
- platform: nws_alerts
zone_id: 'PAC049'
```

or enter comma separated values for multiple zones:

```
- platform: nws_alerts
zone_id: 'PAC049,WVC031'
```

or by entering in specific GPS coordinates:

```
- platform: nws_alerts
gps_loc: 39.52, -119.81
```

or, finally, by adding a device_tracker entity:

```
- platform: nws_alerts
tracker: 'device_tracker.your_tracker_entity'
```
</s>

After you restart Home Assistant then you should have a new sensor called "sensor.nws_alerts" in your system.

You can overide the sensor default name ("sensor.nws_alerts") to one of your choosing by setting the "name:" option:

```
- platform: nws_alerts
zone_id: 'INZ009,INC033'
name: My NWS Alerts Sensor
```

Using the configuration example above the sensor will then be called "sensor.my_nws_alerts_sensor".
After you restart Home Assistant then you should have a new sensor (by default) called "sensor.nws_alerts" in your system.
147 changes: 32 additions & 115 deletions custom_components/nws_alerts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
""" NWS Alerts """

import hashlib
import logging
import uuid
from datetime import timedelta

import aiohttp
Expand Down Expand Up @@ -156,6 +158,7 @@ async def _async_update_data(self):
data = await update_alerts(self.config, coords)
except Exception as error:
raise UpdateFailed(error) from error
_LOGGER.debug("Data: %s", data)
return data

async def _get_tracker_gps(self):
Expand Down Expand Up @@ -231,18 +234,7 @@ async def async_get_alerts(zone_id: str = "", gps_loc: str = "") -> dict:
"""Query API for Alerts."""

url = ""
values = {
"state": 0,
"event": None,
"event_id": None,
"message_type": None,
"event_status": None,
"event_severity": None,
"event_onset": None,
"event_expires": None,
"display_desc": None,
"spoken_desc": None,
}
alerts = {}
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
data = None

Expand All @@ -261,117 +253,42 @@ async def async_get_alerts(zone_id: str = "", gps_loc: str = "") -> dict:
_LOGGER.error("Problem updating NWS data: (%s) - %s", r.status, r.body)

if data is not None:
events = []
headlines = []
event_id = ""
message_type = ""
event_status = ""
event_severity = ""
event_onset = ""
event_expires = ""
display_desc = ""
spoken_desc = ""
features = data["features"]
alert_list = []
for alert in features:
event = alert["properties"]["event"]
if "NWSheadline" in alert["properties"]["parameters"]:
headline = alert["properties"]["parameters"]["NWSheadline"][0]
else:
headline = event

id = alert["id"]
type = alert["properties"]["messageType"]
status = alert["properties"]["status"]
description = alert["properties"]["description"]
instruction = alert["properties"]["instruction"]
severity = alert["properties"]["severity"]
certainty = alert["properties"]["certainty"]
onset = alert["properties"]["onset"]
expires = alert["properties"]["expires"]

# if event in events:
# continue

events.append(event)
headlines.append(headline)

if display_desc != "":
display_desc += "\n\n-\n\n"

display_desc += (
"\n>\nHeadline: %s\nStatus: %s\nMessage Type: %s\nSeverity: %s\nCertainty: %s\nOnset: %s\nExpires: %s\nDescription: %s\nInstruction: %s"
% (
headline,
status,
type,
severity,
certainty,
onset,
expires,
description,
instruction,
)
)

if event_id != "":
event_id += " - "

event_id += id

if message_type != "":
message_type += " - "

message_type += type

if event_status != "":
event_status += " - "

event_status += status
tmp_dict = {}

if event_severity != "":
event_severity += " - "
# Generate stable Alert ID
id = await generate_id(alert["id"])

event_severity += severity
tmp_dict["Event"] = alert["properties"]["event"]
tmp_dict["ID"] = id
tmp_dict["URL"] = alert["id"]

if event_onset != "":
event_onset += " - "

event_onset += onset

if event_expires != "":
event_expires += " - "

event_expires += expires
event = alert["properties"]["event"]
if "NWSheadline" in alert["properties"]["parameters"]:
tmp_dict["Headline"] = alert["properties"]["parameters"]["NWSheadline"][0]
else:
tmp_dict["Headline"] = event

tmp_dict["Type"] = alert["properties"]["messageType"]
tmp_dict["Status"] = alert["properties"]["status"]
tmp_dict["Severity"] = alert["properties"]["severity"]
tmp_dict["Certainty"] = alert["properties"]["certainty"]
tmp_dict["Onset"] = alert["properties"]["onset"]
tmp_dict["Expires"] = alert["properties"]["expires"]
tmp_dict["Description"] = alert["properties"]["description"]
tmp_dict["Instruction"] = alert["properties"]["instruction"]

if headlines:
num_headlines = len(headlines)
i = 0
for headline in headlines:
i += 1
if spoken_desc != "":
if i == num_headlines:
spoken_desc += "\n\n-\n\n"
else:
spoken_desc += "\n\n-\n\n"
alert_list.append(tmp_dict)

spoken_desc += headline
alerts["state"] = len(features)
alerts["alerts"] = alert_list

if len(events) > 0:
event_str = ""
for item in events:
if event_str != "":
event_str += " - "
event_str += item
return alerts

values["state"] = len(events)
values["event"] = event_str
values["event_id"] = event_id
values["message_type"] = message_type
values["event_status"] = event_status
values["event_severity"] = event_severity
values["event_onset"] = event_onset
values["event_expires"] = event_expires
values["display_desc"] = display_desc
values["spoken_desc"] = spoken_desc

return values
async def generate_id(val: str) -> str:
hex_string = hashlib.md5(val.encode("UTF-8")).hexdigest()
return str(uuid.UUID(hex=hex_string))
1 change: 1 addition & 0 deletions custom_components/nws_alerts/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Adds config flow for NWS Alerts."""

from __future__ import annotations

import logging
Expand Down
2 changes: 1 addition & 1 deletion custom_components/nws_alerts/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/finity69x2/nws_alerts/issues",
"requirements": [],
"version": "4.1"
"version": "5.0"
}
69 changes: 5 additions & 64 deletions custom_components/nws_alerts/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,58 +37,6 @@

_LOGGER = logging.getLogger(__name__)

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
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,
}
)


async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Configuration from yaml"""
if DOMAIN not in hass.data.keys():
hass.data.setdefault(DOMAIN, {})
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)}")
else:
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)}")
else:
raise ValueError("GPS, Zone or Device Tracker needs to be configured.")
config.data = config

# Setup the data coordinator
coordinator = AlertsDataUpdateCoordinator(
hass,
config,
config[CONF_TIMEOUT],
config[CONF_INTERVAL],
)

# Fetch initial data so we have data when entities subscribe
await coordinator.async_refresh()

hass.data[DOMAIN][config.entry_id] = {
COORDINATOR: coordinator,
}
async_add_entities([NWSAlertSensor(hass, config)], True)


async def async_setup_entry(hass, entry, async_add_entities):
"""Setup the sensor platform."""
Expand Down Expand Up @@ -124,12 +72,12 @@ def icon(self):
return self._icon

@property
def state(self):
def state(self) -> int | None:
"""Return the state of the sensor."""
if self.coordinator.data is None:
return None
elif "state" in self.coordinator.data.keys():
return self.coordinator.data["state"]
return int(self.coordinator.data["state"])
return None

@property
Expand All @@ -141,16 +89,9 @@ def extra_state_attributes(self):
return attrs

attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
attrs["title"] = self.coordinator.data["event"]
attrs["event_id"] = self.coordinator.data["event_id"]
attrs["message_type"] = self.coordinator.data["message_type"]
attrs["event_status"] = self.coordinator.data["event_status"]
attrs["event_severity"] = self.coordinator.data["event_severity"]
attrs["event_onset"] = self.coordinator.data["event_onset"]
attrs["event_expires"] = self.coordinator.data["event_expires"]
attrs["display_desc"] = self.coordinator.data["display_desc"]
attrs["spoken_desc"] = self.coordinator.data["spoken_desc"]

x = 0
if "alerts" in self.coordinator.data:
attrs["Alerts"] = self.coordinator.data["alerts"]
return attrs

@property
Expand Down
Loading

0 comments on commit e3c6497

Please sign in to comment.