1 """Support for Awair sensors."""
3 from __future__
import annotations
5 from dataclasses
import dataclass
6 from typing
import Any, cast
8 from python_awair.air_data
import AirData
9 from python_awair.devices
import AwairBaseDevice, AwairLocalDevice
14 SensorEntityDescription,
21 CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
22 CONCENTRATION_PARTS_PER_BILLION,
23 CONCENTRATION_PARTS_PER_MILLION,
49 from .coordinator
import AwairConfigEntry, AwairDataUpdateCoordinator
51 DUST_ALIASES = [API_PM25, API_PM10]
54 @dataclass(frozen=True, kw_only=True)
56 """Describes Awair sensor entity."""
63 native_unit_of_measurement=PERCENTAGE,
64 translation_key=
"score",
65 unique_id_tag=
"score",
66 state_class=SensorStateClass.MEASUREMENT,
69 SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
72 device_class=SensorDeviceClass.HUMIDITY,
73 native_unit_of_measurement=PERCENTAGE,
74 unique_id_tag=
"HUMID",
75 state_class=SensorStateClass.MEASUREMENT,
79 device_class=SensorDeviceClass.ILLUMINANCE,
80 native_unit_of_measurement=LIGHT_LUX,
81 unique_id_tag=
"illuminance",
82 state_class=SensorStateClass.MEASUREMENT,
86 device_class=SensorDeviceClass.SOUND_PRESSURE,
87 native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
88 translation_key=
"sound_level",
89 unique_id_tag=
"sound_level",
90 state_class=SensorStateClass.MEASUREMENT,
94 device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
95 native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
97 state_class=SensorStateClass.MEASUREMENT,
101 device_class=SensorDeviceClass.TEMPERATURE,
102 native_unit_of_measurement=UnitOfTemperature.CELSIUS,
103 unique_id_tag=
"TEMP",
104 state_class=SensorStateClass.MEASUREMENT,
108 device_class=SensorDeviceClass.CO2,
109 native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
111 state_class=SensorStateClass.MEASUREMENT,
115 SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
118 device_class=SensorDeviceClass.PM25,
119 native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
120 unique_id_tag=
"PM25",
121 state_class=SensorStateClass.MEASUREMENT,
125 device_class=SensorDeviceClass.PM10,
126 native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
127 unique_id_tag=
"PM10",
128 state_class=SensorStateClass.MEASUREMENT,
135 config_entry: AwairConfigEntry,
136 async_add_entities: AddEntitiesCallback,
138 """Set up Awair sensor entity based on a config entry."""
139 coordinator = config_entry.runtime_data
142 for result
in coordinator.data.values():
144 entities.append(
AwairSensor(result.device, coordinator, SENSOR_TYPE_SCORE))
145 device_sensors = result.air_data.sensors.keys()
148 AwairSensor(result.device, coordinator, description)
149 for description
in (*SENSOR_TYPES, *SENSOR_TYPES_DUST)
150 if description.key
in device_sensors
160 if API_DUST
in device_sensors:
163 AwairSensor(result.device, coordinator, description)
164 for description
in SENSOR_TYPES_DUST
172 """Defines an Awair sensor entity."""
174 entity_description: AwairSensorEntityDescription
175 _attr_has_entity_name =
True
176 _attr_attribution = ATTRIBUTION
180 device: AwairBaseDevice,
181 coordinator: AwairDataUpdateCoordinator,
182 description: AwairSensorEntityDescription,
184 """Set up an individual AwairSensor."""
191 """Return the uuid as the unique_id."""
201 and API_DUST
in self.
_air_data_air_data.sensors
203 unique_id_tag =
"DUST"
205 return f
"{self._device.uuid}_{unique_id_tag}"
209 """Determine if the sensor is available based on API results."""
211 if self.coordinator.last_update_success
and self.
_air_data_air_data:
214 if sensor_type
in self.
_air_data_air_data.sensors:
219 if sensor_type
in DUST_ALIASES
and API_DUST
in self.
_air_data_air_data.sensors:
223 if sensor_type == API_SCORE:
232 """Return the state, rounding off to reasonable values."""
240 if sensor_type == API_SCORE:
242 elif sensor_type
in DUST_ALIASES
and API_DUST
in self.
_air_data_air_data.sensors:
243 state = self.
_air_data_air_data.sensors.dust
245 state = self.
_air_data_air_data.sensors[sensor_type]
247 if sensor_type
in {API_VOC, API_SCORE}:
250 if sensor_type == API_TEMP:
251 return round(state, 1)
253 return round(state, 2)
257 """Return the Awair Index alongside state attributes.
259 The Awair Index is a subjective score ranging from 0-4 (inclusive) that
260 is is used by the Awair app when displaying the relative "safety" of a
261 given measurement. Each value is mapped to a color indicating the safety:
269 The API indicates that both positive and negative values may be returned,
270 but the negative values are mapped to identical colors as the positive values.
271 Knowing that, we just return the absolute value of a given index so that
272 users don't have to handle positive/negative values that ultimately "mean"
275 https://docs.developer.getawair.com/?version=latest#awair-score-and-index
278 attrs: dict[str, Any] = {}
281 if sensor_type
in self.
_air_data_air_data.indices:
282 attrs[
"awair_index"] = abs(self.
_air_data_air_data.indices[sensor_type])
283 elif sensor_type
in DUST_ALIASES
and API_DUST
in self.
_air_data_air_data.indices:
284 attrs[
"awair_index"] = abs(self.
_air_data_air_data.indices.dust)
290 """Device information."""
292 identifiers={(DOMAIN, self.
_device_device.uuid)},
293 manufacturer=
"Awair",
294 model=self.
_device_device.model,
295 model_id=self.
_device_device.device_type,
298 or cast(ConfigEntry, self.coordinator.config_entry).title
299 or f
"{self._device.model} ({self._device.device_id})"
303 if self.
_device_device.mac_address:
304 info[ATTR_CONNECTIONS] = {
305 (dr.CONNECTION_NETWORK_MAC, self.
_device_device.mac_address)
308 if isinstance(self.
_device_device, AwairLocalDevice):
309 info[ATTR_SW_VERSION] = self.
_device_device.fw_version
315 """Return the latest data for our device, or None."""
316 if result := self.coordinator.data.get(self.
_device_device.uuid):
317 return result.air_data
float|None native_value(self)
DeviceInfo device_info(self)
AirData|None _air_data(self)
None __init__(self, AwairBaseDevice device, AwairDataUpdateCoordinator coordinator, AwairSensorEntityDescription description)
dict[str, Any] extra_state_attributes(self)
None async_setup_entry(HomeAssistant hass, AwairConfigEntry config_entry, AddEntitiesCallback async_add_entities)