mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 02:07:09 +00:00
Add pollen sensors to Ambee (#51702)
This commit is contained in:
parent
79996682e5
commit
fca0446ff8
@ -9,30 +9,31 @@ from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
|
from .const import DOMAIN, LOGGER, SCAN_INTERVAL, SERVICE_AIR_QUALITY, SERVICE_POLLEN
|
||||||
|
|
||||||
PLATFORMS = (SENSOR_DOMAIN,)
|
PLATFORMS = (SENSOR_DOMAIN,)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up Ambee from a config entry."""
|
"""Set up Ambee from a config entry."""
|
||||||
|
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
|
||||||
|
|
||||||
client = Ambee(
|
client = Ambee(
|
||||||
api_key=entry.data[CONF_API_KEY],
|
api_key=entry.data[CONF_API_KEY],
|
||||||
latitude=entry.data[CONF_LATITUDE],
|
latitude=entry.data[CONF_LATITUDE],
|
||||||
longitude=entry.data[CONF_LONGITUDE],
|
longitude=entry.data[CONF_LONGITUDE],
|
||||||
)
|
)
|
||||||
|
|
||||||
coordinator = DataUpdateCoordinator(
|
for service in {SERVICE_AIR_QUALITY, SERVICE_POLLEN}:
|
||||||
hass,
|
coordinator: DataUpdateCoordinator = DataUpdateCoordinator(
|
||||||
LOGGER,
|
hass,
|
||||||
name=DOMAIN,
|
LOGGER,
|
||||||
update_interval=SCAN_INTERVAL,
|
name=DOMAIN,
|
||||||
update_method=client.air_quality,
|
update_interval=SCAN_INTERVAL,
|
||||||
)
|
update_method=getattr(client, service),
|
||||||
await coordinator.async_config_entry_first_refresh()
|
)
|
||||||
|
await coordinator.async_config_entry_first_refresh()
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data[DOMAIN][entry.entry_id][service] = coordinator
|
||||||
hass.data[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
|
||||||
|
@ -3,67 +3,210 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, Final
|
from typing import Final
|
||||||
|
|
||||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_ICON,
|
||||||
ATTR_NAME,
|
ATTR_NAME,
|
||||||
ATTR_SERVICE,
|
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_BILLION,
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
DEVICE_CLASS_CO,
|
DEVICE_CLASS_CO,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .models import AmbeeSensor
|
||||||
|
|
||||||
DOMAIN: Final = "ambee"
|
DOMAIN: Final = "ambee"
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
SCAN_INTERVAL = timedelta(minutes=180)
|
SCAN_INTERVAL = timedelta(minutes=180)
|
||||||
|
|
||||||
SERVICE_AIR_QUALITY: Final = ("air_quality", "Air Quality")
|
ATTR_ENABLED_BY_DEFAULT: Final = "enabled_by_default"
|
||||||
|
ATTR_ENTRY_TYPE: Final = "entry_type"
|
||||||
|
ENTRY_TYPE_SERVICE: Final = "service"
|
||||||
|
|
||||||
SENSORS: dict[str, dict[str, Any]] = {
|
DEVICE_CLASS_AMBEE_RISK: Final = "ambee__risk"
|
||||||
"particulate_matter_2_5": {
|
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
SERVICE_AIR_QUALITY: Final = "air_quality"
|
||||||
ATTR_NAME: "Particulate Matter < 2.5 μm",
|
SERVICE_POLLEN: Final = "pollen"
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
SERVICES: dict[str, str] = {
|
||||||
|
SERVICE_AIR_QUALITY: "Air Quality",
|
||||||
|
SERVICE_POLLEN: "Pollen",
|
||||||
|
}
|
||||||
|
|
||||||
|
SENSORS: dict[str, dict[str, AmbeeSensor]] = {
|
||||||
|
SERVICE_AIR_QUALITY: {
|
||||||
|
"particulate_matter_2_5": {
|
||||||
|
ATTR_NAME: "Particulate Matter < 2.5 μm",
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
|
"particulate_matter_10": {
|
||||||
|
ATTR_NAME: "Particulate Matter < 10 μm",
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
|
"sulphur_dioxide": {
|
||||||
|
ATTR_NAME: "Sulphur Dioxide (SO2)",
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
|
"nitrogen_dioxide": {
|
||||||
|
ATTR_NAME: "Nitrogen Dioxide (NO2)",
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
|
"ozone": {
|
||||||
|
ATTR_NAME: "Ozone",
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
|
"carbon_monoxide": {
|
||||||
|
ATTR_NAME: "Carbon Monoxide (CO)",
|
||||||
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_MILLION,
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
|
"air_quality_index": {
|
||||||
|
ATTR_NAME: "Air Quality Index (AQI)",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"particulate_matter_10": {
|
SERVICE_POLLEN: {
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
"grass": {
|
||||||
ATTR_NAME: "Particulate Matter < 10 μm",
|
ATTR_NAME: "Grass Pollen",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
ATTR_ICON: "mdi:grass",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
},
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
"sulphur_dioxide": {
|
},
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
"tree": {
|
||||||
ATTR_NAME: "Sulphur Dioxide (SO2)",
|
ATTR_NAME: "Tree Pollen",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
ATTR_ICON: "mdi:tree",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
},
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
"nitrogen_dioxide": {
|
},
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
"weed": {
|
||||||
ATTR_NAME: "Nitrogen Dioxide (NO2)",
|
ATTR_NAME: "Weed Pollen",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
ATTR_ICON: "mdi:sprout",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
},
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
"ozone": {
|
},
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
"grass_risk": {
|
||||||
ATTR_NAME: "Ozone",
|
ATTR_NAME: "Grass Pollen Risk",
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_BILLION,
|
ATTR_ICON: "mdi:grass",
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK,
|
||||||
},
|
},
|
||||||
"carbon_monoxide": {
|
"tree_risk": {
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
ATTR_NAME: "Tree Pollen Risk",
|
||||||
ATTR_NAME: "Carbon Monoxide (CO)",
|
ATTR_ICON: "mdi:tree",
|
||||||
ATTR_DEVICE_CLASS: DEVICE_CLASS_CO,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK,
|
||||||
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_MILLION,
|
},
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
"weed_risk": {
|
||||||
},
|
ATTR_NAME: "Weed Pollen Risk",
|
||||||
"air_quality_index": {
|
ATTR_ICON: "mdi:sprout",
|
||||||
ATTR_SERVICE: SERVICE_AIR_QUALITY,
|
ATTR_DEVICE_CLASS: DEVICE_CLASS_AMBEE_RISK,
|
||||||
ATTR_NAME: "Air Quality Index (AQI)",
|
},
|
||||||
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
"grass_poaceae": {
|
||||||
|
ATTR_NAME: "Poaceae Grass Pollen",
|
||||||
|
ATTR_ICON: "mdi:grass",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_alder": {
|
||||||
|
ATTR_NAME: "Alder Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_birch": {
|
||||||
|
ATTR_NAME: "Birch Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_cypress": {
|
||||||
|
ATTR_NAME: "Cypress Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_elm": {
|
||||||
|
ATTR_NAME: "Elm Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_hazel": {
|
||||||
|
ATTR_NAME: "Hazel Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_oak": {
|
||||||
|
ATTR_NAME: "Oak Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_pine": {
|
||||||
|
ATTR_NAME: "Pine Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_plane": {
|
||||||
|
ATTR_NAME: "Plane Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"tree_poplar": {
|
||||||
|
ATTR_NAME: "Poplar Tree Pollen",
|
||||||
|
ATTR_ICON: "mdi:tree",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"weed_chenopod": {
|
||||||
|
ATTR_NAME: "Chenopod Weed Pollen",
|
||||||
|
ATTR_ICON: "mdi:sprout",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"weed_mugwort": {
|
||||||
|
ATTR_NAME: "Mugwort Weed Pollen",
|
||||||
|
ATTR_ICON: "mdi:sprout",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"weed_nettle": {
|
||||||
|
ATTR_NAME: "Nettle Weed Pollen",
|
||||||
|
ATTR_ICON: "mdi:sprout",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
|
"weed_ragweed": {
|
||||||
|
ATTR_NAME: "Ragweed Weed Pollen",
|
||||||
|
ATTR_ICON: "mdi:sprout",
|
||||||
|
ATTR_STATE_CLASS: STATE_CLASS_MEASUREMENT,
|
||||||
|
ATTR_UNIT_OF_MEASUREMENT: CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
|
ATTR_ENABLED_BY_DEFAULT: False,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
15
homeassistant/components/ambee/models.py
Normal file
15
homeassistant/components/ambee/models.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"""Models helper class for the Ambee integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
class AmbeeSensor(TypedDict, total=False):
|
||||||
|
"""Represent an Ambee Sensor."""
|
||||||
|
|
||||||
|
device_class: str
|
||||||
|
enabled_by_default: bool
|
||||||
|
icon: str
|
||||||
|
name: str
|
||||||
|
state_class: str
|
||||||
|
unit_of_measurement: str
|
@ -1,18 +1,21 @@
|
|||||||
"""Support for Ambee sensors."""
|
"""Support for Ambee sensors."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorEntity
|
from homeassistant.components.sensor import (
|
||||||
|
ATTR_STATE_CLASS,
|
||||||
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
|
SensorEntity,
|
||||||
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
|
ATTR_ICON,
|
||||||
ATTR_IDENTIFIERS,
|
ATTR_IDENTIFIERS,
|
||||||
ATTR_MANUFACTURER,
|
ATTR_MANUFACTURER,
|
||||||
ATTR_NAME,
|
ATTR_NAME,
|
||||||
ATTR_SERVICE,
|
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import StateType
|
from homeassistant.helpers.typing import StateType
|
||||||
from homeassistant.helpers.update_coordinator import (
|
from homeassistant.helpers.update_coordinator import (
|
||||||
@ -20,7 +23,15 @@ from homeassistant.helpers.update_coordinator import (
|
|||||||
DataUpdateCoordinator,
|
DataUpdateCoordinator,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import DOMAIN, SENSORS
|
from .const import (
|
||||||
|
ATTR_ENABLED_BY_DEFAULT,
|
||||||
|
ATTR_ENTRY_TYPE,
|
||||||
|
DOMAIN,
|
||||||
|
ENTRY_TYPE_SERVICE,
|
||||||
|
SENSORS,
|
||||||
|
SERVICES,
|
||||||
|
)
|
||||||
|
from .models import AmbeeSensor
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -28,42 +39,61 @@ async def async_setup_entry(
|
|||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up Ambee sensor based on a config entry."""
|
"""Set up Ambee sensors based on a config entry."""
|
||||||
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
|
||||||
async_add_entities(
|
async_add_entities(
|
||||||
AmbeeSensor(coordinator=coordinator, entry_id=entry.entry_id, key=sensor)
|
AmbeeSensorEntity(
|
||||||
for sensor in SENSORS
|
coordinator=hass.data[DOMAIN][entry.entry_id][service_key],
|
||||||
|
entry_id=entry.entry_id,
|
||||||
|
sensor_key=sensor_key,
|
||||||
|
sensor=sensor,
|
||||||
|
service_key=service_key,
|
||||||
|
service=SERVICES[service_key],
|
||||||
|
)
|
||||||
|
for service_key, service_sensors in SENSORS.items()
|
||||||
|
for sensor_key, sensor in service_sensors.items()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AmbeeSensor(CoordinatorEntity, SensorEntity):
|
class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
|
||||||
"""Defines an Ambee sensor."""
|
"""Defines an Ambee sensor."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, *, coordinator: DataUpdateCoordinator, entry_id: str, key: str
|
self,
|
||||||
|
*,
|
||||||
|
coordinator: DataUpdateCoordinator,
|
||||||
|
entry_id: str,
|
||||||
|
sensor_key: str,
|
||||||
|
sensor: AmbeeSensor,
|
||||||
|
service_key: str,
|
||||||
|
service: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Ambee sensor."""
|
"""Initialize Ambee sensor."""
|
||||||
super().__init__(coordinator=coordinator)
|
super().__init__(coordinator=coordinator)
|
||||||
self._key = key
|
self._sensor_key = sensor_key
|
||||||
self._entry_id = entry_id
|
self._service_key = service_key
|
||||||
self._service_key, self._service_name = SENSORS[key][ATTR_SERVICE]
|
|
||||||
|
|
||||||
self._attr_device_class = SENSORS[key].get(ATTR_DEVICE_CLASS)
|
self.entity_id = f"{SENSOR_DOMAIN}.{service_key}_{sensor_key}"
|
||||||
self._attr_name = SENSORS[key][ATTR_NAME]
|
self._attr_device_class = sensor.get(ATTR_DEVICE_CLASS)
|
||||||
self._attr_state_class = SENSORS[key].get(ATTR_STATE_CLASS)
|
self._attr_entity_registry_enabled_default = sensor.get(
|
||||||
self._attr_unique_id = f"{entry_id}_{key}"
|
ATTR_ENABLED_BY_DEFAULT, True
|
||||||
self._attr_unit_of_measurement = SENSORS[key].get(ATTR_UNIT_OF_MEASUREMENT)
|
)
|
||||||
|
self._attr_icon = sensor.get(ATTR_ICON)
|
||||||
|
self._attr_name = sensor.get(ATTR_NAME)
|
||||||
|
self._attr_state_class = sensor.get(ATTR_STATE_CLASS)
|
||||||
|
self._attr_unique_id = f"{entry_id}_{service_key}_{sensor_key}"
|
||||||
|
self._attr_unit_of_measurement = sensor.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
|
||||||
|
self._attr_device_info = {
|
||||||
|
ATTR_IDENTIFIERS: {(DOMAIN, f"{entry_id}_{service_key}")},
|
||||||
|
ATTR_NAME: service,
|
||||||
|
ATTR_MANUFACTURER: "Ambee",
|
||||||
|
ATTR_ENTRY_TYPE: ENTRY_TYPE_SERVICE,
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> StateType:
|
def state(self) -> StateType:
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return getattr(self.coordinator.data, self._key) # type: ignore[no-any-return]
|
value = getattr(self.coordinator.data, self._sensor_key)
|
||||||
|
if isinstance(value, str):
|
||||||
@property
|
return value.lower()
|
||||||
def device_info(self) -> DeviceInfo:
|
return value # type: ignore[no-any-return]
|
||||||
"""Return device information about this Ambee Service."""
|
|
||||||
return {
|
|
||||||
ATTR_IDENTIFIERS: {(DOMAIN, f"{self._entry_id}_{self._service_key}")},
|
|
||||||
ATTR_NAME: self._service_name,
|
|
||||||
ATTR_MANUFACTURER: "Ambee",
|
|
||||||
}
|
|
||||||
|
10
homeassistant/components/ambee/strings.sensor.json
Normal file
10
homeassistant/components/ambee/strings.sensor.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"ambee__risk": {
|
||||||
|
"low": "Low",
|
||||||
|
"moderate": "Moderate",
|
||||||
|
"high": "High",
|
||||||
|
"very high": "Very High"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
homeassistant/components/ambee/translations/sensor.en.json
Normal file
10
homeassistant/components/ambee/translations/sensor.en.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"state": {
|
||||||
|
"ambee__risk": {
|
||||||
|
"high": "High",
|
||||||
|
"low": "Low",
|
||||||
|
"moderate": "Moderate",
|
||||||
|
"very high": "Very High"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
import json
|
import json
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
from ambee import AirQuality
|
from ambee import AirQuality, Pollen
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.ambee.const import DOMAIN
|
from homeassistant.components.ambee.const import DOMAIN
|
||||||
@ -34,6 +34,9 @@ def mock_ambee(aioclient_mock: AiohttpClientMocker):
|
|||||||
json.loads(load_fixture("ambee/air_quality.json"))
|
json.loads(load_fixture("ambee/air_quality.json"))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
client.pollen = AsyncMock(
|
||||||
|
return_value=Pollen.from_dict(json.loads(load_fixture("ambee/pollen.json")))
|
||||||
|
)
|
||||||
yield ambee_mock
|
yield ambee_mock
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,11 +29,11 @@ async def test_load_unload_config_entry(
|
|||||||
|
|
||||||
|
|
||||||
@patch(
|
@patch(
|
||||||
"homeassistant.components.ambee.Ambee.air_quality",
|
"homeassistant.components.ambee.Ambee.request",
|
||||||
side_effect=AmbeeConnectionError,
|
side_effect=AmbeeConnectionError,
|
||||||
)
|
)
|
||||||
async def test_config_entry_not_ready(
|
async def test_config_entry_not_ready(
|
||||||
mock_air_quality: MagicMock,
|
mock_request: MagicMock,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_config_entry: MockConfigEntry,
|
mock_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -42,5 +42,5 @@ async def test_config_entry_not_ready(
|
|||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert mock_air_quality.call_count == 1
|
assert mock_request.call_count == 1
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
@ -1,12 +1,26 @@
|
|||||||
"""Tests for the sensors provided by the Ambee integration."""
|
"""Tests for the sensors provided by the Ambee integration."""
|
||||||
from homeassistant.components.ambee.const import DOMAIN
|
from unittest.mock import AsyncMock
|
||||||
from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.ambee.const import (
|
||||||
|
DEVICE_CLASS_AMBEE_RISK,
|
||||||
|
DOMAIN,
|
||||||
|
ENTRY_TYPE_SERVICE,
|
||||||
|
)
|
||||||
|
from homeassistant.components.sensor import (
|
||||||
|
ATTR_STATE_CLASS,
|
||||||
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS,
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
|
ATTR_ICON,
|
||||||
ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_BILLION,
|
CONCENTRATION_PARTS_PER_BILLION,
|
||||||
|
CONCENTRATION_PARTS_PER_CUBIC_METER,
|
||||||
CONCENTRATION_PARTS_PER_MILLION,
|
CONCENTRATION_PARTS_PER_MILLION,
|
||||||
DEVICE_CLASS_CO,
|
DEVICE_CLASS_CO,
|
||||||
)
|
)
|
||||||
@ -25,11 +39,11 @@ async def test_air_quality(
|
|||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
state = hass.states.get("sensor.particulate_matter_2_5_mm")
|
state = hass.states.get("sensor.air_quality_particulate_matter_2_5")
|
||||||
entry = entity_registry.async_get("sensor.particulate_matter_2_5_mm")
|
entry = entity_registry.async_get("sensor.air_quality_particulate_matter_2_5")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_particulate_matter_2_5"
|
assert entry.unique_id == f"{entry_id}_air_quality_particulate_matter_2_5"
|
||||||
assert state.state == "3.14"
|
assert state.state == "3.14"
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Particulate Matter < 2.5 μm"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Particulate Matter < 2.5 μm"
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
@ -38,12 +52,13 @@ async def test_air_quality(
|
|||||||
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
)
|
)
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
state = hass.states.get("sensor.particulate_matter_10_mm")
|
state = hass.states.get("sensor.air_quality_particulate_matter_10")
|
||||||
entry = entity_registry.async_get("sensor.particulate_matter_10_mm")
|
entry = entity_registry.async_get("sensor.air_quality_particulate_matter_10")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_particulate_matter_10"
|
assert entry.unique_id == f"{entry_id}_air_quality_particulate_matter_10"
|
||||||
assert state.state == "5.24"
|
assert state.state == "5.24"
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Particulate Matter < 10 μm"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Particulate Matter < 10 μm"
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
@ -52,12 +67,13 @@ async def test_air_quality(
|
|||||||
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
== CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||||
)
|
)
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
state = hass.states.get("sensor.sulphur_dioxide_so2")
|
state = hass.states.get("sensor.air_quality_sulphur_dioxide")
|
||||||
entry = entity_registry.async_get("sensor.sulphur_dioxide_so2")
|
entry = entity_registry.async_get("sensor.air_quality_sulphur_dioxide")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_sulphur_dioxide"
|
assert entry.unique_id == f"{entry_id}_air_quality_sulphur_dioxide"
|
||||||
assert state.state == "0.031"
|
assert state.state == "0.031"
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Sulphur Dioxide (SO2)"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Sulphur Dioxide (SO2)"
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
@ -66,12 +82,13 @@ async def test_air_quality(
|
|||||||
== CONCENTRATION_PARTS_PER_BILLION
|
== CONCENTRATION_PARTS_PER_BILLION
|
||||||
)
|
)
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
state = hass.states.get("sensor.nitrogen_dioxide_no2")
|
state = hass.states.get("sensor.air_quality_nitrogen_dioxide")
|
||||||
entry = entity_registry.async_get("sensor.nitrogen_dioxide_no2")
|
entry = entity_registry.async_get("sensor.air_quality_nitrogen_dioxide")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_nitrogen_dioxide"
|
assert entry.unique_id == f"{entry_id}_air_quality_nitrogen_dioxide"
|
||||||
assert state.state == "0.66"
|
assert state.state == "0.66"
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Nitrogen Dioxide (NO2)"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Nitrogen Dioxide (NO2)"
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
@ -80,12 +97,13 @@ async def test_air_quality(
|
|||||||
== CONCENTRATION_PARTS_PER_BILLION
|
== CONCENTRATION_PARTS_PER_BILLION
|
||||||
)
|
)
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
state = hass.states.get("sensor.ozone")
|
state = hass.states.get("sensor.air_quality_ozone")
|
||||||
entry = entity_registry.async_get("sensor.ozone")
|
entry = entity_registry.async_get("sensor.air_quality_ozone")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_ozone"
|
assert entry.unique_id == f"{entry_id}_air_quality_ozone"
|
||||||
assert state.state == "17.067"
|
assert state.state == "17.067"
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Ozone"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Ozone"
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
@ -94,12 +112,13 @@ async def test_air_quality(
|
|||||||
== CONCENTRATION_PARTS_PER_BILLION
|
== CONCENTRATION_PARTS_PER_BILLION
|
||||||
)
|
)
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
state = hass.states.get("sensor.carbon_monoxide_co")
|
state = hass.states.get("sensor.air_quality_carbon_monoxide")
|
||||||
entry = entity_registry.async_get("sensor.carbon_monoxide_co")
|
entry = entity_registry.async_get("sensor.air_quality_carbon_monoxide")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_carbon_monoxide"
|
assert entry.unique_id == f"{entry_id}_air_quality_carbon_monoxide"
|
||||||
assert state.state == "0.105"
|
assert state.state == "0.105"
|
||||||
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_CO
|
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_CO
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Carbon Monoxide (CO)"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Carbon Monoxide (CO)"
|
||||||
@ -108,17 +127,19 @@ async def test_air_quality(
|
|||||||
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
== CONCENTRATION_PARTS_PER_MILLION
|
== CONCENTRATION_PARTS_PER_MILLION
|
||||||
)
|
)
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
state = hass.states.get("sensor.air_quality_index_aqi")
|
state = hass.states.get("sensor.air_quality_air_quality_index")
|
||||||
entry = entity_registry.async_get("sensor.air_quality_index_aqi")
|
entry = entity_registry.async_get("sensor.air_quality_air_quality_index")
|
||||||
assert entry
|
assert entry
|
||||||
assert state
|
assert state
|
||||||
assert entry.unique_id == f"{entry_id}_air_quality_index"
|
assert entry.unique_id == f"{entry_id}_air_quality_air_quality_index"
|
||||||
assert state.state == "13"
|
assert state.state == "13"
|
||||||
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Air Quality Index (AQI)"
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Air Quality Index (AQI)"
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
assert ATTR_DEVICE_CLASS not in state.attributes
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||||
|
assert ATTR_ICON not in state.attributes
|
||||||
|
|
||||||
assert entry.device_id
|
assert entry.device_id
|
||||||
device_entry = device_registry.async_get(entry.device_id)
|
device_entry = device_registry.async_get(entry.device_id)
|
||||||
@ -126,5 +147,203 @@ async def test_air_quality(
|
|||||||
assert device_entry.identifiers == {(DOMAIN, f"{entry_id}_air_quality")}
|
assert device_entry.identifiers == {(DOMAIN, f"{entry_id}_air_quality")}
|
||||||
assert device_entry.manufacturer == "Ambee"
|
assert device_entry.manufacturer == "Ambee"
|
||||||
assert device_entry.name == "Air Quality"
|
assert device_entry.name == "Air Quality"
|
||||||
|
assert device_entry.entry_type == ENTRY_TYPE_SERVICE
|
||||||
assert not device_entry.model
|
assert not device_entry.model
|
||||||
assert not device_entry.sw_version
|
assert not device_entry.sw_version
|
||||||
|
|
||||||
|
|
||||||
|
async def test_pollen(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
init_integration: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Ambee Pollen sensors."""
|
||||||
|
entry_id = init_integration.entry_id
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.pollen_grass")
|
||||||
|
entry = entity_registry.async_get("sensor.pollen_grass")
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_grass"
|
||||||
|
assert state.state == "190"
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Grass Pollen"
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:grass"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_PARTS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.pollen_tree")
|
||||||
|
entry = entity_registry.async_get("sensor.pollen_tree")
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_tree"
|
||||||
|
assert state.state == "127"
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Tree Pollen"
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:tree"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_PARTS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.pollen_weed")
|
||||||
|
entry = entity_registry.async_get("sensor.pollen_weed")
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_weed"
|
||||||
|
assert state.state == "95"
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Weed Pollen"
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:sprout"
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_PARTS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.pollen_grass_risk")
|
||||||
|
entry = entity_registry.async_get("sensor.pollen_grass_risk")
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_grass_risk"
|
||||||
|
assert state.state == "high"
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_AMBEE_RISK
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Grass Pollen Risk"
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:grass"
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.pollen_tree_risk")
|
||||||
|
entry = entity_registry.async_get("sensor.pollen_tree_risk")
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_tree_risk"
|
||||||
|
assert state.state == "moderate"
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_AMBEE_RISK
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Tree Pollen Risk"
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:tree"
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||||
|
|
||||||
|
state = hass.states.get("sensor.pollen_weed_risk")
|
||||||
|
entry = entity_registry.async_get("sensor.pollen_weed_risk")
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_weed_risk"
|
||||||
|
assert state.state == "high"
|
||||||
|
assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_AMBEE_RISK
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "Weed Pollen Risk"
|
||||||
|
assert state.attributes.get(ATTR_ICON) == "mdi:sprout"
|
||||||
|
assert ATTR_STATE_CLASS not in state.attributes
|
||||||
|
assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes
|
||||||
|
|
||||||
|
assert entry.device_id
|
||||||
|
device_entry = device_registry.async_get(entry.device_id)
|
||||||
|
assert device_entry
|
||||||
|
assert device_entry.identifiers == {(DOMAIN, f"{entry_id}_pollen")}
|
||||||
|
assert device_entry.manufacturer == "Ambee"
|
||||||
|
assert device_entry.name == "Pollen"
|
||||||
|
assert device_entry.entry_type == ENTRY_TYPE_SERVICE
|
||||||
|
assert not device_entry.model
|
||||||
|
assert not device_entry.sw_version
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"entity_id",
|
||||||
|
(
|
||||||
|
"sensor.pollen_grass_poaceae",
|
||||||
|
"sensor.pollen_tree_alder",
|
||||||
|
"sensor.pollen_tree_birch",
|
||||||
|
"sensor.pollen_tree_cypress",
|
||||||
|
"sensor.pollen_tree_elm",
|
||||||
|
"sensor.pollen_tree_hazel",
|
||||||
|
"sensor.pollen_tree_oak",
|
||||||
|
"sensor.pollen_tree_pine",
|
||||||
|
"sensor.pollen_tree_plane",
|
||||||
|
"sensor.pollen_tree_poplar",
|
||||||
|
"sensor.pollen_weed_chenopod",
|
||||||
|
"sensor.pollen_weed_mugwort",
|
||||||
|
"sensor.pollen_weed_nettle",
|
||||||
|
"sensor.pollen_weed_ragweed",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def test_pollen_disabled_by_default(
|
||||||
|
hass: HomeAssistant, init_integration: MockConfigEntry, entity_id: str
|
||||||
|
) -> None:
|
||||||
|
"""Test the Ambee Pollen sensors that are disabled by default."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
entry = entity_registry.async_get(entity_id)
|
||||||
|
assert entry
|
||||||
|
assert entry.disabled
|
||||||
|
assert entry.disabled_by == er.DISABLED_INTEGRATION
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"key,icon,name,value",
|
||||||
|
[
|
||||||
|
("grass_poaceae", "mdi:grass", "Poaceae Grass Pollen", "190"),
|
||||||
|
("tree_alder", "mdi:tree", "Alder Tree Pollen", "0"),
|
||||||
|
("tree_birch", "mdi:tree", "Birch Tree Pollen", "35"),
|
||||||
|
("tree_cypress", "mdi:tree", "Cypress Tree Pollen", "0"),
|
||||||
|
("tree_elm", "mdi:tree", "Elm Tree Pollen", "0"),
|
||||||
|
("tree_hazel", "mdi:tree", "Hazel Tree Pollen", "0"),
|
||||||
|
("tree_oak", "mdi:tree", "Oak Tree Pollen", "55"),
|
||||||
|
("tree_pine", "mdi:tree", "Pine Tree Pollen", "30"),
|
||||||
|
("tree_plane", "mdi:tree", "Plane Tree Pollen", "5"),
|
||||||
|
("tree_poplar", "mdi:tree", "Poplar Tree Pollen", "0"),
|
||||||
|
("weed_chenopod", "mdi:sprout", "Chenopod Weed Pollen", "0"),
|
||||||
|
("weed_mugwort", "mdi:sprout", "Mugwort Weed Pollen", "1"),
|
||||||
|
("weed_nettle", "mdi:sprout", "Nettle Weed Pollen", "88"),
|
||||||
|
("weed_ragweed", "mdi:sprout", "Ragweed Weed Pollen", "3"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_pollen_enable_disable_by_defaults(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_ambee: AsyncMock,
|
||||||
|
key: str,
|
||||||
|
icon: str,
|
||||||
|
name: str,
|
||||||
|
value: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Ambee Pollen sensors that are disabled by default."""
|
||||||
|
entry_id = mock_config_entry.entry_id
|
||||||
|
entity_id = f"{SENSOR_DOMAIN}.pollen_{key}"
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
# Pre-create registry entry for disabled by default sensor
|
||||||
|
entity_registry.async_get_or_create(
|
||||||
|
SENSOR_DOMAIN,
|
||||||
|
DOMAIN,
|
||||||
|
f"{entry_id}_pollen_{key}",
|
||||||
|
suggested_object_id=f"pollen_{key}",
|
||||||
|
disabled_by=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
entry = entity_registry.async_get(entity_id)
|
||||||
|
assert entry
|
||||||
|
assert state
|
||||||
|
assert entry.unique_id == f"{entry_id}_pollen_{key}"
|
||||||
|
assert state.state == value
|
||||||
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == name
|
||||||
|
assert state.attributes.get(ATTR_ICON) == icon
|
||||||
|
assert state.attributes.get(ATTR_STATE_CLASS) == STATE_CLASS_MEASUREMENT
|
||||||
|
assert (
|
||||||
|
state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||||
|
== CONCENTRATION_PARTS_PER_CUBIC_METER
|
||||||
|
)
|
||||||
|
assert ATTR_DEVICE_CLASS not in state.attributes
|
||||||
|
43
tests/fixtures/ambee/pollen.json
vendored
Normal file
43
tests/fixtures/ambee/pollen.json
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"message": "Success",
|
||||||
|
"lat": 52.42,
|
||||||
|
"lng": 6.42,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"Count": {
|
||||||
|
"grass_pollen": 190,
|
||||||
|
"tree_pollen": 127,
|
||||||
|
"weed_pollen": 95
|
||||||
|
},
|
||||||
|
"Risk": {
|
||||||
|
"grass_pollen": "High",
|
||||||
|
"tree_pollen": "Moderate",
|
||||||
|
"weed_pollen": "High"
|
||||||
|
},
|
||||||
|
"Species": {
|
||||||
|
"Grass": {
|
||||||
|
"Grass / Poaceae": 190
|
||||||
|
},
|
||||||
|
"Others": 5,
|
||||||
|
"Tree": {
|
||||||
|
"Alder": 0,
|
||||||
|
"Birch": 35,
|
||||||
|
"Cypress": 0,
|
||||||
|
"Elm": 0,
|
||||||
|
"Hazel": 0,
|
||||||
|
"Oak": 55,
|
||||||
|
"Pine": 30,
|
||||||
|
"Plane": 5,
|
||||||
|
"Poplar / Cottonwood": 0
|
||||||
|
},
|
||||||
|
"Weed": {
|
||||||
|
"Chenopod": 0,
|
||||||
|
"Mugwort": 1,
|
||||||
|
"Nettle": 88,
|
||||||
|
"Ragweed": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"updatedAt": "2021-06-09T16:24:27.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user