core/homeassistant/components/airq/sensor.py

478 lines
16 KiB
Python
Raw Normal View History

Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
"""Definition of air-Q sensor platform."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Literal
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfPressure,
UnitOfSoundPressure,
UnitOfTemperature,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirQCoordinator
from .const import (
ACTIVITY_BECQUEREL_PER_CUBIC_METER,
CONCENTRATION_GRAMS_PER_CUBIC_METER,
DOMAIN,
)
_LOGGER = logging.getLogger(__name__)
@dataclass
class AirQEntityDescriptionMixin:
"""Class for keys required by AirQ entity."""
value: Callable[[dict], float | int | None]
@dataclass
class AirQEntityDescription(SensorEntityDescription, AirQEntityDescriptionMixin):
"""Describes AirQ sensor entity."""
# Keys must match those in the data dictionary
SENSOR_TYPES: list[AirQEntityDescription] = [
AirQEntityDescription(
key="c2h4o",
name="Acetaldehyde",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c2h4o"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="nh3_MR100",
name="Ammonia",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("nh3_MR100"),
),
AirQEntityDescription(
key="ash3",
name="Arsine",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ash3"),
),
AirQEntityDescription(
key="br2",
name="Bromine",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("br2"),
),
AirQEntityDescription(
key="ch4s",
name="CH4S",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch4s"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="cl2_M20",
name="Chlorine",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("cl2_M20"),
),
AirQEntityDescription(
key="clo2",
name="ClO2",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("clo2"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="co",
name="CO",
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("co"),
),
AirQEntityDescription(
key="co2",
name="CO2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("co2"),
),
AirQEntityDescription(
key="cs2",
name="CS2",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("cs2"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="dewpt",
name="Dew point",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("dewpt"),
device_class=SensorDeviceClass.TEMPERATURE,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
),
AirQEntityDescription(
key="ethanol",
name="Ethanol",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ethanol"),
),
AirQEntityDescription(
key="c2h4",
name="Ethylene",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c2h4"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="ch2o_M10",
name="Formaldehyde",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch2o_M10"),
),
AirQEntityDescription(
key="f2",
name="Fluorine",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("f2"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="h2s",
name="H2S",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("h2s"),
),
AirQEntityDescription(
key="hcl",
name="HCl",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("hcl"),
),
AirQEntityDescription(
key="hcn",
name="HCN",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("hcn"),
),
AirQEntityDescription(
key="hf",
name="HF",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("hf"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="health",
name="Health Index",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:heart-pulse",
value=lambda data: data.get("health", 0.0) / 10.0,
),
AirQEntityDescription(
key="humidity",
name="Humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("humidity"),
),
AirQEntityDescription(
key="humidity_abs",
name="Absolute humidity",
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("humidity_abs"),
icon="mdi:water",
),
AirQEntityDescription(
key="h2_M1000",
name="Hydrogen",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("h2_M1000"),
),
AirQEntityDescription(
key="h2o2",
name="Hydrogen peroxide",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("h2o2"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="ch4_MIPEX",
name="Methane",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch4_MIPEX"),
),
AirQEntityDescription(
key="n2o",
name="N2O",
device_class=SensorDeviceClass.NITROUS_OXIDE,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("n2o"),
),
AirQEntityDescription(
key="no_M250",
name="NO",
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("no_M250"),
),
AirQEntityDescription(
key="no2",
name="NO2",
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("no2"),
),
AirQEntityDescription(
key="acid_M100",
name="Organic acid",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("acid_M100"),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
),
AirQEntityDescription(
key="oxygen",
name="Oxygen",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("oxygen"),
icon="mdi:leaf",
),
AirQEntityDescription(
key="o3",
name="Ozone",
device_class=SensorDeviceClass.OZONE,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("o3"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="performance",
name="Performance Index",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:head-check",
value=lambda data: data.get("performance", 0.0) / 10.0,
),
AirQEntityDescription(
key="ph3",
name="PH3",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ph3"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="pm1",
name="PM1",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pm1"),
icon="mdi:dots-hexagon",
),
AirQEntityDescription(
key="pm2_5",
name="PM2.5",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pm2_5"),
icon="mdi:dots-hexagon",
),
AirQEntityDescription(
key="pm10",
name="PM10",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pm10"),
icon="mdi:dots-hexagon",
),
AirQEntityDescription(
key="pressure",
name="Pressure",
device_class=SensorDeviceClass.PRESSURE,
native_unit_of_measurement=UnitOfPressure.HPA,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pressure"),
),
AirQEntityDescription(
key="pressure_rel",
name="Relative pressure",
native_unit_of_measurement=UnitOfPressure.HPA,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pressure_rel"),
device_class=SensorDeviceClass.PRESSURE,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
),
AirQEntityDescription(
key="c3h8_MIPEX",
name="Propane",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c3h8_MIPEX"),
),
AirQEntityDescription(
key="refigerant",
name="Refrigerant",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("refigerant"),
),
AirQEntityDescription(
key="sih4",
name="SiH4",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("sih4"),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
AirQEntityDescription(
key="so2",
name="SO2",
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("so2"),
),
AirQEntityDescription(
key="sound",
name="Noise",
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("sound"),
device_class=SensorDeviceClass.SOUND_PRESSURE,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
),
AirQEntityDescription(
key="sound_max",
name="Noise (Maximum)",
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("sound_max"),
device_class=SensorDeviceClass.SOUND_PRESSURE,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
),
AirQEntityDescription(
key="radon",
name="Radon",
native_unit_of_measurement=ACTIVITY_BECQUEREL_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("radon"),
icon="mdi:radioactive",
),
AirQEntityDescription(
key="temperature",
name="Temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("temperature"),
),
AirQEntityDescription(
key="tvoc",
name="VOC",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("tvoc"),
),
AirQEntityDescription(
key="tvoc_ionsc",
name="VOC (Industrial)",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("tvoc_ionsc"),
),
AirQEntityDescription(
key="virus",
name="Virus Index",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
icon="mdi:virus-off",
value=lambda data: data.get("virus", 0.0),
),
Add air-Q integration (air quality sensors) (#76999) * Added initial files for air-Q integration * Allow FIXME comments in pylint (temporary) Also reintroduce --ignore-missing-annotations=y * Set up air-q entry to connect to aioairq's API (initial attempt) Also add necessary constants * Implement a class for sensors and its update logic Very early stage, WIP * Zeroconf and authentication are working * Complete the bare-bone minimal working version Specifically, make AirQSensor update its values. * Handle invalid authentication gracefully * Handle ClientConnectionError gracefully * Add field hint for the login form The key in the schema, which defines the form in `ConfigFlow.async_show_form` is looked up in both `airq/strings/json` and `airq/translations/en.json`. I am still not 100% sure how this lookup is performed. WIP * Minor cleanups * Extend sensor list to all supported by SensorDeviceClass Also manage warming up sensors * aioairq is published to PyPI and mentioned in requirements * Reordered constants and list content alphabetically As required by style guides. Also turned SENSOR_TYPES to a list * Updated file docstrings for a dev unfamiliar w/homeassistant like myself * Adding a bit of logging for the integration setup process * Expose scan interval & smoothing flag Also streamline test_authentication in config_flow. * Fix a type annotation mistake * Use as many constants from homeassistant.const as possible My only concern is using CONST_IP_ADDRESS = "ip_address" for smth which stands for both IP address and mDNS... * Temporarily rollback ConfigFlow.async_step_configure and use defaults TODO: implement OptionFlowHandler instead * Define custom Coordinator, w subset of airq config The latter is then accessed from entity / sensor constructors to define correct DeviceInfo * Provide translations to de & fr + minor changes to en * Provide translations to ru + a minor en changes * Make translation a little more helpful and polite * Fix devicename and entry title * Remove stale commented out code * Test config_flow At this point two helper functions which interact with the external library are not tested * Clean up unrelated and meant as temporary changes * Clean up unnecessary comments meant for internal use * Move fetching config to a dedicated async coordinator method As opposed to it being a potentially poorly justified step in async_setup_entry * Remove zeroconf support since it is not yet ready * Remove translations other than en * Remove unnecessary comments, manifest.json entries, and constants * Improve exception handling - `_LOGGER` uses `debug` and not `error` levels. - Drop `ClientConnect` and catch `aiohttop.ClientConnectError` directly - Drop `Exception` as it is not expected from `aioairq` (remove the corresponding test too) * Drop strings for obsolete errors and steps Specifically, `unknown` error isn't caught any more. `configure` step has also been removed. * Refactor en.json to be consistent with strings.json * Move target_route from a coordinator argument to a constant At this point a user cannot configure the target_route route, thus it does not make sense to expose it half-heartedly in `AirQCoordinator.__init__`, since it cannot be accessed. * Fix an async call in `AirQCoordinator.async_setup_entry` * Refactor underlying aioairq API - Use `homeassistant.helpers.aiohttp.async_get_clientsession` and pass a single persistent session to `aioariq.AirQ.__init__` - `aioairq.AirQ.fetch_device_info` now returns a `DeviceInfo` object heavily inspired and almost compatible with `homeassistant.helpers.entity.DeviceInfo`. Make heavier use of this object and define a single `DeviceInfo` in the `AirQCoordinator` (instead of recreating the same object for each sensor of the device in `sensor.AirQSensor`) - Drop two helper functions in `config_flow.py` and operate on `aioariq.AirQ` methods directly * Fix the version of aioairq * Add 15 more sensors + icons * Remove cnt* & TypPS, change units of health & performance * Add 12 more sensors * Add a missing icon * Gracefully handle device not being available on setup If the device and the host are not on the same WiFi, ServerTimeoutError is raised, which is caught by ClientConnectionError. If the device is powered off, ClientConnectionError is expected. In both cases, ConfigEntryNotReady is raised, as prescribed by the docs. Newer version of aioairq times-out far quicker than the default 5 mins. * Rename two sensors * Validate provided IP address / mDNS aioairq now raises InvalidInput if provided IP / mDNS does not seem valid. Handle this exception correctly * Apply suggestions from code review Clean up the comments and rename the logger Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during the first refresh - Fetched info is stored in AirQCoordinator.device_info. - In `AirQSensor.native_value` only multiply by the factor if the sensor reading is not None - Fix the tests for ConfigFlow for aioairq==0.2.3. Specifically make the dummy data pass the new validation step upstream + add a test which fails it * Drop custom device classes for now * Apply suggestions from code review Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Only fetch device info during ConfigFlow.async_step_user Store the result obtained by `airq.fetch_device_info` it in `config_entry.data`. Pass the entire config entry to `AirQCoordinator` and build the entire `homeassistant.helpers.entity.DeviceInfo` in the `AirQCoordinator.__init__`. This makes `AirQCoordinator._async_fetch_device_info` and overloaded `AirQCoordinator._async_config_entry_first_refresh` obsolete. Bump aioairq version. Turn update_interval from `AirQCoordinator.__init__` argument into a contestant. * Custom entity description exposing a hook to modify sensor value Use a `AirQEntityDescription` with a callable `value_fn` which allows to change the sensor value retrieved from the device. Note that the callable does not handle data retrieval itself (even from `coordinator.data`). Instead it is purely a hook to transform obtained value. * Avoid duplicated use of unique_id Device info is fetched during the `ConfigFlow.async_user_step`. `unique_id` is taken from the device info and is **not** stored in `config_entry.data`. Subsequently `config_entry.unique_id` is used instead. * Drop unnecessary try-except Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> * Clarify the use of value_transform_fn * Refactor the use of lambdas in AirQEntityDescription Now it is the job of the callable under `value` to get the sensor reading from the coordinator's data. Factoring this functionality into a callback decouples the key of the description from the key of dict, returned by the API, so `AirQEntityDescription` no longer requires its key to be set to smth clearly internal (e.g. `nh3_MR100`). * Use a callback to update native_value Since all `native_value`s are updated synchronously, it can as well be done in a callback for better state consistency (right?) * Revert the description keys to match data keys Must match given the current way of identifying available sensors. On a broader scale, they must match to be able to relate the descriptions to sensors, unless a separate lookup table is maintained. * Reduce number of loops when adding sensors Filtering warming up sensors and non-sensor keys can be combined with adding entities. * Remove obsolete imports * Update integrations.json * Add integration_type Integration supports multiple devices => hub Co-authored-by: dl2080 <daniel.lehmann@runbox.com> Co-authored-by: Erik Montnemery <erik@montnemery.com> Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com> Co-authored-by: Daniel Lehmann <43613560+dl2080@users.noreply.github.com> Co-authored-by: Martin Selbmann <job@martin-selbmann.de>
2022-11-03 22:13:57 +00:00
]
async def async_setup_entry(
hass: HomeAssistant,
config: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensor entities based on a config entry."""
coordinator = hass.data[DOMAIN][config.entry_id]
entities: list[AirQSensor] = []
device_status: dict[str, str] | Literal["OK"] = coordinator.data["Status"]
for description in SENSOR_TYPES:
if description.key not in coordinator.data:
if isinstance(
device_status, dict
) and "sensor still in warm up phase" in device_status.get(
description.key, "OK"
):
# warming up sensors do not contribute keys to coordinator.data
# but still must be added
_LOGGER.debug("Following sensor is warming up: %s", description.key)
else:
continue
entities.append(AirQSensor(coordinator, description))
async_add_entities(entities)
class AirQSensor(CoordinatorEntity, SensorEntity):
"""Representation of a Sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AirQCoordinator,
description: AirQEntityDescription,
) -> None:
"""Initialize a single sensor."""
super().__init__(coordinator)
self.entity_description: AirQEntityDescription = description
self._attr_device_info = coordinator.device_info
self._attr_unique_id = f"{coordinator.device_id}_{description.key}"
self._attr_native_value = description.value(coordinator.data)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_native_value = self.entity_description.value(self.coordinator.data)
self.async_write_ha_state()