Convert CO2Signal to data update coordinator and add fossil fuel percentage (#53370)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com> Co-authored-by: Daniel Hjelseth Høyer <mail@dahoiv.net>pull/53473/head
parent
d0bef97453
commit
c49decb7f0
|
@ -1,16 +1,53 @@
|
||||||
"""The CO2 Signal integration."""
|
"""The CO2 Signal integration."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from datetime import timedelta
|
||||||
from homeassistant.core import HomeAssistant
|
import logging
|
||||||
|
from typing import TypedDict, cast
|
||||||
|
|
||||||
from .const import DOMAIN # noqa: F401
|
import CO2Signal
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import ConfigEntryAuthFailed, HomeAssistantError
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
|
from .const import CONF_COUNTRY_CODE, DOMAIN
|
||||||
|
from .util import get_extra_name
|
||||||
|
|
||||||
PLATFORMS = ["sensor"]
|
PLATFORMS = ["sensor"]
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CO2SignalData(TypedDict):
|
||||||
|
"""Data field."""
|
||||||
|
|
||||||
|
carbonIntensity: float
|
||||||
|
fossilFuelPercentage: float
|
||||||
|
|
||||||
|
|
||||||
|
class CO2SignalUnit(TypedDict):
|
||||||
|
"""Unit field."""
|
||||||
|
|
||||||
|
carbonIntensity: str
|
||||||
|
|
||||||
|
|
||||||
|
class CO2SignalResponse(TypedDict):
|
||||||
|
"""API response."""
|
||||||
|
|
||||||
|
status: str
|
||||||
|
countryCode: str
|
||||||
|
data: CO2SignalData
|
||||||
|
units: CO2SignalUnit
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up CO2 Signal from a config entry."""
|
"""Set up CO2 Signal from a config entry."""
|
||||||
|
coordinator = CO2SignalCoordinator(hass, entry)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -18,3 +55,95 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
||||||
|
|
||||||
|
class CO2SignalCoordinator(DataUpdateCoordinator[CO2SignalResponse]):
|
||||||
|
"""Data update coordinator."""
|
||||||
|
|
||||||
|
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
|
"""Initialize the coordinator."""
|
||||||
|
super().__init__(
|
||||||
|
hass, _LOGGER, name=DOMAIN, update_interval=timedelta(minutes=15)
|
||||||
|
)
|
||||||
|
self._entry = entry
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entry_id(self) -> str:
|
||||||
|
"""Return entry ID."""
|
||||||
|
return self._entry.entry_id
|
||||||
|
|
||||||
|
def get_extra_name(self) -> str | None:
|
||||||
|
"""Return the extra name describing the location if not home."""
|
||||||
|
return get_extra_name(self._entry.data)
|
||||||
|
|
||||||
|
async def _async_update_data(self) -> CO2SignalResponse:
|
||||||
|
"""Fetch the latest data from the source."""
|
||||||
|
try:
|
||||||
|
data = await self.hass.async_add_executor_job(
|
||||||
|
get_data, self.hass, self._entry.data
|
||||||
|
)
|
||||||
|
except InvalidAuth as err:
|
||||||
|
raise ConfigEntryAuthFailed from err
|
||||||
|
except CO2Error as err:
|
||||||
|
raise UpdateFailed(str(err)) from err
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class CO2Error(HomeAssistantError):
|
||||||
|
"""Base error."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidAuth(CO2Error):
|
||||||
|
"""Raised when invalid authentication credentials are provided."""
|
||||||
|
|
||||||
|
|
||||||
|
class APIRatelimitExceeded(CO2Error):
|
||||||
|
"""Raised when the API rate limit is exceeded."""
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownError(CO2Error):
|
||||||
|
"""Raised when an unknown error occurs."""
|
||||||
|
|
||||||
|
|
||||||
|
def get_data(hass: HomeAssistant, config: dict) -> CO2SignalResponse:
|
||||||
|
"""Get data from the API."""
|
||||||
|
if CONF_COUNTRY_CODE in config:
|
||||||
|
latitude = None
|
||||||
|
longitude = None
|
||||||
|
else:
|
||||||
|
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
||||||
|
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = CO2Signal.get_latest(
|
||||||
|
config[CONF_API_KEY],
|
||||||
|
config.get(CONF_COUNTRY_CODE),
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
wait=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
except ValueError as err:
|
||||||
|
err_str = str(err)
|
||||||
|
|
||||||
|
if "Invalid authentication credentials" in err_str:
|
||||||
|
raise InvalidAuth from err
|
||||||
|
if "API rate limit exceeded." in err_str:
|
||||||
|
raise APIRatelimitExceeded from err
|
||||||
|
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
raise UnknownError from err
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unexpected exception")
|
||||||
|
raise UnknownError from err
|
||||||
|
|
||||||
|
else:
|
||||||
|
if "error" in data:
|
||||||
|
raise UnknownError(data["error"])
|
||||||
|
|
||||||
|
if data.get("status") != "ok":
|
||||||
|
_LOGGER.exception("Unexpected response: %s", data)
|
||||||
|
raise UnknownError
|
||||||
|
|
||||||
|
return cast(CO2SignalResponse, data)
|
||||||
|
|
|
@ -4,15 +4,14 @@ from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import CO2Signal
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_TOKEN
|
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_TOKEN
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from . import APIRatelimitExceeded, CO2Error, InvalidAuth, UnknownError, get_data
|
||||||
from .const import CONF_COUNTRY_CODE, DOMAIN
|
from .const import CONF_COUNTRY_CODE, DOMAIN
|
||||||
from .util import get_extra_name
|
from .util import get_extra_name
|
||||||
|
|
||||||
|
@ -34,62 +33,6 @@ def _get_entry_type(config: dict) -> str:
|
||||||
return TYPE_USE_HOME
|
return TYPE_USE_HOME
|
||||||
|
|
||||||
|
|
||||||
def _validate_info(hass, config: dict) -> dict:
|
|
||||||
"""Validate the passed in info."""
|
|
||||||
if CONF_COUNTRY_CODE in config:
|
|
||||||
latitude = None
|
|
||||||
longitude = None
|
|
||||||
else:
|
|
||||||
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
|
|
||||||
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = CO2Signal.get_latest(
|
|
||||||
config[CONF_API_KEY],
|
|
||||||
config.get(CONF_COUNTRY_CODE),
|
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
wait=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
except ValueError as err:
|
|
||||||
err_str = str(err)
|
|
||||||
|
|
||||||
if "Invalid authentication credentials" in err_str:
|
|
||||||
raise InvalidAuth from err
|
|
||||||
if "API rate limit exceeded." in err_str:
|
|
||||||
raise APIRatelimitExceeded from err
|
|
||||||
|
|
||||||
_LOGGER.exception("Unexpected exception")
|
|
||||||
raise UnknownError from err
|
|
||||||
except Exception as err: # pylint: disable=broad-except
|
|
||||||
_LOGGER.exception("Unexpected exception")
|
|
||||||
raise UnknownError from err
|
|
||||||
|
|
||||||
else:
|
|
||||||
if data.get("status") != "ok":
|
|
||||||
_LOGGER.exception("Unexpected response: %s", data)
|
|
||||||
raise UnknownError
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class CO2Error(HomeAssistantError):
|
|
||||||
"""Base error."""
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidAuth(CO2Error):
|
|
||||||
"""Raised when invalid authentication credentials are provided."""
|
|
||||||
|
|
||||||
|
|
||||||
class APIRatelimitExceeded(CO2Error):
|
|
||||||
"""Raised when the API rate limit is exceeded."""
|
|
||||||
|
|
||||||
|
|
||||||
class UnknownError(CO2Error):
|
|
||||||
"""Raised when an unknown error occurs."""
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Co2signal."""
|
"""Handle a config flow for Co2signal."""
|
||||||
|
|
||||||
|
@ -136,12 +79,12 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
return self.async_abort(reason="already_configured")
|
return self.async_abort(reason="already_configured")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(_validate_info, self.hass, data)
|
await self.hass.async_add_executor_job(get_data, self.hass, data)
|
||||||
except CO2Error:
|
except CO2Error:
|
||||||
return self.async_abort(reason="unknown")
|
return self.async_abort(reason="unknown")
|
||||||
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=get_extra_name(self.hass, data) or "CO2 Signal", data=data
|
title=get_extra_name(data) or "CO2 Signal", data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
|
@ -227,7 +170,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, str] = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.hass.async_add_executor_job(_validate_info, self.hass, data)
|
await self.hass.async_add_executor_job(get_data, self.hass, data)
|
||||||
except InvalidAuth:
|
except InvalidAuth:
|
||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except APIRatelimitExceeded:
|
except APIRatelimitExceeded:
|
||||||
|
@ -236,7 +179,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
else:
|
else:
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=get_extra_name(self.hass, data) or "CO2 Signal",
|
title=get_extra_name(data) or "CO2 Signal",
|
||||||
data=data,
|
data=data,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,36 @@
|
||||||
"""Support for the CO2signal platform."""
|
"""Support for the CO2signal platform."""
|
||||||
from datetime import timedelta
|
from __future__ import annotations
|
||||||
import logging
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import CO2Signal
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
|
PLATFORM_SCHEMA,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
SensorEntity,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
ATTR_IDENTIFIERS,
|
ATTR_IDENTIFIERS,
|
||||||
ATTR_MANUFACTURER,
|
ATTR_MANUFACTURER,
|
||||||
ATTR_NAME,
|
ATTR_NAME,
|
||||||
CONF_API_KEY,
|
|
||||||
CONF_LATITUDE,
|
CONF_LATITUDE,
|
||||||
CONF_LONGITUDE,
|
CONF_LONGITUDE,
|
||||||
CONF_TOKEN,
|
CONF_TOKEN,
|
||||||
ENERGY_KILO_WATT_HOUR,
|
PERCENTAGE,
|
||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
from homeassistant.helpers import config_validation as cv, update_coordinator
|
||||||
|
from homeassistant.helpers.typing import StateType
|
||||||
|
|
||||||
|
from . import CO2SignalCoordinator, CO2SignalResponse
|
||||||
from .const import ATTRIBUTION, CONF_COUNTRY_CODE, DOMAIN, MSG_LOCATION
|
from .const import ATTRIBUTION, CONF_COUNTRY_CODE, DOMAIN, MSG_LOCATION
|
||||||
from .util import get_extra_name
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
SCAN_INTERVAL = timedelta(minutes=3)
|
SCAN_INTERVAL = timedelta(minutes=3)
|
||||||
|
|
||||||
CO2_INTENSITY_UNIT = f"CO2eq/{ENERGY_KILO_WATT_HOUR}"
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_TOKEN): cv.string,
|
vol.Required(CONF_TOKEN): cv.string,
|
||||||
|
@ -37,6 +41,32 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CO2SensorEntityDescription:
|
||||||
|
"""Provide a description of a CO2 sensor."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
name: str
|
||||||
|
unit_of_measurement: str | None = None
|
||||||
|
# For backwards compat, allow description to override unique ID key to use
|
||||||
|
unique_id: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
SENSORS = (
|
||||||
|
CO2SensorEntityDescription(
|
||||||
|
key="carbonIntensity",
|
||||||
|
name="CO2 intensity",
|
||||||
|
unique_id="co2intensity",
|
||||||
|
# No unit, it's extracted from response.
|
||||||
|
),
|
||||||
|
CO2SensorEntityDescription(
|
||||||
|
key="fossilFuelPercentage",
|
||||||
|
name="Grid fossil fuel percentage",
|
||||||
|
unit_of_measurement=PERCENTAGE,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
"""Set up the CO2signal sensor."""
|
"""Set up the CO2signal sensor."""
|
||||||
await hass.config_entries.flow.async_init(
|
await hass.config_entries.flow.async_init(
|
||||||
|
@ -48,59 +78,47 @@ async def async_setup_platform(hass, config, add_entities, discovery_info=None):
|
||||||
|
|
||||||
async def async_setup_entry(hass, entry, async_add_entities):
|
async def async_setup_entry(hass, entry, async_add_entities):
|
||||||
"""Set up the CO2signal sensor."""
|
"""Set up the CO2signal sensor."""
|
||||||
name = "CO2 intensity"
|
coordinator: CO2SignalCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||||
if extra_name := get_extra_name(hass, entry.data):
|
async_add_entities(CO2Sensor(coordinator, description) for description in SENSORS)
|
||||||
name += f" - {extra_name}"
|
|
||||||
|
|
||||||
async_add_entities(
|
|
||||||
[
|
|
||||||
CO2Sensor(
|
|
||||||
name,
|
|
||||||
entry.data,
|
|
||||||
entry_id=entry.entry_id,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CO2Sensor(SensorEntity):
|
class CO2Sensor(update_coordinator.CoordinatorEntity[CO2SignalResponse], SensorEntity):
|
||||||
"""Implementation of the CO2Signal sensor."""
|
"""Implementation of the CO2Signal sensor."""
|
||||||
|
|
||||||
|
_attr_state_class = STATE_CLASS_MEASUREMENT
|
||||||
_attr_icon = "mdi:molecule-co2"
|
_attr_icon = "mdi:molecule-co2"
|
||||||
_attr_unit_of_measurement = CO2_INTENSITY_UNIT
|
|
||||||
|
|
||||||
def __init__(self, name, config, entry_id):
|
def __init__(
|
||||||
|
self, coordinator: CO2SignalCoordinator, description: CO2SensorEntityDescription
|
||||||
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
self._config = config
|
super().__init__(coordinator)
|
||||||
|
self._description = description
|
||||||
|
|
||||||
|
name = description.name
|
||||||
|
if extra_name := coordinator.get_extra_name():
|
||||||
|
name = f"{extra_name} - {name}"
|
||||||
|
|
||||||
self._attr_name = name
|
self._attr_name = name
|
||||||
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
|
||||||
self._attr_device_info = {
|
self._attr_device_info = {
|
||||||
ATTR_IDENTIFIERS: {(DOMAIN, entry_id)},
|
ATTR_IDENTIFIERS: {(DOMAIN, coordinator.entry_id)},
|
||||||
ATTR_NAME: "CO2 signal",
|
ATTR_NAME: "CO2 signal",
|
||||||
ATTR_MANUFACTURER: "Tmrow.com",
|
ATTR_MANUFACTURER: "Tmrow.com",
|
||||||
"entry_type": "service",
|
"entry_type": "service",
|
||||||
}
|
}
|
||||||
self._attr_unique_id = f"{entry_id}_co2intensity"
|
self._attr_unique_id = (
|
||||||
|
f"{coordinator.entry_id}_{description.unique_id or description.key}"
|
||||||
def update(self):
|
|
||||||
"""Get the latest data and updates the states."""
|
|
||||||
_LOGGER.debug("Update data for %s", self.name)
|
|
||||||
|
|
||||||
if CONF_COUNTRY_CODE in self._config:
|
|
||||||
kwargs = {"country_code": self._config[CONF_COUNTRY_CODE]}
|
|
||||||
elif CONF_LATITUDE in self._config:
|
|
||||||
kwargs = {
|
|
||||||
"latitude": self._config[CONF_LATITUDE],
|
|
||||||
"longitude": self._config[CONF_LONGITUDE],
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
kwargs = {
|
|
||||||
"latitude": self.hass.config.latitude,
|
|
||||||
"longitude": self.hass.config.longitude,
|
|
||||||
}
|
|
||||||
|
|
||||||
self._attr_state = round(
|
|
||||||
CO2Signal.get_latest_carbon_intensity(self._config[CONF_API_KEY], **kwargs),
|
|
||||||
2,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> StateType:
|
||||||
|
"""Return sensor state."""
|
||||||
|
return round(self.coordinator.data["data"][self._description.key], 2) # type: ignore[misc]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self) -> str | None:
|
||||||
|
"""Return the unit of measurement."""
|
||||||
|
if self._description.unit_of_measurement:
|
||||||
|
return self._description.unit_of_measurement
|
||||||
|
return cast(str, self.coordinator.data["units"].get(self._description.key))
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
"""Utils for CO2 signal."""
|
"""Utils for CO2 signal."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Mapping
|
||||||
|
|
||||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||||
from homeassistant.core import HomeAssistant
|
|
||||||
|
|
||||||
from .const import CONF_COUNTRY_CODE
|
from .const import CONF_COUNTRY_CODE
|
||||||
|
|
||||||
|
|
||||||
def get_extra_name(hass: HomeAssistant, config: dict) -> str | None:
|
def get_extra_name(config: Mapping) -> str | None:
|
||||||
"""Return the extra name describing the location if not home."""
|
"""Return the extra name describing the location if not home."""
|
||||||
if CONF_COUNTRY_CODE in config:
|
if CONF_COUNTRY_CODE in config:
|
||||||
return config[CONF_COUNTRY_CODE]
|
return config[CONF_COUNTRY_CODE]
|
||||||
|
|
|
@ -23,10 +23,7 @@ async def test_form_home(hass: HomeAssistant) -> None:
|
||||||
assert result["type"] == RESULT_TYPE_FORM
|
assert result["type"] == RESULT_TYPE_FORM
|
||||||
assert result["errors"] is None
|
assert result["errors"] is None
|
||||||
|
|
||||||
with patch(
|
with patch("CO2Signal.get_latest", return_value=VALID_PAYLOAD,), patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
|
||||||
return_value=VALID_PAYLOAD,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.co2signal.async_setup_entry",
|
"homeassistant.components.co2signal.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_setup_entry:
|
) as mock_setup_entry:
|
||||||
|
@ -65,10 +62,7 @@ async def test_form_coordinates(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
assert result2["type"] == RESULT_TYPE_FORM
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
|
||||||
with patch(
|
with patch("CO2Signal.get_latest", return_value=VALID_PAYLOAD,), patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
|
||||||
return_value=VALID_PAYLOAD,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.co2signal.async_setup_entry",
|
"homeassistant.components.co2signal.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_setup_entry:
|
) as mock_setup_entry:
|
||||||
|
@ -109,10 +103,7 @@ async def test_form_country(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
assert result2["type"] == RESULT_TYPE_FORM
|
assert result2["type"] == RESULT_TYPE_FORM
|
||||||
|
|
||||||
with patch(
|
with patch("CO2Signal.get_latest", return_value=VALID_PAYLOAD,), patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
|
||||||
return_value=VALID_PAYLOAD,
|
|
||||||
), patch(
|
|
||||||
"homeassistant.components.co2signal.async_setup_entry",
|
"homeassistant.components.co2signal.async_setup_entry",
|
||||||
return_value=True,
|
return_value=True,
|
||||||
) as mock_setup_entry:
|
) as mock_setup_entry:
|
||||||
|
@ -148,7 +139,7 @@ async def test_form_error_handling(hass: HomeAssistant, err_str, err_code) -> No
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
side_effect=ValueError(err_str),
|
side_effect=ValueError(err_str),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
@ -170,7 +161,7 @@ async def test_form_error_unexpected_error(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
side_effect=Exception("Boom"),
|
side_effect=Exception("Boom"),
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
@ -192,7 +183,7 @@ async def test_form_error_unexpected_data(hass: HomeAssistant) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
return_value={"status": "error"},
|
return_value={"status": "error"},
|
||||||
):
|
):
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
@ -212,7 +203,7 @@ async def test_import(hass: HomeAssistant) -> None:
|
||||||
await setup.async_setup_component(hass, "persistent_notification", {})
|
await setup.async_setup_component(hass, "persistent_notification", {})
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
return_value=VALID_PAYLOAD,
|
return_value=VALID_PAYLOAD,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -221,10 +212,18 @@ async def test_import(hass: HomeAssistant) -> None:
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(hass.config_entries.async_entries("co2signal")) == 1
|
assert len(hass.config_entries.async_entries("co2signal")) == 1
|
||||||
|
|
||||||
state = hass.states.get("sensor.co2_intensity")
|
state = hass.states.get("sensor.co2_intensity")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == "45.99"
|
assert state.state == "45.99"
|
||||||
assert state.name == "CO2 intensity"
|
assert state.name == "CO2 intensity"
|
||||||
|
assert state.attributes["unit_of_measurement"] == "gCO2eq/kWh"
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.grid_fossil_fuel_percentage")
|
||||||
|
assert state is not None
|
||||||
|
assert state.state == "5.46"
|
||||||
|
assert state.name == "Grid fossil fuel percentage"
|
||||||
|
assert state.attributes["unit_of_measurement"] == "%"
|
||||||
|
|
||||||
|
|
||||||
async def test_import_abort_existing_home(hass: HomeAssistant) -> None:
|
async def test_import_abort_existing_home(hass: HomeAssistant) -> None:
|
||||||
|
@ -233,7 +232,7 @@ async def test_import_abort_existing_home(hass: HomeAssistant) -> None:
|
||||||
MockConfigEntry(domain="co2signal", data={"api_key": "abcd"}).add_to_hass(hass)
|
MockConfigEntry(domain="co2signal", data={"api_key": "abcd"}).add_to_hass(hass)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
return_value=VALID_PAYLOAD,
|
return_value=VALID_PAYLOAD,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -252,7 +251,7 @@ async def test_import_abort_existing_country(hass: HomeAssistant) -> None:
|
||||||
).add_to_hass(hass)
|
).add_to_hass(hass)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
return_value=VALID_PAYLOAD,
|
return_value=VALID_PAYLOAD,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -279,7 +278,7 @@ async def test_import_abort_existing_coordinates(hass: HomeAssistant) -> None:
|
||||||
).add_to_hass(hass)
|
).add_to_hass(hass)
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.co2signal.config_flow.CO2Signal.get_latest",
|
"CO2Signal.get_latest",
|
||||||
return_value=VALID_PAYLOAD,
|
return_value=VALID_PAYLOAD,
|
||||||
):
|
):
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
|
Loading…
Reference in New Issue