Environment Canada integration: add get_alerts action (#172393)
This commit is contained in:
@@ -8,9 +8,12 @@ from env_canada import ECAirQuality, ECMap, ECWeather
|
|||||||
from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE, Platform
|
from homeassistant.const import CONF_LANGUAGE, CONF_LATITUDE, CONF_LONGITUDE, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
from homeassistant.exceptions import ConfigEntryNotReady
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import CONF_STATION
|
from .const import CONF_STATION, DOMAIN
|
||||||
from .coordinator import ECConfigEntry, ECDataUpdateCoordinator, ECRuntimeData
|
from .coordinator import ECConfigEntry, ECDataUpdateCoordinator, ECRuntimeData
|
||||||
|
from .services import async_setup_services
|
||||||
|
|
||||||
DEFAULT_RADAR_UPDATE_INTERVAL = timedelta(minutes=5)
|
DEFAULT_RADAR_UPDATE_INTERVAL = timedelta(minutes=5)
|
||||||
DEFAULT_WEATHER_UPDATE_INTERVAL = timedelta(minutes=5)
|
DEFAULT_WEATHER_UPDATE_INTERVAL = timedelta(minutes=5)
|
||||||
@@ -19,6 +22,14 @@ PLATFORMS = [Platform.CAMERA, Platform.SENSOR, Platform.WEATHER]
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
|
"""Set up the Environment Canada services."""
|
||||||
|
async_setup_services(hass)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ECConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, config_entry: ECConfigEntry) -> bool:
|
||||||
"""Set up EC as config entry."""
|
"""Set up EC as config entry."""
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_alerts": {
|
||||||
|
"service": "mdi:bell-alert"
|
||||||
|
},
|
||||||
"get_forecasts": {
|
"get_forecasts": {
|
||||||
"service": "mdi:weather-cloudy-clock"
|
"service": "mdi:weather-cloudy-clock"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
"""Define services for the Environment Canada integration."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from env_canada import ECWeather
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import ATTR_CONFIG_ENTRY_ID
|
||||||
|
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import config_validation as cv, service
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
SERVICE_GET_ALERTS = "get_alerts"
|
||||||
|
SERVICE_GET_ALERTS_SCHEMA = vol.Schema({vol.Required(ATTR_CONFIG_ENTRY_ID): cv.string})
|
||||||
|
|
||||||
|
SNAKE_MAPPING = {
|
||||||
|
"alertColourLevel": "alert_colour_level",
|
||||||
|
"expiryTime": "expiry_time",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_get_alerts(call: ServiceCall) -> dict[str, Any]:
|
||||||
|
"""Return the active alerts."""
|
||||||
|
entry = service.async_get_config_entry(
|
||||||
|
call.hass, DOMAIN, call.data[ATTR_CONFIG_ENTRY_ID]
|
||||||
|
)
|
||||||
|
|
||||||
|
ec: ECWeather | None = entry.runtime_data.weather_coordinator.ec_data
|
||||||
|
if ec is None:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="not_connected",
|
||||||
|
)
|
||||||
|
|
||||||
|
data: dict[str, Any] = ec.alerts
|
||||||
|
return {
|
||||||
|
k: [
|
||||||
|
{SNAKE_MAPPING.get(ik, ik): iv for ik, iv in item.items()}
|
||||||
|
for item in v["value"]
|
||||||
|
]
|
||||||
|
for k, v in data.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_setup_services(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up the services for the Environment Canada integration."""
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_ALERTS,
|
||||||
|
_async_get_alerts,
|
||||||
|
schema=SERVICE_GET_ALERTS_SCHEMA,
|
||||||
|
supports_response=SupportsResponse.ONLY,
|
||||||
|
)
|
||||||
@@ -1,3 +1,11 @@
|
|||||||
|
get_alerts:
|
||||||
|
fields:
|
||||||
|
config_entry_id:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
config_entry:
|
||||||
|
integration: environment_canada
|
||||||
|
|
||||||
get_forecasts:
|
get_forecasts:
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
|
|||||||
@@ -112,7 +112,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"not_connected": {
|
||||||
|
"message": "Environment Canada is not connected"
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
"get_alerts": {
|
||||||
|
"description": "Retrieves the alerts from the selected weather service.",
|
||||||
|
"fields": {
|
||||||
|
"config_entry_id": {
|
||||||
|
"description": "The Environment Canada service to retrieve alerts from.",
|
||||||
|
"name": "Environment Canada service"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Get alerts"
|
||||||
|
},
|
||||||
"get_forecasts": {
|
"get_forecasts": {
|
||||||
"description": "Retrieves the forecast from selected weather services.",
|
"description": "Retrieves the forecast from selected weather services.",
|
||||||
"name": "Get forecasts"
|
"name": "Get forecasts"
|
||||||
|
|||||||
@@ -1,29 +1,49 @@
|
|||||||
{
|
{
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"warnings": {
|
"warnings": {
|
||||||
"value": [],
|
"label": "Warnings",
|
||||||
"label": "Warnings"
|
|
||||||
},
|
|
||||||
"watches": {
|
|
||||||
"value": [],
|
|
||||||
"label": "Watches"
|
|
||||||
},
|
|
||||||
"advisories": {
|
|
||||||
"value": [
|
"value": [
|
||||||
{
|
{
|
||||||
"title": "Frost Advisory",
|
"title": "Air Quality Warning",
|
||||||
"date": "Monday October 03, 2022 at 15:05 EDT"
|
"date": "2026-06-04T10:35:16.386Z",
|
||||||
|
"alertColourLevel": "yellow",
|
||||||
|
"expiryTime": "2026-06-04T19:00:16.386Z",
|
||||||
|
"text": "Wildfire smoke is causing poor air quality. Conditions are expected to improve later this morning. Air quality and visibility due to wildfire smoke can fluctuate over short distances and can vary considerably from hour to hour. As smoke levels increase, health risks increase. Limit time outdoors. Consider reducing or rescheduling outdoor sports, activities and events.",
|
||||||
|
"area": "Yellowknife Region",
|
||||||
|
"status": "continued",
|
||||||
|
"confidence": "High",
|
||||||
|
"impact": "Moderate",
|
||||||
|
"alert_code": "AQW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Wow, it is hot out there!",
|
||||||
|
"date": "2026-06-04T10:35:16.386Z",
|
||||||
|
"alertColourLevel": "red",
|
||||||
|
"expiryTime": "2026-06-04T19:00:16.386Z",
|
||||||
|
"text": "It is so hot you can fry an egg on the pavement!",
|
||||||
|
"area": "Yellowknife Region",
|
||||||
|
"status": "continued",
|
||||||
|
"confidence": "High",
|
||||||
|
"impact": "Moderate",
|
||||||
|
"alert_code": "HOT"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"label": "Advisories"
|
},
|
||||||
|
"watches": {
|
||||||
|
"label": "Watches",
|
||||||
|
"value": []
|
||||||
|
},
|
||||||
|
"advisories": {
|
||||||
|
"label": "Advisories",
|
||||||
|
"value": []
|
||||||
},
|
},
|
||||||
"statements": {
|
"statements": {
|
||||||
"value": [],
|
"label": "Statements",
|
||||||
"label": "Statements"
|
"value": []
|
||||||
},
|
},
|
||||||
"endings": {
|
"endings": {
|
||||||
"value": [],
|
"label": "Endings",
|
||||||
"label": "Endings"
|
"value": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditions": {
|
"conditions": {
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_get_alerts
|
||||||
|
dict({
|
||||||
|
'advisories': list([
|
||||||
|
]),
|
||||||
|
'endings': list([
|
||||||
|
]),
|
||||||
|
'statements': list([
|
||||||
|
]),
|
||||||
|
'warnings': list([
|
||||||
|
dict({
|
||||||
|
'alert_code': 'AQW',
|
||||||
|
'alert_colour_level': 'yellow',
|
||||||
|
'area': 'Yellowknife Region',
|
||||||
|
'confidence': 'High',
|
||||||
|
'date': '2026-06-04T10:35:16.386Z',
|
||||||
|
'expiry_time': '2026-06-04T19:00:16.386Z',
|
||||||
|
'impact': 'Moderate',
|
||||||
|
'status': 'continued',
|
||||||
|
'text': 'Wildfire smoke is causing poor air quality. Conditions are expected to improve later this morning. Air quality and visibility due to wildfire smoke can fluctuate over short distances and can vary considerably from hour to hour. As smoke levels increase, health risks increase. Limit time outdoors. Consider reducing or rescheduling outdoor sports, activities and events.',
|
||||||
|
'title': 'Air Quality Warning',
|
||||||
|
}),
|
||||||
|
dict({
|
||||||
|
'alert_code': 'HOT',
|
||||||
|
'alert_colour_level': 'red',
|
||||||
|
'area': 'Yellowknife Region',
|
||||||
|
'confidence': 'High',
|
||||||
|
'date': '2026-06-04T10:35:16.386Z',
|
||||||
|
'expiry_time': '2026-06-04T19:00:16.386Z',
|
||||||
|
'impact': 'Moderate',
|
||||||
|
'status': 'continued',
|
||||||
|
'text': 'It is so hot you can fry an egg on the pavement!',
|
||||||
|
'title': 'Wow, it is hot out there!',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
'watches': list([
|
||||||
|
]),
|
||||||
|
})
|
||||||
|
# ---
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
"""Tests for the Environment Canada services."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
|
from homeassistant.components.environment_canada.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
|
|
||||||
|
SERVICE_GET_ALERTS = "get_alerts"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_alerts(
|
||||||
|
hass: HomeAssistant, snapshot: SnapshotAssertion, ec_data: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Test the get_alerts service returns active alerts."""
|
||||||
|
config_entry = await init_integration(hass, ec_data)
|
||||||
|
|
||||||
|
response = await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_ALERTS,
|
||||||
|
{"config_entry_id": config_entry.entry_id},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
assert response == snapshot
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_alerts_not_connected(
|
||||||
|
hass: HomeAssistant, ec_data: dict[str, Any]
|
||||||
|
) -> None:
|
||||||
|
"""Test get_alerts raises when weather data is not connected."""
|
||||||
|
config_entry = await init_integration(hass, ec_data)
|
||||||
|
config_entry.runtime_data.weather_coordinator.ec_data = None
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError, match="not connected"):
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_ALERTS,
|
||||||
|
{"config_entry_id": config_entry.entry_id},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user