Compare commits

..

3 Commits

Author SHA1 Message Date
J. Nick Koston ab7a140c0e Note legacy-collision migration resolves by iteration order
When two entities collided on the same legacy unique id (the bug this
fixes) only one registry entry exists, so document that the first iterated
info claims it and the rest get fresh version 3 ids.
2026-06-26 09:06:52 +02:00
copilot-swe-agent[bot] 9b78e32f59 Merge branch 'dev' into fix_esphome_utf8_unique_id_collisions 2026-06-26 06:03:52 +00:00
J. Nick Koston 599b123f98 Fix ESPHome UTF-8 unique id collisions
The unique id was built from the mangled object_id which lowercases the
name, swaps spaces for underscores, and replaces every non ASCII character
with an underscore, so any UTF-8 name collapsed to underscores and distinct
entities collided; for example 温度传感器 and 湿度传感器 both became ___.

This switches to the version 3 unique id format from aioesphomeapi,
{mac}/{device_id}/{platform}/{name}, which keeps the raw name and namespaces
entities by sub-device so collisions no longer happen. Existing entities are
migrated from the legacy format on connect, keeping their entity_id and
settings; a version 3 id that already exists is left untouched so a downgrade
then upgrade keeps the original entity.

The device aware id handling now lives in aioesphomeapi via
build_device_unique_id instead of being duplicated here.
2026-06-25 14:14:44 +02:00
298 changed files with 6423 additions and 9190 deletions
+4 -4
View File
@@ -31,7 +31,7 @@
# - GITHUB_TOKEN
#
# Custom actions used:
# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
@@ -155,7 +155,7 @@ jobs:
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
- name: Checkout .github and .agents folders
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
sparse-checkout: |
@@ -403,7 +403,7 @@ jobs:
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
} >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
- name: Create gh-aw temp directory
@@ -1234,7 +1234,7 @@ jobs:
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false
# --- Threat Detection ---
Generated
+2
View File
@@ -181,6 +181,7 @@ CLAUDE.md @home-assistant/core
/tests/components/asuswrt/ @kennedyshead @ollo69 @Vaskivskyi
/homeassistant/components/atag/ @MatsNL
/tests/components/atag/ @MatsNL
/homeassistant/components/aten_pe/ @mtdcr
/homeassistant/components/atome/ @baqs
/homeassistant/components/august/ @bdraco
/tests/components/august/ @bdraco
@@ -1986,6 +1987,7 @@ CLAUDE.md @home-assistant/core
/tests/components/waterfurnace/ @sdague @masterkoppa
/homeassistant/components/watergate/ @adam-the-hero
/tests/components/watergate/ @adam-the-hero
/homeassistant/components/watson_tts/ @rutkai
/homeassistant/components/watts/ @theobld-ww @devender-verma-ww @ssi-spyro
/tests/components/watts/ @theobld-ww @devender-verma-ww @ssi-spyro
/homeassistant/components/watttime/ @bachya
+3 -36
View File
@@ -66,7 +66,6 @@ from .const import (
BASE_PLATFORMS,
FORMAT_DATETIME,
KEY_DATA_LOGGING as DATA_LOGGING,
KEY_DATA_LOGGING_DISABLED_REASON as DATA_LOGGING_DISABLED_REASON,
SIGNAL_BOOTSTRAP_INTEGRATIONS,
)
from .core_config import async_process_ha_core_config
@@ -130,11 +129,6 @@ SETUP_ORDER_SORT_KEY = partial(contains, BASE_PLATFORMS)
ERROR_LOG_FILENAME = "home-assistant.log"
ENV_DISABLE_LOG_FILE = "HA_DISABLE_LOG_FILE"
ENV_DUPLICATE_LOG_FILE = "HA_DUPLICATE_LOG_FILE"
ENV_SUPERVISOR = "SUPERVISOR"
LOG_FILE_DISABLED_REASON_ENVIRONMENT = "environment"
LOG_FILE_DISABLED_REASON_SUPERVISOR = "supervisor"
# hass.data key for logging information.
DATA_REGISTRIES_LOADED: HassKey[None] = HassKey("bootstrap_registries_loaded")
@@ -648,12 +642,10 @@ async def async_enable_logging(
logger.setLevel(logging.INFO if verbose else logging.WARNING)
if log_file is None:
disabled_log_file_reason = _log_file_disabled_reason()
default_log_path = hass.config.path(ERROR_LOG_FILENAME)
if disabled_log_file_reason:
if "SUPERVISOR" in os.environ and "HA_DUPLICATE_LOG_FILE" not in os.environ:
# Rename the default log file if it exists, since previous versions created
# it before Supervisor disabled duplicate file logging or
# HA_DISABLE_LOG_FILE disabled the log file.
# it even on Supervisor
def rename_old_file() -> None:
"""Rename old log file in executor."""
if os.path.isfile(default_log_path):
@@ -665,7 +657,6 @@ async def async_enable_logging(
else:
err_log_path = default_log_path
else:
disabled_log_file_reason = None
err_log_path = os.path.abspath(log_file)
if err_log_path:
@@ -678,34 +669,10 @@ async def async_enable_logging(
# Save the log file location for access by other components.
hass.data[DATA_LOGGING] = err_log_path
elif disabled_log_file_reason == LOG_FILE_DISABLED_REASON_ENVIRONMENT:
hass.data[DATA_LOGGING_DISABLED_REASON] = disabled_log_file_reason
async_activate_log_queue_handler(hass)
def _log_file_disabled_reason() -> str | None:
"""Return why the log file is disabled."""
if ENV_SUPERVISOR in os.environ and ENV_DUPLICATE_LOG_FILE not in os.environ:
return LOG_FILE_DISABLED_REASON_SUPERVISOR
disable_log_file = os.environ.get(ENV_DISABLE_LOG_FILE)
if disable_log_file is None:
return None
try:
if cv.boolean(disable_log_file):
return LOG_FILE_DISABLED_REASON_ENVIRONMENT
except vol.Invalid:
_LOGGER.warning(
"Ignoring invalid %s value: %s. Expected a boolean value: "
"1/0, true/false, yes/no, on/off, or enable/disable",
ENV_DISABLE_LOG_FILE,
disable_log_file,
)
return None
def _create_log_file(
err_log_path: str, log_rotate_days: int | None
) -> RotatingFileHandler | TimedRotatingFileHandler:
@@ -767,7 +734,7 @@ def _get_domains(hass: core.HomeAssistant, config: dict[str, Any]) -> set[str]:
domains.update(DEFAULT_INTEGRATIONS_RECOVERY_MODE)
# Add domains depending on if the Supervisor is used or not
if ENV_SUPERVISOR in os.environ:
if "SUPERVISOR" in os.environ:
domains.update(DEFAULT_INTEGRATIONS_SUPERVISOR)
return domains
+3
View File
@@ -7,6 +7,9 @@
"azure_event_hub",
"azure_service_bus",
"azure_storage",
"microsoft_face_detect",
"microsoft_face_identify",
"microsoft_face",
"microsoft",
"onedrive",
"onedrive_for_business",
@@ -27,7 +27,7 @@ from .const import CONDITIONS_MAP, DOMAIN, FORECAST_MAP
_LOGGER = logging.getLogger(__name__)
API_TIMEOUT: Final[int] = 120
WEATHER_UPDATE_INTERVAL = timedelta(minutes=20)
WEATHER_UPDATE_INTERVAL = timedelta(minutes=10)
type AemetConfigEntry = ConfigEntry[AemetData]
@@ -12,7 +12,7 @@ from homeassistant.components.number import (
NumberEntity,
NumberEntityDescription,
)
from homeassistant.const import EntityCategory, UnitOfRatio
from homeassistant.const import PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -40,7 +40,7 @@ DISPLAY_BRIGHTNESS = AirGradientNumberEntityDescription(
native_min_value=0,
native_max_value=100,
native_step=1,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda config: config.display_brightness,
set_value_fn=lambda client, value: client.set_display_brightness(value),
)
@@ -52,7 +52,7 @@ LED_BAR_BRIGHTNESS = AirGradientNumberEntityDescription(
native_min_value=0,
native_max_value=100,
native_step=1,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda config: config.led_bar_brightness,
set_value_fn=lambda client, value: client.set_led_bar_brightness(value),
)
+12 -11
View File
@@ -19,10 +19,11 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfDensity,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -56,21 +57,21 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
AirGradientMeasurementSensorEntityDescription(
key="pm01",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.pm01,
),
AirGradientMeasurementSensorEntityDescription(
key="pm02",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.pm02,
),
AirGradientMeasurementSensorEntityDescription(
key="pm10",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.pm10,
),
@@ -84,7 +85,7 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
AirGradientMeasurementSensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.relative_humidity,
),
@@ -112,7 +113,7 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
AirGradientMeasurementSensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.rco2,
),
@@ -143,7 +144,7 @@ MEASUREMENT_SENSOR_TYPES: tuple[AirGradientMeasurementSensorEntityDescription, .
key="pm02_raw",
translation_key="raw_pm02",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
value_fn=lambda status: status.raw_pm02,
@@ -189,7 +190,7 @@ CONFIG_LED_BAR_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...
AirGradientConfigSensorEntityDescription(
key="led_bar_brightness",
translation_key="led_bar_brightness",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda config: config.led_bar_brightness,
),
@@ -215,9 +216,9 @@ CONFIG_DISPLAY_SENSOR_TYPES: tuple[AirGradientConfigSensorEntityDescription, ...
AirGradientConfigSensorEntityDescription(
key="display_brightness",
translation_key="display_brightness",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda config: config.led_bar_brightness,
value_fn=lambda config: config.display_brightness,
),
)
+8 -4
View File
@@ -12,7 +12,11 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import ATTR_TIME, UnitOfDensity, UnitOfRatio
from homeassistant.const import (
ATTR_TIME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -91,7 +95,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
AirNowEntityDescription(
key=ATTR_API_PM10,
translation_key="pm10",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.PM10,
value_fn=lambda data: data.get(ATTR_API_PM10),
@@ -100,7 +104,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
AirNowEntityDescription(
key=ATTR_API_PM25,
translation_key="pm25",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.PM25,
value_fn=lambda data: data.get(ATTR_API_PM25),
@@ -109,7 +113,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
AirNowEntityDescription(
key=ATTR_API_O3,
translation_key="o3",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.get(ATTR_API_O3),
extra_state_attributes_fn=None,
+4 -3
View File
@@ -14,8 +14,9 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -58,7 +59,7 @@ SENSOR_TYPES: tuple[AirobotSensorEntityDescription, ...] = (
AirobotSensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.hum_air,
),
@@ -74,7 +75,7 @@ SENSOR_TYPES: tuple[AirobotSensorEntityDescription, ...] = (
AirobotSensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.co2,
supported_fn=lambda status: status.has_co2_sensor,
+2 -2
View File
@@ -8,7 +8,7 @@ from typing import override
from aioairq.core import AirQ
from homeassistant.components.number import NumberEntity, NumberEntityDescription
from homeassistant.const import UnitOfRatio
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -32,7 +32,7 @@ AIRQ_LED_BRIGHTNESS = AirQBrightnessDescription(
native_min_value=0.0,
native_max_value=100.0,
native_step=1.0,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value=lambda data: data["brightness"],
set_value=lambda device, value: device.set_current_brightness(value),
)
+51 -47
View File
@@ -12,9 +12,13 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
UnitOfDensity,
CONCENTRATION_GRAMS_PER_CUBIC_METER,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfTemperature,
)
@@ -40,70 +44,70 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
AirQEntityDescription(
key="c2h4o",
translation_key="acetaldehyde",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c2h4o"),
),
AirQEntityDescription(
key="nh3_MR100",
translation_key="ammonia",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("nh3_MR100"),
),
AirQEntityDescription(
key="ash3",
translation_key="arsine",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ash3"),
),
AirQEntityDescription(
key="br2",
translation_key="bromine",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("br2"),
),
AirQEntityDescription(
key="ch4s",
translation_key="methanethiol",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch4s"),
),
AirQEntityDescription(
key="cl2_M20",
translation_key="chlorine",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("cl2_M20"),
),
AirQEntityDescription(
key="clo2",
translation_key="chlorine_dioxide",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("clo2"),
),
AirQEntityDescription(
key="co",
translation_key="carbon_monoxide",
native_unit_of_measurement=UnitOfDensity.MILLIGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("co"),
),
AirQEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("co2"),
),
AirQEntityDescription(
key="cs2",
translation_key="carbon_disulfide",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("cs2"),
),
@@ -118,182 +122,182 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
AirQEntityDescription(
key="ethanol",
translation_key="ethanol",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ethanol"),
),
AirQEntityDescription(
key="c2h4",
translation_key="ethylene",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c2h4"),
),
AirQEntityDescription(
key="ch2o_M10",
translation_key="formaldehyde",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch2o_M10"),
),
AirQEntityDescription(
key="f2",
translation_key="fluorine",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("f2"),
),
AirQEntityDescription(
key="h2s",
translation_key="hydrogen_sulfide",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("h2s"),
),
AirQEntityDescription(
key="hcl",
translation_key="hydrochloric_acid",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("hcl"),
),
AirQEntityDescription(
key="hcn",
translation_key="hydrogen_cyanide",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("hcn"),
),
AirQEntityDescription(
key="hf",
translation_key="hydrogen_fluoride",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("hf"),
),
AirQEntityDescription(
key="health",
translation_key="health_index",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("health", 0.0) / 10.0,
),
AirQEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("humidity"),
),
AirQEntityDescription(
key="humidity_abs",
device_class=SensorDeviceClass.ABSOLUTE_HUMIDITY,
native_unit_of_measurement=UnitOfDensity.GRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("humidity_abs"),
),
AirQEntityDescription(
key="h2_M1000",
translation_key="hydrogen",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("h2_M1000"),
),
AirQEntityDescription(
key="h2o2",
translation_key="hydrogen_peroxide",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("h2o2"),
),
AirQEntityDescription(
key="ch4_MIPEX",
translation_key="methane",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ch4_MIPEX"),
),
AirQEntityDescription(
key="mold",
translation_key="mold",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("mold"),
),
AirQEntityDescription(
key="n2o",
device_class=SensorDeviceClass.NITROUS_OXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("n2o"),
),
AirQEntityDescription(
key="no_M250",
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("no_M250"),
),
AirQEntityDescription(
key="no2",
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("no2"),
),
AirQEntityDescription(
key="acid_M100",
translation_key="organic_acid",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("acid_M100"),
),
AirQEntityDescription(
key="oxygen",
translation_key="oxygen",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("oxygen"),
),
AirQEntityDescription(
key="o3",
device_class=SensorDeviceClass.OZONE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("o3"),
),
AirQEntityDescription(
key="performance",
translation_key="performance_index",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("performance", 0.0) / 10.0,
),
AirQEntityDescription(
key="ph3",
translation_key="hydrogen_phosphide",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("ph3"),
),
AirQEntityDescription(
key="pm1",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pm1"),
),
AirQEntityDescription(
key="pm2_5",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pm2_5"),
),
AirQEntityDescription(
key="pm10",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("pm10"),
),
@@ -315,42 +319,42 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
AirQEntityDescription(
key="c3h8_MIPEX",
translation_key="propane",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("c3h8_MIPEX"),
),
AirQEntityDescription(
key="r32",
translation_key="r32",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("r32"),
),
AirQEntityDescription(
key="r454b",
translation_key="r454b",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("r454b"),
),
AirQEntityDescription(
key="r454c",
translation_key="r454c",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("r454c"),
),
AirQEntityDescription(
key="sih4",
translation_key="silicon_hydride",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("sih4"),
),
AirQEntityDescription(
key="so2",
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("so2"),
),
@@ -387,7 +391,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
AirQEntityDescription(
key="tvoc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("tvoc"),
),
@@ -395,14 +399,14 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
key="tvoc_ionsc",
translation_key="industrial_volatile_organic_compounds",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("tvoc_ionsc"),
),
AirQEntityDescription(
key="virus",
translation_key="virus_index",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value=lambda data: data.get("virus", 0.0),
),
+11 -9
View File
@@ -11,12 +11,14 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS,
EntityCategory,
UnitOfDensity,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfTemperature,
)
@@ -47,7 +49,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"humidity": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
@@ -68,7 +70,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"battery": SensorEntityDescription(
key="battery",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
@@ -76,20 +78,20 @@ SENSORS: dict[str, SensorEntityDescription] = {
"co2": SensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"voc": SensorEntityDescription(
key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"light": SensorEntityDescription(
key="light",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key="light",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
@@ -124,14 +126,14 @@ SENSORS: dict[str, SensorEntityDescription] = {
),
"pm1": SensorEntityDescription(
key="pm1",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"pm25": SensorEntityDescription(
key="pm25",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
@@ -1,8 +1,6 @@
"""Support for airthings ble sensors."""
from collections.abc import Callable
import dataclasses
from dataclasses import dataclass
import logging
from typing import override
@@ -15,11 +13,13 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
EntityCategory,
Platform,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfTemperature,
)
@@ -46,108 +46,87 @@ CONNECTIVITY_MODE_MAP = {
AirthingsConnectivityMode.NOT_CONFIGURED.value: "not_configured",
}
def get_connectivity_mode(value: str | float | None) -> str | None:
"""Get connectivity mode."""
if not isinstance(value, str):
return None
return CONNECTIVITY_MODE_MAP.get(value)
@dataclass(frozen=True, kw_only=True)
class AirthingsBLESensorEntityDescription(SensorEntityDescription):
"""Describes Airthings BLE sensor entity."""
value_fn: Callable[[str | float | None], str | float | None] = lambda x: x
SENSORS_MAPPING_TEMPLATE: dict[str, AirthingsBLESensorEntityDescription] = {
"radon_1day_avg": AirthingsBLESensorEntityDescription(
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
"radon_1day_avg": SensorEntityDescription(
key="radon_1day_avg",
translation_key="radon_1day_avg",
native_unit_of_measurement=VOLUME_BECQUEREL,
suggested_display_precision=0,
state_class=SensorStateClass.MEASUREMENT,
),
"radon_longterm_avg": AirthingsBLESensorEntityDescription(
"radon_longterm_avg": SensorEntityDescription(
key="radon_longterm_avg",
translation_key="radon_longterm_avg",
native_unit_of_measurement=VOLUME_BECQUEREL,
suggested_display_precision=0,
state_class=SensorStateClass.MEASUREMENT,
),
"radon_1day_level": AirthingsBLESensorEntityDescription(
"radon_1day_level": SensorEntityDescription(
key="radon_1day_level",
translation_key="radon_1day_level",
device_class=SensorDeviceClass.ENUM,
options=["good", "fair", "poor"],
value_fn=lambda value: value if value != "unknown" else None,
),
"radon_longterm_level": AirthingsBLESensorEntityDescription(
"radon_longterm_level": SensorEntityDescription(
key="radon_longterm_level",
translation_key="radon_longterm_level",
device_class=SensorDeviceClass.ENUM,
options=["good", "fair", "poor"],
value_fn=lambda value: value if value != "unknown" else None,
),
"temperature": AirthingsBLESensorEntityDescription(
"temperature": SensorEntityDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"humidity": AirthingsBLESensorEntityDescription(
"humidity": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"pressure": AirthingsBLESensorEntityDescription(
"pressure": SensorEntityDescription(
key="pressure",
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"battery": AirthingsBLESensorEntityDescription(
"battery": SensorEntityDescription(
key="battery",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=0,
),
"co2": AirthingsBLESensorEntityDescription(
"co2": SensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"voc": AirthingsBLESensorEntityDescription(
"voc": SensorEntityDescription(
key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"illuminance": AirthingsBLESensorEntityDescription(
"illuminance": SensorEntityDescription(
key="illuminance",
translation_key="illuminance",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"lux": AirthingsBLESensorEntityDescription(
"lux": SensorEntityDescription(
key="lux",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"noise": AirthingsBLESensorEntityDescription(
"noise": SensorEntityDescription(
key="noise",
translation_key="ambient_noise",
device_class=SensorDeviceClass.SOUND_PRESSURE,
@@ -155,14 +134,13 @@ SENSORS_MAPPING_TEMPLATE: dict[str, AirthingsBLESensorEntityDescription] = {
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"connectivity_mode": AirthingsBLESensorEntityDescription(
"connectivity_mode": SensorEntityDescription(
key="connectivity_mode",
translation_key="connectivity_mode",
device_class=SensorDeviceClass.ENUM,
options=list(CONNECTIVITY_MODE_MAP.values()),
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=get_connectivity_mode,
),
}
@@ -250,13 +228,12 @@ class AirthingsSensor(
"""Airthings BLE sensors for the device."""
_attr_has_entity_name = True
entity_description: AirthingsBLESensorEntityDescription
def __init__(
self,
coordinator: AirthingsBLEDataUpdateCoordinator,
airthings_device: AirthingsDevice,
entity_description: AirthingsBLESensorEntityDescription,
entity_description: SensorEntityDescription,
) -> None:
"""Populate the airthings entity with relevant data."""
super().__init__(coordinator)
@@ -295,4 +272,11 @@ class AirthingsSensor(
def native_value(self) -> StateType:
"""Return the value reported by the sensor."""
value = self.coordinator.data.sensors[self.entity_description.key]
return self.entity_description.value_fn(value)
# Map connectivity mode to enum values
if self.entity_description.key == "connectivity_mode":
if not isinstance(value, str):
return None
return CONNECTIVITY_MODE_MAP.get(value)
return value
@@ -45,23 +45,13 @@
"name": "Radon 1-day average"
},
"radon_1day_level": {
"name": "Radon 1-day level",
"state": {
"fair": "Fair",
"good": "Good",
"poor": "Poor"
}
"name": "Radon 1-day level"
},
"radon_longterm_avg": {
"name": "Radon longterm average"
},
"radon_longterm_level": {
"name": "Radon longterm level",
"state": {
"fair": "[%key:component::airthings_ble::entity::sensor::radon_1day_level::state::fair%]",
"good": "[%key:component::airthings_ble::entity::sensor::radon_1day_level::state::good%]",
"poor": "[%key:component::airthings_ble::entity::sensor::radon_1day_level::state::poor%]"
}
"name": "Radon longterm level"
}
}
},
+9 -8
View File
@@ -12,13 +12,14 @@ from homeassistant.const import (
ATTR_LATITUDE,
ATTR_LONGITUDE,
ATTR_STATE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
CONF_COUNTRY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_SHOW_ON_MAP,
CONF_STATE,
UnitOfDensity,
UnitOfRatio,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -93,12 +94,12 @@ POLLUTANT_LEVELS = {
}
POLLUTANT_UNITS = {
"co": UnitOfRatio.PARTS_PER_MILLION,
"n2": UnitOfRatio.PARTS_PER_BILLION,
"o3": UnitOfRatio.PARTS_PER_BILLION,
"p1": UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
"p2": UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
"s2": UnitOfRatio.PARTS_PER_BILLION,
"co": CONCENTRATION_PARTS_PER_MILLION,
"n2": CONCENTRATION_PARTS_PER_BILLION,
"o3": CONCENTRATION_PARTS_PER_BILLION,
"p1": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"p2": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
"s2": CONCENTRATION_PARTS_PER_BILLION,
}
@@ -11,9 +11,10 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfDensity,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
@@ -56,21 +57,21 @@ SENSOR_DESCRIPTIONS = (
key="battery_level",
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: status["battery"],
),
AirVisualProMeasurementDescription(
key="carbon_dioxide",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["co2"],
),
AirVisualProMeasurementDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements[
"humidity"
@@ -79,21 +80,21 @@ SENSOR_DESCRIPTIONS = (
AirVisualProMeasurementDescription(
key="particulate_matter_0_1",
translation_key="pm01",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["pm0_1"],
),
AirVisualProMeasurementDescription(
key="particulate_matter_1_0",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["pm1_0"],
),
AirVisualProMeasurementDescription(
key="particulate_matter_2_5",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["pm2_5"],
),
@@ -109,7 +110,7 @@ SENSOR_DESCRIPTIONS = (
AirVisualProMeasurementDescription(
key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda settings, status, measurements, history: measurements["voc"],
),
@@ -35,13 +35,13 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfInformation,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
@@ -141,7 +141,7 @@ WEBSERVER_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
key=AZD_CPU_USAGE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="cpu_usage",
),
@@ -172,19 +172,19 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription(
device_class=SensorDeviceClass.PM1,
key=AZD_AQ_PM_1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
device_class=SensorDeviceClass.PM25,
key=AZD_AQ_PM_2P5,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
device_class=SensorDeviceClass.PM10,
key=AZD_AQ_PM_10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
@@ -196,20 +196,20 @@ ZONE_SENSOR_TYPES: Final[tuple[SensorEntityDescription, ...]] = (
SensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
key=AZD_HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
device_class=SensorDeviceClass.BATTERY,
key=AZD_THERMOSTAT_BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
key=AZD_THERMOSTAT_COVERAGE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="thermostat_coverage",
),
@@ -13,7 +13,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory, Platform
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
import homeassistant.helpers.entity_registry as er
@@ -118,7 +118,7 @@ async def async_setup_entry(
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{sensor_desc.key}"
if entity_id := entity_registry.async_get_entity_id(
Platform.BINARY_SENSOR, DOMAIN, unique_id
BINARY_SENSOR_DOMAIN, DOMAIN, unique_id
):
_LOGGER.debug("Removing deprecated entity %s", entity_id)
entity_registry.async_remove(entity_id)
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==14.1.8"]
"requirements": ["aioamazondevices==14.1.6"]
}
@@ -18,7 +18,13 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import LIGHT_LUX, UnitOfDensity, UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
@@ -91,25 +97,25 @@ SENSORS: Final = (
AmazonSensorEntityDescription(
key="Humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
AmazonSensorEntityDescription(
key="PM10",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
AmazonSensorEntityDescription(
key="PM25",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
AmazonSensorEntityDescription(
key="CO",
device_class=SensorDeviceClass.CO,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
AmazonSensorEntityDescription(
@@ -7,7 +7,7 @@ from aioamazondevices.const.schedules import (
NOTIFICATION_TIMER,
)
from homeassistant.const import Platform
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.core import HomeAssistant
import homeassistant.helpers.entity_registry as er
@@ -70,7 +70,7 @@ async def async_remove_unsupported_notification_sensors(
):
unique_id = f"{serial_num}-{notification_key}"
entity_id = entity_registry.async_get_entity_id(
Platform.SENSOR, DOMAIN, unique_id=unique_id
SENSOR_DOMAIN, DOMAIN, unique_id=unique_id
)
is_unsupported = not coordinator.data[serial_num].notifications_supported
+12 -11
View File
@@ -12,11 +12,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfDensity,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfTemperature,
)
@@ -44,7 +45,7 @@ SENSOR_DESCRIPTIONS = [
device_class=SensorDeviceClass.HUMIDITY,
key="BME280_humidity",
translation_key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "BME280"},
),
@@ -69,7 +70,7 @@ SENSOR_DESCRIPTIONS = [
device_class=SensorDeviceClass.HUMIDITY,
key="BME680_humidity",
translation_key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "BME680"},
),
@@ -128,7 +129,7 @@ SENSOR_DESCRIPTIONS = [
device_class=SensorDeviceClass.HUMIDITY,
key="HTU21D_humidity",
translation_key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "HTU21D"},
),
@@ -144,21 +145,21 @@ SENSOR_DESCRIPTIONS = [
device_class=SensorDeviceClass.PM10,
translation_key="pm_10",
key="SDS_P1",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.PM25,
translation_key="pm_25",
key="SDS_P2",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
key="SHT3X_humidity",
translation_key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "SHT3X"},
),
@@ -193,7 +194,7 @@ SENSOR_DESCRIPTIONS = [
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
translation_key="co2",
key="CCS_CO2",
suggested_display_precision=2,
@@ -202,7 +203,7 @@ SENSOR_DESCRIPTIONS = [
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
key="CCS_TVOC",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
@@ -214,7 +215,7 @@ SENSOR_DESCRIPTIONS = [
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.CO2,
translation_key="co2",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
key="SCD4x_co2",
suggested_display_precision=2,
translation_placeholders={"sensor_name": "SCD4x"},
@@ -10,14 +10,15 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
CONF_MAC,
DEGREE,
UnitOfDensity,
PERCENTAGE,
UnitOfIrradiance,
UnitOfLength,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolumetricFlux,
@@ -93,7 +94,7 @@ SENSOR_DESCRIPTIONS = (
),
SensorEntityDescription(
key=TYPE_CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=2,
@@ -133,7 +134,7 @@ SENSOR_DESCRIPTIONS = (
),
SensorEntityDescription(
key=TYPE_HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
@@ -187,14 +188,14 @@ SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_PM25_24H,
translation_key="pm25_24h_average",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
suggested_display_precision=1,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key=TYPE_PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
@@ -11,14 +11,15 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEGREE,
LIGHT_LUX,
UnitOfDensity,
PERCENTAGE,
UnitOfIrradiance,
UnitOfLength,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolumetricFlux,
@@ -156,13 +157,13 @@ SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_PM25_IN_AQIN,
translation_key="pm25_indoor_aqin",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_PM25_IN_24H_AQIN,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
translation_key="pm25_indoor_24h_aqin",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
@@ -170,28 +171,28 @@ SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_PM10_IN_AQIN,
translation_key="pm10_indoor_aqin",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_PM10_IN_24H_AQIN,
translation_key="pm10_indoor_24h_aqin",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_CO2_IN_AQIN,
translation_key="co2_indoor_aqin",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_CO2_IN_24H_AQIN,
translation_key="co2_indoor_24h_aqin",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -205,7 +206,7 @@ SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_PM_IN_HUMIDITY_AQIN,
translation_key="pm_indoor_humidity_aqin",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -249,7 +250,7 @@ SENSOR_DESCRIPTIONS = (
),
SensorEntityDescription(
key=TYPE_CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -290,83 +291,83 @@ SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_HUMIDITY10,
translation_key="humidity_10",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY1,
translation_key="humidity_1",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY2,
translation_key="humidity_2",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY3,
translation_key="humidity_3",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY4,
translation_key="humidity_4",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY5,
translation_key="humidity_5",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY6,
translation_key="humidity_6",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY7,
translation_key="humidity_7",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY8,
translation_key="humidity_8",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY9,
translation_key="humidity_9",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_HUMIDITYIN,
translation_key="humidity_indoor",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -416,95 +417,95 @@ SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=TYPE_PM25_24H,
translation_key="pm25_24h_average",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
),
SensorEntityDescription(
key=TYPE_PM25_IN,
translation_key="pm25_indoor",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_PM25_IN_24H,
translation_key="pm25_indoor_24h_average",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
),
SensorEntityDescription(
key=TYPE_PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM10,
translation_key="soil_humidity_10",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM1,
translation_key="soil_humidity_1",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM2,
translation_key="soil_humidity_2",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM3,
translation_key="soil_humidity_3",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM4,
translation_key="soil_humidity_4",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM5,
translation_key="soil_humidity_5",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM6,
translation_key="soil_humidity_6",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM7,
translation_key="soil_humidity_7",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM8,
translation_key="soil_humidity_8",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TYPE_SOILHUM9,
translation_key="soil_humidity_9",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["pyanglianwater"],
"quality_scale": "bronze",
"requirements": ["pyanglianwater==3.2.3"]
"requirements": ["pyanglianwater==3.2.2"]
}
+5 -4
View File
@@ -23,9 +23,10 @@ from homeassistant.const import (
ATTR_MODEL,
ATTR_NAME,
ATTR_SW_VERSION,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -61,7 +62,7 @@ SENSOR_DESCRIPTIONS = {
key="humidity",
name="Humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
"pressure": AranetSensorEntityDescription(
@@ -82,7 +83,7 @@ SENSOR_DESCRIPTIONS = {
key="co2",
name="Carbon Dioxide",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"radiation_rate": AranetSensorEntityDescription(
@@ -114,7 +115,7 @@ SENSOR_DESCRIPTIONS = {
key="battery",
name="Battery",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
+10 -5
View File
@@ -12,7 +12,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfDensity, UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -30,7 +35,7 @@ class ArveDeviceEntityDescription(SensorEntityDescription):
SENSORS: tuple[ArveDeviceEntityDescription, ...] = (
ArveDeviceEntityDescription(
key="CO2",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
value_fn=lambda arve_data: arve_data.co2,
state_class=SensorStateClass.MEASUREMENT,
@@ -43,21 +48,21 @@ SENSORS: tuple[ArveDeviceEntityDescription, ...] = (
),
ArveDeviceEntityDescription(
key="Humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
value_fn=lambda arve_data: arve_data.humidity,
state_class=SensorStateClass.MEASUREMENT,
),
ArveDeviceEntityDescription(
key="PM10",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
value_fn=lambda arve_data: arve_data.pm10,
state_class=SensorStateClass.MEASUREMENT,
),
ArveDeviceEntityDescription(
key="PM25",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
value_fn=lambda arve_data: arve_data.pm25,
state_class=SensorStateClass.MEASUREMENT,
@@ -0,0 +1 @@
"""The ATEN PE component."""
@@ -0,0 +1,9 @@
{
"domain": "aten_pe",
"name": "ATEN Rack PDU",
"codeowners": ["@mtdcr"],
"documentation": "https://www.home-assistant.io/integrations/aten_pe",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["atenpdu==0.3.6"]
}
+119
View File
@@ -0,0 +1,119 @@
"""The ATEN PE switch component."""
import logging
from typing import Any, override
from atenpdu import AtenPE, AtenPEError
import voluptuous as vol
from homeassistant.components.switch import (
PLATFORM_SCHEMA as SWITCH_PLATFORM_SCHEMA,
SwitchDeviceClass,
SwitchEntity,
)
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
CONF_AUTH_KEY = "auth_key"
CONF_COMMUNITY = "community"
CONF_PRIV_KEY = "priv_key"
DEFAULT_COMMUNITY = "private"
DEFAULT_PORT = "161"
DEFAULT_USERNAME = "administrator"
PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_COMMUNITY, default=DEFAULT_COMMUNITY): cv.string,
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_AUTH_KEY): cv.string,
vol.Optional(CONF_PRIV_KEY): cv.string,
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the ATEN PE switch."""
node = config[CONF_HOST]
serv = config[CONF_PORT]
dev = AtenPE(
node=node,
serv=serv,
community=config[CONF_COMMUNITY],
username=config[CONF_USERNAME],
authkey=config.get(CONF_AUTH_KEY),
privkey=config.get(CONF_PRIV_KEY),
)
try:
await hass.async_add_executor_job(dev.initialize)
mac = await dev.deviceMAC()
outlets = dev.outlets()
name = await dev.deviceName()
model = await dev.modelName()
sw_version = await dev.deviceFWversion()
except AtenPEError as exc:
_LOGGER.error("Failed to initialize %s:%s: %s", node, serv, str(exc))
raise PlatformNotReady from exc
info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, mac)},
manufacturer="ATEN",
model=model,
name=name,
sw_version=sw_version,
)
async_add_entities(
(AtenSwitch(dev, info, mac, outlet.id, outlet.name) for outlet in outlets), True
)
class AtenSwitch(SwitchEntity):
"""Represents an ATEN PE switch."""
_attr_device_class = SwitchDeviceClass.OUTLET
def __init__(
self, device: AtenPE, info: DeviceInfo, mac: str, outlet: str, name: str
) -> None:
"""Initialize an ATEN PE switch."""
self._device = device
self._outlet = outlet
self._attr_device_info = info
self._attr_unique_id = f"{mac}-{outlet}"
self._attr_name = name or f"Outlet {outlet}"
@override
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
await self._device.setOutletStatus(self._outlet, "on")
self._attr_is_on = True
@override
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
await self._device.setOutletStatus(self._outlet, "off")
self._attr_is_on = False
async def async_update(self) -> None:
"""Process update from entity."""
status = await self._device.displayOutletStatus(self._outlet)
if status == "on":
self._attr_is_on = True
elif status == "off":
self._attr_is_on = False
+12 -9
View File
@@ -16,9 +16,12 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_CONNECTIONS,
ATTR_SW_VERSION,
CONCENTRATION_GRAMS_PER_CUBIC_METER,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
UnitOfDensity,
UnitOfRatio,
PERCENTAGE,
UnitOfSoundPressure,
UnitOfTemperature,
)
@@ -58,7 +61,7 @@ class AwairSensorEntityDescription(SensorEntityDescription):
SENSOR_TYPE_SCORE = AwairSensorEntityDescription(
key=API_SCORE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key="score",
unique_id_tag="score", # matches legacy format
state_class=SensorStateClass.MEASUREMENT,
@@ -68,7 +71,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_HUMID,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
unique_id_tag="HUMID", # matches legacy format
state_class=SensorStateClass.MEASUREMENT,
),
@@ -90,7 +93,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_VOC,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
unique_id_tag="VOC", # matches legacy format
state_class=SensorStateClass.MEASUREMENT,
),
@@ -104,7 +107,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_CO2,
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
unique_id_tag="CO2", # matches legacy format
state_class=SensorStateClass.MEASUREMENT,
),
@@ -120,7 +123,7 @@ SENSOR_TYPES: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_ABS_HUMID,
device_class=SensorDeviceClass.ABSOLUTE_HUMIDITY,
native_unit_of_measurement=UnitOfDensity.GRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
unique_id_tag="absolute_humidity",
state_class=SensorStateClass.MEASUREMENT,
entity_registry_enabled_default=False,
@@ -131,14 +134,14 @@ SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
AwairSensorEntityDescription(
key=API_PM25,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
unique_id_tag="PM25", # matches legacy format
state_class=SensorStateClass.MEASUREMENT,
),
AwairSensorEntityDescription(
key=API_PM10,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
unique_id_tag="PM10", # matches legacy format
state_class=SensorStateClass.MEASUREMENT,
),
@@ -0,0 +1 @@
"""The BlinkStick integration."""
@@ -0,0 +1,87 @@
"""Support for BlinkStick lights."""
# mypy: ignore-errors
from typing import Any
# from blinkstick import blinkstick
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_HS_COLOR,
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
ColorMode,
LightEntity,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import color as color_util
CONF_SERIAL = "serial"
DEFAULT_NAME = "Blinkstick"
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_SERIAL): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up BlinkStick device specified by serial number."""
name = config[CONF_NAME]
serial = config[CONF_SERIAL]
stick = blinkstick.find_by_serial(serial)
add_entities([BlinkStickLight(stick, name)], True)
class BlinkStickLight(LightEntity):
"""Representation of a BlinkStick light."""
_attr_color_mode = ColorMode.HS
_attr_supported_color_modes = {ColorMode.HS}
def __init__(self, stick, name):
"""Initialize the light."""
self._stick = stick
self._attr_name = name
def update(self) -> None:
"""Read back the device state."""
rgb_color = self._stick.get_color()
hsv = color_util.color_RGB_to_hsv(*rgb_color)
self._attr_hs_color = hsv[:2]
self._attr_brightness = int(hsv[2])
self._attr_is_on = self.brightness is not None and self.brightness > 0
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
if ATTR_HS_COLOR in kwargs:
self._attr_hs_color = kwargs[ATTR_HS_COLOR]
brightness: int = kwargs.get(ATTR_BRIGHTNESS, 255)
self._attr_brightness = brightness
self._attr_is_on = bool(brightness)
assert self.hs_color
rgb_color = color_util.color_hsv_to_RGB(
self.hs_color[0], self.hs_color[1], brightness / 255 * 100
)
self._stick.set_color(red=rgb_color[0], green=rgb_color[1], blue=rgb_color[2])
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
self._stick.turn_off()
@@ -0,0 +1,11 @@
{
"domain": "blinksticklight",
"name": "BlinkStick",
"codeowners": [],
"disabled": "This integration is disabled because it uses non-open source code to operate.",
"documentation": "https://www.home-assistant.io/integrations/blinksticklight",
"iot_class": "local_polling",
"loggers": ["blinkstick"],
"quality_scale": "legacy",
"requirements": ["BlinkStick==1.2.0"]
}
@@ -0,0 +1,5 @@
extend = "../../../pyproject.toml"
lint.extend-ignore = [
"F821"
]
+5 -4
View File
@@ -13,9 +13,10 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -57,13 +58,13 @@ SENSOR_DESCRIPTIONS: dict[str, SHCSensorEntityDescription] = {
HUMIDITY_SENSOR: SHCSensorEntityDescription(
key=HUMIDITY_SENSOR,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda device: device.humidity,
),
PURITY_SENSOR: SHCSensorEntityDescription(
key=PURITY_SENSOR,
translation_key=PURITY_SENSOR,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
value_fn=lambda device: device.purity,
),
AIR_QUALITY_SENSOR: SHCSensorEntityDescription(
@@ -111,7 +112,7 @@ SENSOR_DESCRIPTIONS: dict[str, SHCSensorEntityDescription] = {
key=VALVE_TAPPET_SENSOR,
translation_key=VALVE_TAPPET_SENSOR,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda device: device.position,
attributes_fn=lambda device: {
"valve_tappet_state": device.valvestate.name,
+6 -6
View File
@@ -10,12 +10,12 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
UnitOfDensity,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -37,25 +37,25 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
),
SensorEntityDescription(
key="pm10",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="pm2_5",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="pm1",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["bsblan"],
"quality_scale": "silver",
"requirements": ["python-bsblan==6.1.4"],
"requirements": ["python-bsblan==6.1.3"],
"zeroconf": [
{
"name": "bsb-lan*",
@@ -0,0 +1 @@
"""The clementine component."""
@@ -0,0 +1,10 @@
{
"domain": "clementine",
"name": "Clementine Music Player",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/clementine",
"iot_class": "local_polling",
"loggers": ["clementineremote"],
"quality_scale": "legacy",
"requirements": ["python-clementine-remote==1.0.1"]
}
@@ -0,0 +1,166 @@
"""Support for Clementine Music Player as media player."""
from datetime import timedelta
import time
from typing import override
from clementineremote import ClementineRemote
import voluptuous as vol
from homeassistant.components.media_player import (
PLATFORM_SCHEMA as MEDIA_PLAYER_PLATFORM_SCHEMA,
MediaPlayerEntity,
MediaPlayerEntityFeature,
MediaPlayerState,
MediaType,
)
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
DEFAULT_NAME = "Clementine Remote"
DEFAULT_PORT = 5500
SCAN_INTERVAL = timedelta(seconds=5)
PLATFORM_SCHEMA = MEDIA_PLAYER_PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_ACCESS_TOKEN): cv.positive_int,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Clementine platform."""
host = config[CONF_HOST]
port = config[CONF_PORT]
token = config.get(CONF_ACCESS_TOKEN)
client = ClementineRemote(host, port, token, reconnect=True)
add_entities([ClementineDevice(client, config[CONF_NAME])])
class ClementineDevice(MediaPlayerEntity):
"""Representation of Clementine Player."""
_attr_media_content_type = MediaType.MUSIC
_attr_supported_features = (
MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.PLAY
)
_attr_volume_step = 0.04
def __init__(self, client, name):
"""Initialize the Clementine device."""
self._client = client
self._attr_name = name
def update(self) -> None:
"""Retrieve the latest data from the Clementine Player."""
try:
client = self._client
if client.state == "Playing":
self._attr_state = MediaPlayerState.PLAYING
elif client.state == "Paused":
self._attr_state = MediaPlayerState.PAUSED
elif client.state == "Disconnected":
self._attr_state = MediaPlayerState.OFF
else:
self._attr_state = MediaPlayerState.PAUSED
if client.last_update and (time.time() - client.last_update > 40):
self._attr_state = MediaPlayerState.OFF
volume = float(client.volume) if client.volume else 0.0
self._attr_volume_level = volume / 100.0
if client.active_playlist_id in client.playlists:
self._attr_source = client.playlists[client.active_playlist_id]["name"]
else:
self._attr_source = "Unknown"
self._attr_source_list = [s["name"] for s in client.playlists.values()]
if client.current_track:
self._attr_media_title = client.current_track["title"]
self._attr_media_artist = client.current_track["track_artist"]
self._attr_media_album_name = client.current_track["track_album"]
self._attr_media_image_hash = client.current_track["track_id"]
else:
self._attr_media_image_hash = None
except Exception:
self._attr_state = MediaPlayerState.OFF
raise
@override
def select_source(self, source: str) -> None:
"""Select input source."""
client = self._client
sources = [s for s in client.playlists.values() if s["name"] == source]
if len(sources) == 1:
client.change_song(sources[0]["id"], 0)
@override
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
"""Fetch media image of current playing image."""
if self._client.current_track:
image = bytes(self._client.current_track["art"])
return (image, "image/png")
return None, None
@override
def mute_volume(self, mute: bool) -> None:
"""Send mute command."""
self._client.set_volume(0)
@override
def set_volume_level(self, volume: float) -> None:
"""Set volume level."""
self._client.set_volume(int(100 * volume))
def media_play_pause(self) -> None:
"""Simulate play pause media player."""
if self.state == MediaPlayerState.PLAYING:
self.media_pause()
else:
self.media_play()
@override
def media_play(self) -> None:
"""Send play command."""
self._attr_state = MediaPlayerState.PLAYING
self._client.play()
@override
def media_pause(self) -> None:
"""Send media pause command to media player."""
self._attr_state = MediaPlayerState.PAUSED
self._client.pause()
@override
def media_next_track(self) -> None:
"""Send next track command."""
self._client.next()
@override
def media_previous_track(self) -> None:
"""Send the previous track command."""
self._client.previous()
+12 -11
View File
@@ -13,11 +13,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
UnitOfDensity,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -116,7 +117,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
CompitParameter.BOILER_TEMPERATURE: SensorEntityDescription(
key=CompitParameter.BOILER_TEMPERATURE.value,
@@ -203,14 +204,14 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
CompitParameter.CO2_PERCENT: SensorEntityDescription(
key=CompitParameter.CO2_PERCENT.value,
translation_key="co2_percent",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
CompitParameter.COLLECTOR_POWER: SensorEntityDescription(
key=CompitParameter.COLLECTOR_POWER.value,
@@ -289,7 +290,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
translation_key="fuel_level",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
CompitParameter.HEATING1_TARGET_TEMPERATURE: SensorEntityDescription(
key=CompitParameter.HEATING1_TARGET_TEMPERATURE.value,
@@ -332,7 +333,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
CompitParameter.LOWER_SOURCE_TEMPERATURE: SensorEntityDescription(
key=CompitParameter.LOWER_SOURCE_TEMPERATURE.value,
@@ -400,14 +401,14 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CompitParameter.PM4_LEVEL_MEASURED: SensorEntityDescription(
key=CompitParameter.PM4_LEVEL_MEASURED.value,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.PM4,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CompitParameter.PM10_LEVEL: SensorEntityDescription(
key=CompitParameter.PM10_LEVEL.value,
@@ -421,7 +422,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
key=CompitParameter.PM10_MEASURED.value,
device_class=SensorDeviceClass.PM10,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CompitParameter.PM25_LEVEL: SensorEntityDescription(
key=CompitParameter.PM25_LEVEL.value,
@@ -435,7 +436,7 @@ DESCRIPTIONS: dict[CompitParameter, SensorEntityDescription] = {
key=CompitParameter.PM25_MEASURED.value,
device_class=SensorDeviceClass.PM25,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
CompitParameter.PROTECTION_TEMPERATURE: SensorEntityDescription(
key=CompitParameter.PROTECTION_TEMPERATURE.value,
+14 -12
View File
@@ -35,13 +35,15 @@ from homeassistant.components.sensor import (
from homeassistant.const import (
ATTR_TEMPERATURE,
ATTR_VOLTAGE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
EntityCategory,
UnitOfDensity,
UnitOfEnergy,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -136,7 +138,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
name_suffix="PPB",
old_unique_id_suffix="ppb",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
),
DeconzSensorDescription[AirQuality](
key="air_quality_formaldehyde",
@@ -147,7 +149,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
name_suffix="CH2O",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
DeconzSensorDescription[AirQuality](
key="air_quality_co2",
@@ -158,7 +160,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
name_suffix="CO2",
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
DeconzSensorDescription[AirQuality](
key="air_quality_pm2_5",
@@ -169,7 +171,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
name_suffix="PM25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
DeconzSensorDescription[CarbonDioxide](
key="carbon_dioxide",
@@ -179,7 +181,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
instance_check=CarbonDioxide,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
),
DeconzSensorDescription[Consumption](
key="consumption",
@@ -208,7 +210,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
instance_check=Formaldehyde,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
),
DeconzSensorDescription[GenericStatus](
key="status",
@@ -225,7 +227,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
instance_check=Humidity,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=1,
),
DeconzSensorDescription[LightLevel](
@@ -246,7 +248,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
instance_check=Moisture,
device_class=SensorDeviceClass.MOISTURE,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=1,
),
DeconzSensorDescription[ParticulateMatter](
@@ -258,7 +260,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
name_suffix="PM25",
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
DeconzSensorDescription[Power](
key="power",
@@ -308,7 +310,7 @@ ENTITY_DESCRIPTIONS: tuple[DeconzSensorDescription, ...] = (
old_unique_id_suffix="battery",
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
DeconzSensorDescription[SensorResources](
+7 -6
View File
@@ -12,10 +12,11 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
UnitOfVolume,
)
@@ -51,7 +52,7 @@ async def async_setup_entry(
12,
SensorDeviceClass.BATTERY,
SensorStateClass.MEASUREMENT,
UnitOfRatio.PERCENTAGE,
PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_name="Battery",
),
@@ -62,7 +63,7 @@ async def async_setup_entry(
54,
SensorDeviceClass.HUMIDITY,
SensorStateClass.MEASUREMENT,
UnitOfRatio.PERCENTAGE,
PERCENTAGE,
),
DemoSensor(
"sensor_3",
@@ -71,7 +72,7 @@ async def async_setup_entry(
54,
SensorDeviceClass.CO,
SensorStateClass.MEASUREMENT,
UnitOfRatio.PARTS_PER_MILLION,
CONCENTRATION_PARTS_PER_MILLION,
),
DemoSensor(
"sensor_4",
@@ -80,7 +81,7 @@ async def async_setup_entry(
54,
SensorDeviceClass.CO2,
SensorStateClass.MEASUREMENT,
UnitOfRatio.PARTS_PER_MILLION,
CONCENTRATION_PARTS_PER_MILLION,
),
DemoSensor(
"battery_4",
@@ -89,7 +90,7 @@ async def async_setup_entry(
99,
SensorDeviceClass.BATTERY,
SensorStateClass.MEASUREMENT,
UnitOfRatio.PERCENTAGE,
PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
entity_name="Battery",
),
@@ -0,0 +1,87 @@
"""Support for Dovado router."""
# mypy: ignore-errors
from datetime import timedelta
import logging
# import dovado
import voluptuous as vol
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
DEVICE_DEFAULT_NAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DOMAIN = "dovado"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_PORT): cv.port,
}
)
},
extra=vol.ALLOW_EXTRA,
)
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Dovado component."""
hass.data[DOMAIN] = DovadoData(
dovado.Dovado(
config[DOMAIN][CONF_USERNAME],
config[DOMAIN][CONF_PASSWORD],
config[DOMAIN].get(CONF_HOST),
config[DOMAIN].get(CONF_PORT),
)
)
return True
class DovadoData:
"""Maintain a connection to the router."""
def __init__(self, client):
"""Set up a new Dovado connection."""
self._client = client
self.state = {}
@property
def name(self):
"""Name of the router."""
return self.state.get("product name", DEVICE_DEFAULT_NAME)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Update device state."""
try:
self.state = self._client.state or {}
if not self.state:
return False
self.state.update(connected=self.state.get("modem status") == "CONNECTED")
except OSError as error:
_LOGGER.warning("Could not contact the router: %s", error)
return None
_LOGGER.debug("Received: %s", self.state)
return True
@property
def client(self):
"""Dovado client instance."""
return self._client
@@ -0,0 +1,10 @@
{
"domain": "dovado",
"name": "Dovado",
"codeowners": [],
"disabled": "This integration is disabled because it uses non-open source code to operate.",
"documentation": "https://www.home-assistant.io/integrations/dovado",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["dovado==0.4.1"]
}
+38
View File
@@ -0,0 +1,38 @@
"""Support for SMS notifications from the Dovado router."""
import logging
from typing import Any, override
from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
def get_service(
hass: HomeAssistant,
config: ConfigType,
discovery_info: DiscoveryInfoType | None = None,
) -> DovadoSMSNotificationService:
"""Get the Dovado Router SMS notification service."""
return DovadoSMSNotificationService(hass.data[DOMAIN].client)
class DovadoSMSNotificationService(BaseNotificationService):
"""Implement the notification service for the Dovado SMS component."""
def __init__(self, client):
"""Initialize the service."""
self._client = client
@override
def send_message(self, message: str, **kwargs: Any) -> None:
"""Send SMS to the specified target phone number."""
if not (target := kwargs.get(ATTR_TARGET)):
_LOGGER.error("One target is required")
return
self._client.send_sms(target, message)
@@ -0,0 +1,5 @@
extend = "../../../pyproject.toml"
lint.extend-ignore = [
"F821"
]
+143
View File
@@ -0,0 +1,143 @@
"""Support for sensors from the Dovado router."""
from dataclasses import dataclass
from datetime import timedelta
import re
from typing import Any, override
import voluptuous as vol
from homeassistant.components.sensor import (
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import CONF_SENSORS, PERCENTAGE, UnitOfInformation
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import DOMAIN
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
SENSOR_UPLOAD = "upload"
SENSOR_DOWNLOAD = "download"
SENSOR_SIGNAL = "signal"
SENSOR_NETWORK = "network"
SENSOR_SMS_UNREAD = "sms"
@dataclass(frozen=True, kw_only=True)
class DovadoSensorEntityDescription(SensorEntityDescription):
"""Describes Dovado sensor entity."""
identifier: str
SENSOR_TYPES: tuple[DovadoSensorEntityDescription, ...] = (
DovadoSensorEntityDescription(
identifier=SENSOR_NETWORK,
key="signal strength",
name="Network",
icon="mdi:access-point-network",
),
DovadoSensorEntityDescription(
identifier=SENSOR_SIGNAL,
key="signal strength",
name="Signal Strength",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:signal",
),
DovadoSensorEntityDescription(
identifier=SENSOR_SMS_UNREAD,
key="sms unread",
name="SMS unread",
icon="mdi:message-text-outline",
),
DovadoSensorEntityDescription(
identifier=SENSOR_UPLOAD,
key="traffic modem tx",
name="Sent",
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:cloud-upload",
),
DovadoSensorEntityDescription(
identifier=SENSOR_DOWNLOAD,
key="traffic modem rx",
name="Received",
native_unit_of_measurement=UnitOfInformation.GIGABYTES,
device_class=SensorDeviceClass.DATA_SIZE,
icon="mdi:cloud-download",
),
)
SENSOR_KEYS: list[str] = [desc.key for desc in SENSOR_TYPES]
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_SENSORS): vol.All(cv.ensure_list, [vol.In(SENSOR_KEYS)])}
)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Dovado sensor platform."""
dovado = hass.data[DOMAIN]
sensors = config[CONF_SENSORS]
entities = [
DovadoSensor(dovado, description)
for description in SENSOR_TYPES
if description.key in sensors
]
add_entities(entities)
class DovadoSensor(SensorEntity):
"""Representation of a Dovado sensor."""
entity_description: DovadoSensorEntityDescription
def __init__(self, data, description: DovadoSensorEntityDescription) -> None:
"""Initialize the sensor."""
self.entity_description = description
self._data = data
self._attr_name = f"{data.name} {description.name}"
self._attr_native_value = self._compute_state()
def _compute_state(self):
"""Compute the state of the sensor."""
state = self._data.state.get(self.entity_description.key)
sensor_identifier = self.entity_description.identifier
if sensor_identifier == SENSOR_NETWORK:
match = re.search(r"\((.+)\)", state)
return match.group(1) if match else None
if sensor_identifier == SENSOR_SIGNAL:
try:
return int(state.split()[0])
except ValueError:
return None
if sensor_identifier == SENSOR_SMS_UNREAD:
return int(state)
if sensor_identifier in [SENSOR_UPLOAD, SENSOR_DOWNLOAD]:
return round(float(state) / 1e6, 1)
return state
def update(self) -> None:
"""Update sensor values."""
self._data.update()
self._attr_native_value = self._compute_state()
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the state attributes."""
return {k: v for k, v in self._data.state.items() if k not in ["date", "time"]}
@@ -12,10 +12,11 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
TEMPERATURE,
EntityCategory,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
UnitOfVolume,
UnitOfVolumeFlowRate,
@@ -140,7 +141,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
DROPSensorEntityDescription(
key=BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
value_fn=lambda device: device.drop_api.battery(),
state_class=SensorStateClass.MEASUREMENT,
@@ -157,7 +158,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
DROPSensorEntityDescription(
key=INLET_TDS,
translation_key=INLET_TDS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
value_fn=lambda device: device.drop_api.inlet_tds(),
@@ -165,7 +166,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
DROPSensorEntityDescription(
key=OUTLET_TDS,
translation_key=OUTLET_TDS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
value_fn=lambda device: device.drop_api.outlet_tds(),
@@ -173,7 +174,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
DROPSensorEntityDescription(
key=CARTRIDGE_1_LIFE,
translation_key=CARTRIDGE_1_LIFE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=0,
@@ -182,7 +183,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
DROPSensorEntityDescription(
key=CARTRIDGE_2_LIFE,
translation_key=CARTRIDGE_2_LIFE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=0,
@@ -191,7 +192,7 @@ SENSORS: list[DROPSensorEntityDescription] = [
DROPSensorEntityDescription(
key=CARTRIDGE_3_LIFE,
translation_key=CARTRIDGE_3_LIFE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=0,
+1 -16
View File
@@ -27,19 +27,6 @@ _LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
SUPPORTED_SELECT_NODE_TYPES = {
NodeType.BOX,
NodeType.VLV,
NodeType.VLVRH,
NodeType.VLVVOC,
NodeType.VLVCO2,
NodeType.VLVCO2RH,
NodeType.EAV,
NodeType.EAVRH,
NodeType.EAVVOC,
NodeType.EAVCO2,
}
def _get_ventilation_options(action: ActionItem) -> tuple[str, ...] | None:
"""Return ventilation options advertised by a node action."""
@@ -84,9 +71,7 @@ async def async_setup_entry(
if node.node_id in known_nodes:
continue
# Duco advertises SetVentilationState broadly, so keep the select
# limited to the box and known valve node families.
if node.general.node_type not in SUPPORTED_SELECT_NODE_TYPES:
if node.general.node_type is not NodeType.BOX:
continue
options = options_by_node.get(node.node_id)
+9 -4
View File
@@ -11,7 +11,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfDensity, UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -37,14 +42,14 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
),
EcobeeSensorEntityDescription(
key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
runtime_key=None,
),
EcobeeSensorEntityDescription(
key="co2PPM",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
runtime_key="actualCO2",
@@ -52,7 +57,7 @@ SENSOR_TYPES: tuple[EcobeeSensorEntityDescription, ...] = (
EcobeeSensorEntityDescription(
key="vocPPM",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
runtime_key="actualVOC",
),
+12 -11
View File
@@ -14,17 +14,18 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEGREE,
LIGHT_LUX,
PERCENTAGE,
UV_INDEX,
EntityCategory,
UnitOfDensity,
UnitOfElectricPotential,
UnitOfIrradiance,
UnitOfLength,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolumetricFlux,
@@ -98,7 +99,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
EcoWittSensorTypes.HUMIDITY: SensorEntityDescription(
key="HUMIDITY",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.DEGREE: SensorEntityDescription(
@@ -121,19 +122,19 @@ ECOWITT_SENSORS_MAPPING: Final = {
EcoWittSensorTypes.PM25: SensorEntityDescription(
key="PM25",
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.PM10: SensorEntityDescription(
key="PM10",
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.BATTERY_PERCENTAGE: SensorEntityDescription(
key="BATTERY_PERCENTAGE",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
@@ -148,7 +149,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
EcoWittSensorTypes.CO2_PPM: SensorEntityDescription(
key="CO2_PPM",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.LUX: SensorEntityDescription(
@@ -262,13 +263,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
),
EcoWittSensorTypes.PERCENTAGE: SensorEntityDescription(
key="PERCENTAGE",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.SOIL_MOISTURE: SensorEntityDescription(
key="SOIL_MOISTURE",
device_class=SensorDeviceClass.MOISTURE,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.DISTANCE_MM: SensorEntityDescription(
@@ -285,13 +286,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
EcoWittSensorTypes.PM1: SensorEntityDescription(
key="PM1",
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.PM4: SensorEntityDescription(
key="PM4",
device_class=SensorDeviceClass.PM4,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
}
+6 -5
View File
@@ -9,16 +9,17 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
CONF_URL,
PERCENTAGE,
UnitOfApparentPower,
UnitOfDensity,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
UnitOfFrequency,
UnitOfPower,
UnitOfPressure,
UnitOfRatio,
UnitOfSoundPressure,
UnitOfSpeed,
UnitOfTemperature,
@@ -157,19 +158,19 @@ SENSORS: dict[str | None, SensorEntityDescription] = {
"μg/m³": SensorEntityDescription(
key="concentration|microgram_per_cubic_meter",
translation_key="concentration",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
"ppm": SensorEntityDescription(
key="concentration|microgram_parts_per_million",
translation_key="concentration",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
),
"%": SensorEntityDescription(
key="percent",
translation_key="percent",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
}
@@ -65,14 +65,6 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
"/ivp/meters/readings",
"/ivp/pdm/device_data",
"/home",
"/inventory.json?deleted=1",
"/admin/lib/acb_config",
"/ivp/sc/sched",
"/admin/lib/network_display",
"/admin/lib/wireless_display",
"/ivp/ensemble/relay",
"/ivp/livedata/status",
"/ivp/pdm/energy",
]
for end_point in end_points:
@@ -142,15 +134,16 @@ async def async_get_config_entry_diagnostics(
"encharge_power": envoy_data.encharge_power,
"encharge_aggregate": envoy_data.encharge_aggregate,
"enpower": envoy_data.enpower,
"acb_power": envoy_data.acb_power,
"acb_inventory": envoy_data.acb_inventory,
"battery_aggregate": envoy_data.battery_aggregate,
"collar": envoy_data.collar,
"c6cc": envoy_data.c6cc,
"system_consumption": envoy_data.system_consumption,
"system_production": envoy_data.system_production,
"system_consumption_phases": envoy_data.system_consumption_phases,
"system_production_phases": envoy_data.system_production_phases,
"ctmeter_production": envoy_data.ctmeter_production,
"ctmeter_consumption": envoy_data.ctmeter_consumption,
"ctmeter_storage": envoy_data.ctmeter_storage,
"ctmeter_production_phases": envoy_data.ctmeter_production_phases,
"ctmeter_consumption_phases": envoy_data.ctmeter_consumption_phases,
"ctmeter_storage_phases": envoy_data.ctmeter_storage_phases,
"ctmeters": envoy_data.ctmeters,
"ctmeters_phases": envoy_data.ctmeters_phases,
"dry_contact_status": envoy_data.dry_contact_status,
+8 -3
View File
@@ -8,7 +8,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfPressure, UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfPressure,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -21,7 +26,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
key="co2",
suggested_display_precision=0,
),
@@ -35,7 +40,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
key="humidity",
suggested_display_precision=1,
),
+2 -6
View File
@@ -12,6 +12,7 @@ from aioesphomeapi import (
EntityCategory as EsphomeEntityCategory,
EntityInfo,
EntityState,
build_device_unique_id,
)
import voluptuous as vol
@@ -31,12 +32,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
# Import config flow so that it's added to the registry
from .entry_data import (
DeviceEntityKey,
ESPHomeConfigEntry,
RuntimeEntryData,
build_device_unique_id,
)
from .entry_data import DeviceEntityKey, ESPHomeConfigEntry, RuntimeEntryData
from .enum_mapper import EsphomeEnumMapper
_LOGGER = logging.getLogger(__name__)
+21 -17
View File
@@ -45,7 +45,7 @@ from aioesphomeapi import (
UserService,
ValveInfo,
WaterHeaterInfo,
build_unique_id,
build_device_unique_id,
)
from aioesphomeapi.model import ButtonInfo
from bleak_esphome.backend.device import ESPHomeBluetoothDevice
@@ -103,22 +103,6 @@ INFO_TYPE_TO_PLATFORM: dict[type[EntityInfo], Platform] = {
}
def build_device_unique_id(mac: str, entity_info: EntityInfo) -> str:
"""Build unique ID for entity, appending @device_id if it belongs to a sub-device.
This wrapper around build_unique_id ensures that entities belonging to sub-devices
have their device_id appended to the unique_id to handle proper migration when
entities move between devices.
"""
base_unique_id = build_unique_id(mac, entity_info)
# If entity belongs to a sub-device, append @device_id
if entity_info.device_id:
return f"{base_unique_id}@{entity_info.device_id}"
return base_unique_id
class StoreData(TypedDict, total=False):
"""ESPHome storage data."""
@@ -310,11 +294,31 @@ class RuntimeEntryData:
infos_by_type: defaultdict[type[EntityInfo], list[EntityInfo]] = defaultdict(
list
)
ent_reg = er.async_get(hass)
registry_get_entity = ent_reg.async_get_entity_id
for info in infos:
info_type = type(info)
if platform := info_types_to_platform.get(info_type):
needed_platforms.add(platform)
infos_by_type[info_type].append(info)
# Migrate legacy unique ids to the version 3 format that fixes
# UTF-8 collisions. Skip when a version 3 id already exists so a
# downgrade then upgrade keeps the original entity. When two
# legacy ids collided (the bug this fixes) only one registry
# entry exists for it, so the first iterated info claims it and
# the rest get fresh version 3 ids.
old_unique_id = build_device_unique_id(mac, info, version=1)
new_unique_id = build_device_unique_id(mac, info, version=3)
if (
old_unique_id != new_unique_id
and (
old_entry := registry_get_entity(
platform, DOMAIN, old_unique_id
)
)
and not registry_get_entity(platform, DOMAIN, new_unique_id)
):
ent_reg.async_update_entity(old_entry, new_unique_id=new_unique_id)
else:
_LOGGER.warning(
"Entity type %s is not supported in this version of Home Assistant",
+8 -7
View File
@@ -1417,13 +1417,14 @@ async def async_replace_device(
upper_mac = new_mac.upper()
old_upper_mac = old_mac.upper()
for entity in er.async_entries_for_config_entry(ent_reg, entry.entry_id):
# <upper_mac>-<entity type>-<object_id>
old_unique_id = entity.unique_id.split("-")
new_unique_id = "-".join([upper_mac, *old_unique_id[1:]])
if entity.unique_id != new_unique_id and entity.unique_id.startswith(
old_upper_mac
):
ent_reg.async_update_entity(entity.entity_id, new_unique_id=new_unique_id)
# The mac is the leading segment of the unique id in every format,
# so swap the prefix without parsing the rest.
if entity.unique_id.startswith(old_upper_mac):
new_unique_id = upper_mac + entity.unique_id[len(old_upper_mac) :]
if new_unique_id != entity.unique_id:
ent_reg.async_update_entity(
entity.entity_id, new_unique_id=new_unique_id
)
domain_data = DomainData.get(hass)
store = domain_data.get_or_create_store(hass, entry)
+5 -4
View File
@@ -13,11 +13,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
Platform,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -38,20 +39,20 @@ MAIN_SENSOR_TYPES: dict[str, SensorEntityDescription] = {
"com.fibaro.smokeSensor": SensorEntityDescription(
key="com.fibaro.smokeSensor",
name="Smoke",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:fire",
),
"CO2": SensorEntityDescription(
key="CO2",
name="CO2",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
"com.fibaro.humiditySensor": SensorEntityDescription(
key="com.fibaro.humiditySensor",
name="Humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
+9 -7
View File
@@ -16,10 +16,12 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
ATTR_TEMPERATURE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
CONF_TOKEN,
CONF_USERNAME,
UnitOfDensity,
UnitOfRatio,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -42,7 +44,7 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="pm",
name=ATTR_PM2_5,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
icon="mdi:cloud",
),
SensorEntityDescription(
@@ -54,25 +56,25 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="hum",
name=ATTR_HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:water-percent",
),
SensorEntityDescription(
key="co2",
name=ATTR_CARBON_DIOXIDE,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:molecule-co2",
),
SensorEntityDescription(
key="voc",
name=ATTR_VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
icon="mdi:cloud",
),
SensorEntityDescription(
key="allpollu",
name=ATTR_FOOBOT_INDEX,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
icon="mdi:percent",
),
)
+8 -3
View File
@@ -13,7 +13,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
StateType,
)
from homeassistant.const import UnitOfRatio, UnitOfTemperature, UnitOfVolumeFlowRate
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfTemperature,
UnitOfVolumeFlowRate,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -49,14 +54,14 @@ _T2 = FreshrSensorEntityDescription(
_CO2 = FreshrSensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda r: r.co2,
)
_HUM = FreshrSensorEntityDescription(
key="hum",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda r: r.hum,
)
+3 -3
View File
@@ -10,7 +10,7 @@ from homeassistant.components.button import (
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.const import EntityCategory, Platform
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
@@ -75,7 +75,7 @@ def repair_issue_cleanup(hass: HomeAssistant, avm_wrapper: AvmWrapper) -> None:
if (
(
entity_button := entity_registry.async_get_entity_id(
Platform.BUTTON, DOMAIN, f"{avm_wrapper.unique_id}-cleanup"
"button", DOMAIN, f"{avm_wrapper.unique_id}-cleanup"
)
)
and (entity_entry := entity_registry.async_get(entity_button))
@@ -102,7 +102,7 @@ def repair_issue_firmware_update(hass: HomeAssistant, avm_wrapper: AvmWrapper) -
if (
(
entity_button := entity_registry.async_get_entity_id(
Platform.BUTTON, DOMAIN, f"{avm_wrapper.unique_id}-firmware_update"
"button", DOMAIN, f"{avm_wrapper.unique_id}-firmware_update"
)
)
and (entity_entry := entity_registry.async_get(entity_button))
+2 -2
View File
@@ -7,13 +7,13 @@ from typing import override
from requests.exceptions import RequestException
from homeassistant.components.image import ImageEntity
from homeassistant.const import EntityCategory, Platform
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util, slugify
from .const import DOMAIN
from .const import DOMAIN, Platform
from .coordinator import AvmWrapper, FritzConfigEntry
from .entity import FritzBoxBaseEntity
+10 -10
View File
@@ -14,7 +14,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfDensity
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -63,7 +63,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
key=ATTR_C6H6,
value=lambda sensors: sensors.c6h6.value if sensors.c6h6 else None,
suggested_display_precision=0,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
translation_key="c6h6",
),
@@ -72,7 +72,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.co.value if sensors.co else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.CO,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -80,7 +80,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.no.value if sensors.no else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.NITROGEN_MONOXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -88,7 +88,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.no2.value if sensors.no2 else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -104,7 +104,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
translation_key=ATTR_NOX,
value=lambda sensors: sensors.nox.value if sensors.nox else None,
suggested_display_precision=0,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -112,7 +112,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.o3.value if sensors.o3 else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.OZONE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -128,7 +128,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.pm10.value if sensors.pm10 else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -144,7 +144,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.pm25.value if sensors.pm25 else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -160,7 +160,7 @@ SENSOR_TYPES: tuple[GiosSensorEntityDescription, ...] = (
value=lambda sensors: sensors.so2.value if sensors.so2 else None,
suggested_display_precision=0,
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
GiosSensorEntityDescription(
@@ -14,7 +14,11 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.config_entries import ConfigSubentry
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, UnitOfRatio
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
CONF_LATITUDE,
CONF_LONGITUDE,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -121,7 +125,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
native_unit_of_measurement_fn=lambda x: x.pollutants.co.concentration.units,
exists_fn=lambda x: "co" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.co.concentration.value,
suggested_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
suggested_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
AirQualitySensorEntityDescription(
key="nh3",
@@ -0,0 +1 @@
"""The greenwave component."""
+123
View File
@@ -0,0 +1,123 @@
"""Support for Greenwave Reality (TCP Connected) lights."""
from datetime import timedelta
import logging
import os
from typing import Any, override
import greenwavereality as greenwave
import voluptuous as vol
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
ColorMode,
LightEntity,
)
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
CONF_VERSION = "version"
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_HOST): cv.string, vol.Required(CONF_VERSION): cv.positive_int}
)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
def setup_platform(
hass: HomeAssistant,
config: ConfigType,
add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Greenwave Reality Platform."""
host = config.get(CONF_HOST)
tokenfilename = hass.config.path(".greenwave")
if config.get(CONF_VERSION) == 3:
if os.path.exists(tokenfilename):
with open(tokenfilename, encoding="utf8") as tokenfile:
token = tokenfile.read()
else:
try:
token = greenwave.grab_token(host, "hass", "homeassistant")
except PermissionError:
_LOGGER.error("The Gateway Is Not In Sync Mode")
raise
with open(tokenfilename, "w+", encoding="utf8") as tokenfile:
tokenfile.write(token)
else:
token = None
bulbs = greenwave.grab_bulbs(host, token)
add_entities(
GreenwaveLight(device, host, token, GatewayData(host, token))
for device in bulbs.values()
)
class GreenwaveLight(LightEntity):
"""Representation of an Greenwave Reality Light."""
_attr_color_mode = ColorMode.BRIGHTNESS
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
def __init__(self, light, host, token, gatewaydata):
"""Initialize a Greenwave Reality Light."""
self._did = int(light["did"])
self._attr_name = light["name"]
self._attr_is_on = bool(int(light["state"]))
self._attr_brightness = greenwave.hass_brightness(light)
self._host = host
self._attr_available = greenwave.check_online(light)
self._token = token
self._gatewaydata = gatewaydata
@override
def turn_on(self, **kwargs: Any) -> None:
"""Instruct the light to turn on."""
temp_brightness = int((kwargs.get(ATTR_BRIGHTNESS, 255) / 255) * 100)
greenwave.set_brightness(self._host, self._did, temp_brightness, self._token)
greenwave.turn_on(self._host, self._did, self._token)
@override
def turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
greenwave.turn_off(self._host, self._did, self._token)
def update(self) -> None:
"""Fetch new state data for this light."""
self._gatewaydata.update()
bulbs = self._gatewaydata.greenwave
self._attr_is_on = bool(int(bulbs[self._did]["state"]))
self._attr_brightness = greenwave.hass_brightness(bulbs[self._did])
self._attr_available = greenwave.check_online(bulbs[self._did])
self._attr_name = bulbs[self._did]["name"]
class GatewayData:
"""Handle Gateway data and limit updates."""
def __init__(self, host, token):
"""Initialize the data object."""
self._host = host
self._token = token
self._greenwave = greenwave.grab_bulbs(host, token)
@property
def greenwave(self):
"""Return Gateway API object."""
return self._greenwave
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from the gateway."""
self._greenwave = greenwave.grab_bulbs(self._host, self._token)
return self._greenwave
@@ -0,0 +1,10 @@
{
"domain": "greenwave",
"name": "Greenwave Reality",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/greenwave",
"iot_class": "local_polling",
"loggers": ["greenwavereality"],
"quality_scale": "legacy",
"requirements": ["greenwavereality==0.5.1"]
}
+1 -1
View File
@@ -92,7 +92,7 @@ class SupervisorJobs:
# We catch all errors to prevent an error in one from stopping the others
for match in [job for job in self._jobs.values() if subscription.matches(job)]:
try:
subscription.event_callback(match)
return subscription.event_callback(match)
except Exception as err: # noqa: BLE001
_LOGGER.error(
"Error encountered processing Supervisor Job (%s %s %s) - %s",
+14 -13
View File
@@ -12,9 +12,11 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
ATTR_NAME,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
DEGREE,
LIGHT_LUX,
UnitOfDensity,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfEnergy,
@@ -22,7 +24,6 @@ from homeassistant.const import (
UnitOfPower,
UnitOfPrecipitationDepth,
UnitOfPressure,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
UnitOfVolume,
@@ -56,7 +57,7 @@ HM_STATE_HA_CAST = {
SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
"HUMIDITY": SensorEntityDescription(
key="HUMIDITY",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -128,7 +129,7 @@ SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
),
"CONCENTRATION": SensorEntityDescription(
key="CONCENTRATION",
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -203,15 +204,15 @@ SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
),
"VALVE_STATE": SensorEntityDescription(
key="VALVE_STATE",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
"CARRIER_SENSE_LEVEL": SensorEntityDescription(
key="CARRIER_SENSE_LEVEL",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
"DUTY_CYCLE_LEVEL": SensorEntityDescription(
key="DUTY_CYCLE_LEVEL",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
),
"BRIGHTNESS": SensorEntityDescription(
key="BRIGHTNESS",
@@ -220,37 +221,37 @@ SENSOR_DESCRIPTIONS: dict[str, SensorEntityDescription] = {
),
"MASS_CONCENTRATION_PM_1": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_1",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
),
"MASS_CONCENTRATION_PM_2_5": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_2_5",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
"MASS_CONCENTRATION_PM_10": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_10",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
"MASS_CONCENTRATION_PM_1_24H_AVERAGE": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_1_24H_AVERAGE",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
),
"MASS_CONCENTRATION_PM_2_5_24H_AVERAGE": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_2_5_24H_AVERAGE",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
"MASS_CONCENTRATION_PM_10_24H_AVERAGE": SensorEntityDescription(
key="MASS_CONCENTRATION_PM_10_24H_AVERAGE",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -9,5 +9,5 @@
"iot_class": "local_polling",
"loggers": ["aioimmich"],
"quality_scale": "platinum",
"requirements": ["aioimmich==0.15.1"]
"requirements": ["aioimmich==0.15.0"]
}
@@ -161,7 +161,5 @@ SENSOR_KEYS: Final[dict[int, list[str]]] = {
IndevoltSolar.CUMULATIVE_PRODUCTION,
IndevoltBattery.GEN_2_CYCLE_COUNT,
IndevoltBattery.GEN_2_TRANSFORMER_TEMPERATURE,
IndevoltBattery.REMAINING_CHARGING_TIME,
IndevoltBattery.REMAINING_DISCHARGING_TIME,
],
}
@@ -27,7 +27,6 @@ from homeassistant.const import (
UnitOfFrequency,
UnitOfPower,
UnitOfTemperature,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -46,7 +45,6 @@ class IndevoltSensorEntityDescription(SensorEntityDescription):
state_mapping: dict[str | int, str] = field(default_factory=dict)
generation: tuple[int, ...] = (1, 2)
energy_mode: IndevoltEnergyMode | None = None
charge_discharge_state: int | None = None
SENSORS: Final = (
@@ -244,26 +242,6 @@ SENSORS: Final = (
state_mapping={1000: "static", 1001: "charging", 1002: "discharging"},
device_class=SensorDeviceClass.ENUM,
),
IndevoltSensorEntityDescription(
key=IndevoltBattery.REMAINING_CHARGING_TIME,
generation=(2,),
translation_key="remaining_charging_time",
native_unit_of_measurement=UnitOfTime.MINUTES,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
charge_discharge_state=1001,
entity_registry_enabled_default=False,
),
IndevoltSensorEntityDescription(
key=IndevoltBattery.REMAINING_DISCHARGING_TIME,
generation=(2,),
translation_key="remaining_discharging_time",
native_unit_of_measurement=UnitOfTime.MINUTES,
device_class=SensorDeviceClass.DURATION,
state_class=SensorStateClass.MEASUREMENT,
charge_discharge_state=1002,
entity_registry_enabled_default=False,
),
IndevoltSensorEntityDescription(
key=IndevoltBattery.SOC,
translation_key="battery_soc",
@@ -970,14 +948,6 @@ class IndevoltSensorEntity(IndevoltEntity, SensorEntity):
if energy_mode != self.entity_description.energy_mode:
return False
# Check whether the battery is not in the required charge/discharge state
if (
self.entity_description.charge_discharge_state is not None
and self.coordinator.data.get(IndevoltBattery.CHARGE_DISCHARGE_STATE)
!= self.entity_description.charge_discharge_state
):
return False
# Check whether inverter is reporting 0 degrees with heater not active (thus reporting to indicate "idle")
# Pending fix by Indevolt: https://discord.com/channels/1417471269942591571/1510277757689659522
if self.entity_description.key == IndevoltBattery.GEN_1_INVERTER_TEMPERATURE:
@@ -365,12 +365,6 @@
"realtime_target_soc": {
"name": "Real-time target SOC"
},
"remaining_charging_time": {
"name": "Remaining charging time"
},
"remaining_discharging_time": {
"name": "Remaining discharging time"
},
"serial_number": {
"name": "Serial number"
},
@@ -12,7 +12,11 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -42,14 +46,14 @@ INTELLICLIMA_SENSORS: tuple[IntelliClimaSensorEntityDescription, ...] = (
key="humidity",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda device_data: float(device_data.rh),
),
IntelliClimaSensorEntityDescription(
key="voc",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
value_fn=lambda device_data: float(device_data.voc_state),
),
)
+4 -3
View File
@@ -14,14 +14,15 @@ from homeassistant.components.sensor import (
SensorEntity,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
CONF_DOMAIN,
CONF_ENTITIES,
CONF_SOURCE,
CONF_UNIT_OF_MEASUREMENT,
LIGHT_LUX,
PERCENTAGE,
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfRatio,
UnitOfSpeed,
UnitOfTemperature,
)
@@ -66,8 +67,8 @@ UNIT_OF_MEASUREMENT_MAPPING = {
pypck.lcn_defs.VarUnit.METERPERSECOND: UnitOfSpeed.METERS_PER_SECOND,
pypck.lcn_defs.VarUnit.VOLT: UnitOfElectricPotential.VOLT,
pypck.lcn_defs.VarUnit.AMPERE: UnitOfElectricCurrent.AMPERE,
pypck.lcn_defs.VarUnit.PPM: UnitOfRatio.PARTS_PER_MILLION,
pypck.lcn_defs.VarUnit.PERCENT: UnitOfRatio.PERCENTAGE,
pypck.lcn_defs.VarUnit.PPM: CONCENTRATION_PARTS_PER_MILLION,
pypck.lcn_defs.VarUnit.PERCENT: PERCENTAGE,
}
+3 -3
View File
@@ -15,7 +15,7 @@ from homeassistant.components.number import (
NumberMode,
)
from homeassistant.components.script import scripts_with_entity
from homeassistant.const import UnitOfRatio, UnitOfTemperature, UnitOfTime
from homeassistant.const import PERCENTAGE, UnitOfTemperature, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -41,13 +41,13 @@ NUMBER_DESC: dict[ThinQProperty, NumberEntityDescription] = {
),
ThinQProperty.LIGHT_STATUS: NumberEntityDescription(
key=ThinQProperty.LIGHT_STATUS,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.LIGHT_STATUS,
),
ThinQProperty.TARGET_HUMIDITY: NumberEntityDescription(
key=ThinQProperty.TARGET_HUMIDITY,
device_class=NumberDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.TARGET_HUMIDITY,
),
ThinQProperty.TARGET_TEMPERATURE: NumberEntityDescription(
+14 -14
View File
@@ -18,9 +18,9 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
UnitOfDensity,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
PERCENTAGE,
UnitOfEnergy,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -37,25 +37,25 @@ AIR_QUALITY_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
ThinQProperty.PM1: SensorEntityDescription(
key=ThinQProperty.PM1,
device_class=SensorDeviceClass.PM1,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
ThinQProperty.PM2: SensorEntityDescription(
key=ThinQProperty.PM2,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
ThinQProperty.PM10: SensorEntityDescription(
key=ThinQProperty.PM10,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
),
ThinQProperty.HUMIDITY: SensorEntityDescription(
key=ThinQProperty.HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
ThinQProperty.MONITORING_ENABLED: SensorEntityDescription(
@@ -106,12 +106,12 @@ FILTER_INFO_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.FILTER_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.FILTER_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.FILTER_LIFETIME,
),
ThinQProperty.TOP_FILTER_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.TOP_FILTER_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.TOP_FILTER_REMAIN_PERCENT,
),
}
@@ -119,7 +119,7 @@ HUMIDITY_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
ThinQProperty.CURRENT_HUMIDITY: SensorEntityDescription(
key=ThinQProperty.CURRENT_HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
)
}
@@ -215,7 +215,7 @@ RECIPE_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.BEER_REMAIN: SensorEntityDescription(
key=ThinQProperty.BEER_REMAIN,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.BEER_REMAIN,
),
}
@@ -227,7 +227,7 @@ REFRIGERATION_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.FRESH_AIR_FILTER_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.FRESH_AIR_FILTER_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.FRESH_AIR_FILTER,
),
}
@@ -318,17 +318,17 @@ WATER_FILTER_INFO_SENSOR_DESC: dict[ThinQProperty, SensorEntityDescription] = {
),
ThinQProperty.WATER_FILTER_1_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.WATER_FILTER_1_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.WATER_FILTER_1_REMAIN_PERCENT,
),
ThinQProperty.WATER_FILTER_2_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.WATER_FILTER_2_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.WATER_FILTER_2_REMAIN_PERCENT,
),
ThinQProperty.WATER_FILTER_3_REMAIN_PERCENT: SensorEntityDescription(
key=ThinQProperty.WATER_FILTER_3_REMAIN_PERCENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key=ThinQProperty.WATER_FILTER_3_REMAIN_PERCENT,
),
}
@@ -0,0 +1,56 @@
"""Support for sending data to Logentries webhook endpoint."""
import json
import logging
import requests
import voluptuous as vol
from homeassistant.const import CONF_TOKEN, EVENT_STATE_CHANGED
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, state as state_helper
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "logentries"
DEFAULT_HOST = "https://webhook.logentries.com/noformat/logs/"
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_TOKEN): cv.string})}, extra=vol.ALLOW_EXTRA
)
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Logentries component."""
conf = config[DOMAIN]
token = conf.get(CONF_TOKEN)
le_wh = f"{DEFAULT_HOST}{token}"
def logentries_event_listener(event):
"""Listen for new messages on the bus and sends them to Logentries."""
if (state := event.data.get("new_state")) is None:
return
try:
_state = state_helper.state_as_number(state)
except ValueError:
_state = state.state
json_body = [
{
"domain": state.domain,
"entity_id": state.object_id,
"attributes": dict(state.attributes),
"time": str(event.time_fired),
"value": _state,
}
]
try:
payload = {"host": le_wh, "event": json_body}
requests.post(le_wh, data=json.dumps(payload), timeout=10)
except requests.exceptions.RequestException:
_LOGGER.exception("Error sending to Logentries")
hass.bus.listen(EVENT_STATE_CHANGED, logentries_event_listener)
return True
@@ -0,0 +1,8 @@
{
"domain": "logentries",
"name": "Logentries",
"codeowners": [],
"documentation": "https://www.home-assistant.io/integrations/logentries",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
+5 -5
View File
@@ -11,10 +11,10 @@ from homeassistant.components.sensor import (
from homeassistant.const import (
ATTR_LATITUDE,
ATTR_LONGITUDE,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONF_SHOW_ON_MAP,
UnitOfDensity,
PERCENTAGE,
UnitOfPressure,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -36,7 +36,7 @@ SENSORS: tuple[SensorEntityDescription, ...] = (
),
SensorEntityDescription(
key="humidity",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
),
@@ -55,13 +55,13 @@ SENSORS: tuple[SensorEntityDescription, ...] = (
),
SensorEntityDescription(
key="P1",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key="P2",
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
+3 -3
View File
@@ -16,10 +16,10 @@ from homeassistant.components.number import (
NumberMode,
)
from homeassistant.const import (
PERCENTAGE,
EntityCategory,
Platform,
UnitOfLength,
UnitOfRatio,
UnitOfTemperature,
UnitOfTime,
)
@@ -354,7 +354,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.NUMBER,
entity_description=MatterNumberEntityDescription(
key="pump_setpoint",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
translation_key="pump_setpoint",
native_max_value=100,
native_min_value=0.5,
@@ -516,7 +516,7 @@ DISCOVERY_SCHEMAS = [
entity_description=MatterRangeNumberEntityDescription(
key="speaker_setpoint",
translation_key="speaker_setpoint",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
command=lambda value: clusters.LevelControl.Commands.MoveToLevel(
level=int(value)
),
@@ -31,9 +31,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
}
)
+9 -3
View File
@@ -10,7 +10,13 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import LIGHT_LUX, UnitOfPower, UnitOfRatio, UnitOfTemperature
from homeassistant.const import (
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
UnitOfPower,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -36,14 +42,14 @@ SENSOR_TYPES = {
14: SensorEntityDescription(
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
key="carbon_dioxide",
suggested_display_precision=1,
),
16: SensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
key="humidity",
suggested_display_precision=1,
),
@@ -0,0 +1,344 @@
"""Support for Microsoft face recognition."""
import asyncio
from collections.abc import Coroutine
import json
import logging
from typing import Any, override
import aiohttp
from aiohttp.hdrs import CONTENT_TYPE
import voluptuous as vol
from homeassistant.components import camera
from homeassistant.const import ATTR_NAME, CONF_API_KEY, CONF_TIMEOUT, CONTENT_TYPE_JSON
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify
from homeassistant.util.hass_dict import HassKey
_LOGGER = logging.getLogger(__name__)
ATTR_CAMERA_ENTITY = "camera_entity"
ATTR_GROUP = "group"
ATTR_PERSON = "person"
CONF_AZURE_REGION = "azure_region"
DEFAULT_TIMEOUT = 10
DOMAIN = "microsoft_face"
DATA_MICROSOFT_FACE: HassKey[MicrosoftFace] = HassKey(DOMAIN)
FACE_API_URL = "api.cognitive.microsoft.com/face/v1.0/{0}"
SERVICE_CREATE_GROUP = "create_group"
SERVICE_CREATE_PERSON = "create_person"
SERVICE_DELETE_GROUP = "delete_group"
SERVICE_DELETE_PERSON = "delete_person"
SERVICE_FACE_PERSON = "face_person"
SERVICE_TRAIN_GROUP = "train_group"
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_AZURE_REGION, default="westus"): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
}
)
},
extra=vol.ALLOW_EXTRA,
)
SCHEMA_GROUP_SERVICE = vol.Schema({vol.Required(ATTR_NAME): cv.string})
SCHEMA_PERSON_SERVICE = SCHEMA_GROUP_SERVICE.extend(
{vol.Required(ATTR_GROUP): cv.slugify}
)
SCHEMA_FACE_SERVICE = vol.Schema(
{
vol.Required(ATTR_PERSON): cv.string,
vol.Required(ATTR_GROUP): cv.slugify,
vol.Required(ATTR_CAMERA_ENTITY): cv.entity_id,
}
)
SCHEMA_TRAIN_SERVICE = vol.Schema({vol.Required(ATTR_GROUP): cv.slugify})
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Microsoft Face."""
component = EntityComponent[MicrosoftFaceGroupEntity](
logging.getLogger(__name__), DOMAIN, hass
)
entities: dict[str, MicrosoftFaceGroupEntity] = {}
domain_config: dict[str, Any] = config[DOMAIN]
azure_region: str = domain_config[CONF_AZURE_REGION]
api_key: str = domain_config[CONF_API_KEY]
timeout: int = domain_config[CONF_TIMEOUT]
face = MicrosoftFace(
hass,
azure_region,
api_key,
timeout,
component,
entities,
)
try:
# read exists group/person from cloud and create entities
await face.update_store()
except HomeAssistantError as err:
_LOGGER.error("Can't load data from face api: %s", err)
return False
hass.data[DATA_MICROSOFT_FACE] = face
async def async_create_group(service: ServiceCall) -> None:
"""Create a new person group."""
name = service.data[ATTR_NAME]
g_id = slugify(name)
try:
await face.call_api("put", f"persongroups/{g_id}", {"name": name})
face.store[g_id] = {}
old_entity = entities.pop(g_id, None)
if old_entity:
await component.async_remove_entity(old_entity.entity_id)
entities[g_id] = MicrosoftFaceGroupEntity(face, g_id, name)
await component.async_add_entities([entities[g_id]])
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't create group '%s' with error: %s", g_id, err)
hass.services.async_register(
DOMAIN, SERVICE_CREATE_GROUP, async_create_group, schema=SCHEMA_GROUP_SERVICE
)
async def async_delete_group(service: ServiceCall) -> None:
"""Delete a person group."""
g_id = slugify(service.data[ATTR_NAME])
try:
await face.call_api("delete", f"persongroups/{g_id}")
face.store.pop(g_id)
entity = entities.pop(g_id)
await component.async_remove_entity(entity.entity_id)
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't delete group '%s' with error: %s", g_id, err)
hass.services.async_register(
DOMAIN, SERVICE_DELETE_GROUP, async_delete_group, schema=SCHEMA_GROUP_SERVICE
)
async def async_train_group(service: ServiceCall) -> None:
"""Train a person group."""
g_id = service.data[ATTR_GROUP]
try:
await face.call_api("post", f"persongroups/{g_id}/train")
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't train group '%s' with error: %s", g_id, err)
hass.services.async_register(
DOMAIN, SERVICE_TRAIN_GROUP, async_train_group, schema=SCHEMA_TRAIN_SERVICE
)
async def async_create_person(service: ServiceCall) -> None:
"""Create a person in a group."""
name = service.data[ATTR_NAME]
g_id = service.data[ATTR_GROUP]
try:
user_data = await face.call_api(
"post", f"persongroups/{g_id}/persons", {"name": name}
)
face.store[g_id][name] = user_data["personId"]
entities[g_id].async_write_ha_state()
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't create person '%s' with error: %s", name, err)
hass.services.async_register(
DOMAIN, SERVICE_CREATE_PERSON, async_create_person, schema=SCHEMA_PERSON_SERVICE
)
async def async_delete_person(service: ServiceCall) -> None:
"""Delete a person in a group."""
name = service.data[ATTR_NAME]
g_id = service.data[ATTR_GROUP]
p_id = face.store[g_id].get(name)
try:
await face.call_api("delete", f"persongroups/{g_id}/persons/{p_id}")
face.store[g_id].pop(name)
entities[g_id].async_write_ha_state()
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error("Can't delete person '%s' with error: %s", p_id, err)
hass.services.async_register(
DOMAIN, SERVICE_DELETE_PERSON, async_delete_person, schema=SCHEMA_PERSON_SERVICE
)
async def async_face_person(service: ServiceCall) -> None:
"""Add a new face picture to a person."""
g_id = service.data[ATTR_GROUP]
p_id = face.store[g_id].get(service.data[ATTR_PERSON])
camera_entity = service.data[ATTR_CAMERA_ENTITY]
try:
image = await camera.async_get_image(hass, camera_entity)
await face.call_api(
"post",
f"persongroups/{g_id}/persons/{p_id}/persistedFaces",
image.content,
binary=True,
)
# pylint: disable-next=home-assistant-action-swallowed-exception
except HomeAssistantError as err:
_LOGGER.error(
"Can't add an image of a person '%s' with error: %s", p_id, err
)
hass.services.async_register(
DOMAIN, SERVICE_FACE_PERSON, async_face_person, schema=SCHEMA_FACE_SERVICE
)
return True
class MicrosoftFaceGroupEntity(Entity):
"""Person-Group state/data Entity."""
_attr_should_poll = False
def __init__(self, api: MicrosoftFace, g_id: str, name: str) -> None:
"""Initialize person/group entity."""
self.entity_id = f"{DOMAIN}.{g_id}"
self._api = api
self._id = g_id
self._attr_name = name
@property
@override
def state(self) -> int:
"""Return the state of the entity."""
return len(self._api.store[self._id])
@property
@override
def extra_state_attributes(self) -> dict[str, Any]:
"""Return device specific state attributes."""
return dict(self._api.store[self._id])
class MicrosoftFace:
"""Microsoft Face api for Home Assistant."""
def __init__(
self,
hass: HomeAssistant,
server_loc: str,
api_key: str,
timeout: int,
component: EntityComponent[MicrosoftFaceGroupEntity],
entities: dict[str, MicrosoftFaceGroupEntity],
) -> None:
"""Initialize Microsoft Face api."""
self.hass = hass
self.websession = async_get_clientsession(hass)
self.timeout = timeout
self._api_key = api_key
self._server_url = f"https://{server_loc}.{FACE_API_URL}"
self._store: dict[str, dict[str, Any]] = {}
self._component = component
self._entities = entities
@property
def store(self) -> dict[str, dict[str, Any]]:
"""Store group/person data and IDs."""
return self._store
async def update_store(self) -> None:
"""Load all group/person data into local store."""
groups = await self.call_api("get", "persongroups")
remove_tasks: list[Coroutine[Any, Any, None]] = []
new_entities = []
for group in groups:
g_id = group["personGroupId"]
self._store[g_id] = {}
old_entity = self._entities.pop(g_id, None)
if old_entity:
remove_tasks.append(
self._component.async_remove_entity(old_entity.entity_id)
)
self._entities[g_id] = MicrosoftFaceGroupEntity(self, g_id, group["name"])
new_entities.append(self._entities[g_id])
persons = await self.call_api("get", f"persongroups/{g_id}/persons")
for person in persons:
self._store[g_id][person["name"]] = person["personId"]
if remove_tasks:
await asyncio.gather(*remove_tasks)
await self._component.async_add_entities(new_entities)
async def call_api(self, method, function, data=None, binary=False, params=None):
"""Make an api call."""
headers = {"Ocp-Apim-Subscription-Key": self._api_key}
url = self._server_url.format(function)
payload = None
if binary:
headers[CONTENT_TYPE] = "application/octet-stream"
payload = data
else:
headers[CONTENT_TYPE] = CONTENT_TYPE_JSON
if data is not None:
payload = json.dumps(data).encode()
else:
payload = None
try:
async with asyncio.timeout(self.timeout):
response = await self.websession.request(
method, url, data=payload, headers=headers, params=params
)
answer = await response.json()
_LOGGER.debug("Read from microsoft face api: %s", answer)
if response.status < 300:
return answer
_LOGGER.warning(
"Error %d microsoft face api %s", response.status, response.url
)
raise HomeAssistantError(answer["error"]["message"])
except aiohttp.ClientError:
_LOGGER.warning("Can't connect to microsoft face api")
except TimeoutError:
_LOGGER.warning("Timeout from microsoft face api %s", response.url)
raise HomeAssistantError("Network error on microsoft face api.")
@@ -0,0 +1,22 @@
{
"services": {
"create_group": {
"service": "mdi:account-multiple-plus"
},
"create_person": {
"service": "mdi:account-plus"
},
"delete_group": {
"service": "mdi:account-multiple-remove"
},
"delete_person": {
"service": "mdi:account-remove"
},
"face_person": {
"service": "mdi:face-man"
},
"train_group": {
"service": "mdi:account-multiple-check"
}
}
}
@@ -0,0 +1,9 @@
{
"domain": "microsoft_face",
"name": "Microsoft Face",
"codeowners": [],
"dependencies": ["camera"],
"documentation": "https://www.home-assistant.io/integrations/microsoft_face",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
@@ -0,0 +1,62 @@
create_group:
fields:
name:
required: true
example: family
selector:
text:
create_person:
fields:
group:
required: true
example: family
selector:
text:
name:
required: true
example: Hans
selector:
text:
delete_group:
fields:
name:
required: true
example: family
selector:
text:
delete_person:
fields:
group:
required: true
example: family
selector:
text:
name:
required: true
example: Hans
selector:
text:
face_person:
fields:
camera_entity:
required: true
example: camera.door
selector:
text:
group:
required: true
example: family
selector:
text:
person:
required: true
example: Hans
selector:
text:
train_group:
fields:
group:
required: true
example: family
selector:
text:
@@ -0,0 +1,80 @@
{
"services": {
"create_group": {
"description": "Creates a new person group.",
"fields": {
"name": {
"description": "Name of the group.",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Create group"
},
"create_person": {
"description": "Creates a new person in the group.",
"fields": {
"group": {
"description": "Name of the group.",
"name": "Group"
},
"name": {
"description": "Name of the person.",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Create person"
},
"delete_group": {
"description": "Deletes a new person group.",
"fields": {
"name": {
"description": "Name of the group.",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Delete group"
},
"delete_person": {
"description": "Deletes a person in the group.",
"fields": {
"group": {
"description": "Name of the group.",
"name": "Group"
},
"name": {
"description": "[%key:component::microsoft_face::services::create_person::fields::name::description%]",
"name": "[%key:common::config_flow::data::name%]"
}
},
"name": "Delete person"
},
"face_person": {
"description": "Adds a new picture to a person.",
"fields": {
"camera_entity": {
"description": "Camera to take a picture.",
"name": "Camera entity"
},
"group": {
"description": "Name of the group.",
"name": "Group"
},
"person": {
"description": "[%key:component::microsoft_face::services::create_person::fields::name::description%]",
"name": "Person"
}
},
"name": "Face person"
},
"train_group": {
"description": "Trains a person group.",
"fields": {
"group": {
"description": "Name of the group.",
"name": "Group"
}
},
"name": "Train group"
}
}
}
@@ -0,0 +1 @@
"""The microsoft_face_detect component."""
@@ -0,0 +1,125 @@
"""Component that will help set the Microsoft face detect processing."""
import logging
from typing import TYPE_CHECKING, override
import voluptuous as vol
from homeassistant.components.image_processing import (
ATTR_AGE,
ATTR_GENDER,
ATTR_GLASSES,
PLATFORM_SCHEMA as IMAGE_PROCESSING_PLATFORM_SCHEMA,
FaceInformation,
ImageProcessingFaceEntity,
)
from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE, MicrosoftFace
from homeassistant.const import CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE
from homeassistant.core import HomeAssistant, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
SUPPORTED_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER, ATTR_GLASSES]
CONF_ATTRIBUTES = "attributes"
DEFAULT_ATTRIBUTES = [ATTR_AGE, ATTR_GENDER]
def validate_attributes(list_attributes):
"""Validate face attributes."""
for attr in list_attributes:
if attr not in SUPPORTED_ATTRIBUTES:
raise vol.Invalid(f"Invalid attribute {attr}")
return list_attributes
PLATFORM_SCHEMA = IMAGE_PROCESSING_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_ATTRIBUTES, default=DEFAULT_ATTRIBUTES): vol.All(
cv.ensure_list, validate_attributes
)
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Microsoft Face detection platform."""
api = hass.data[DATA_MICROSOFT_FACE]
attributes: list[str] = config[CONF_ATTRIBUTES]
source: list[dict[str, str]] = config[CONF_SOURCE]
async_add_entities(
MicrosoftFaceDetectEntity(
camera[CONF_ENTITY_ID], api, attributes, camera.get(CONF_NAME)
)
for camera in source
)
class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
"""Microsoft Face API entity for identify."""
def __init__(
self,
camera_entity: str,
api: MicrosoftFace,
attributes: list[str],
name: str | None,
) -> None:
"""Initialize Microsoft Face."""
super().__init__()
self._api = api
self._attr_camera_entity = camera_entity
self._attributes = attributes
if name:
self._attr_name = name
else:
self._attr_name = f"MicrosoftFace {split_entity_id(camera_entity)[1]}"
@override
async def async_process_image(self, image: bytes) -> None:
"""Process image.
This method is a coroutine.
"""
face_data = None
try:
face_data = await self._api.call_api(
"post",
"detect",
image,
binary=True,
params={"returnFaceAttributes": ",".join(self._attributes)},
)
except HomeAssistantError as err:
_LOGGER.error("Can't process image on microsoft face: %s", err)
return
if not face_data:
face_data = []
faces: list[FaceInformation] = []
for face in face_data:
face_attr = FaceInformation()
for attr in self._attributes:
if TYPE_CHECKING:
assert attr in SUPPORTED_ATTRIBUTES
if attr in face["faceAttributes"]:
face_attr[attr] = face["faceAttributes"][attr] # type: ignore[literal-required]
if face_attr:
faces.append(face_attr)
self.async_process_faces(faces, len(face_data))
@@ -0,0 +1,9 @@
{
"domain": "microsoft_face_detect",
"name": "Microsoft Face Detect",
"codeowners": [],
"dependencies": ["microsoft_face"],
"documentation": "https://www.home-assistant.io/integrations/microsoft_face_detect",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
@@ -0,0 +1 @@
"""The microsoft_face_identify component."""
@@ -0,0 +1,121 @@
"""Component that will help set the Microsoft face for verify processing."""
import logging
from typing import override
import voluptuous as vol
from homeassistant.components.image_processing import (
ATTR_CONFIDENCE,
CONF_CONFIDENCE,
PLATFORM_SCHEMA as IMAGE_PROCESSING_PLATFORM_SCHEMA,
FaceInformation,
ImageProcessingFaceEntity,
)
from homeassistant.components.microsoft_face import DATA_MICROSOFT_FACE, MicrosoftFace
from homeassistant.const import ATTR_NAME, CONF_ENTITY_ID, CONF_NAME, CONF_SOURCE
from homeassistant.core import HomeAssistant, split_entity_id
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
_LOGGER = logging.getLogger(__name__)
CONF_GROUP = "group"
PLATFORM_SCHEMA = IMAGE_PROCESSING_PLATFORM_SCHEMA.extend(
{vol.Required(CONF_GROUP): cv.slugify}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Microsoft Face identify platform."""
api = hass.data[DATA_MICROSOFT_FACE]
face_group: str = config[CONF_GROUP]
confidence: float = config[CONF_CONFIDENCE]
source: list[dict[str, str]] = config[CONF_SOURCE]
async_add_entities(
MicrosoftFaceIdentifyEntity(
camera[CONF_ENTITY_ID],
api,
face_group,
confidence,
camera.get(CONF_NAME),
)
for camera in source
)
class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
"""Representation of the Microsoft Face API entity for identify."""
def __init__(
self,
camera_entity: str,
api: MicrosoftFace,
face_group: str,
confidence: float,
name: str | None,
) -> None:
"""Initialize the Microsoft Face API."""
super().__init__()
self._api = api
self._attr_camera_entity = camera_entity
self._attr_confidence = confidence
self._face_group = face_group
if name:
self._attr_name = name
else:
self._attr_name = f"MicrosoftFace {split_entity_id(camera_entity)[1]}"
@override
async def async_process_image(self, image: bytes) -> None:
"""Process image.
This method is a coroutine.
"""
detect = []
try:
face_data = await self._api.call_api("post", "detect", image, binary=True)
if face_data:
face_ids = [data["faceId"] for data in face_data]
detect = await self._api.call_api(
"post",
"identify",
{"faceIds": face_ids, "personGroupId": self._face_group},
)
except HomeAssistantError as err:
_LOGGER.error("Can't process image on Microsoft face: %s", err)
return
# Parse data
known_faces: list[FaceInformation] = []
total = 0
for face in detect:
total += 1
if not face["candidates"]:
continue
data = face["candidates"][0]
name = ""
for s_name, s_id in self._api.store[self._face_group].items():
if data["personId"] == s_id:
name = s_name
break
known_faces.append(
{ATTR_NAME: name, ATTR_CONFIDENCE: data["confidence"] * 100}
)
self.async_process_faces(known_faces, total)
@@ -0,0 +1,9 @@
{
"domain": "microsoft_face_identify",
"name": "Microsoft Face Identify",
"codeowners": [],
"dependencies": ["microsoft_face"],
"documentation": "https://www.home-assistant.io/integrations/microsoft_face_identify",
"iot_class": "cloud_push",
"quality_scale": "legacy"
}
+4 -10
View File
@@ -114,26 +114,20 @@ class MieleDataUpdateCoordinator(DataUpdateCoordinator[MieleCoordinatorData]):
async def callback_update_data(self, devices_json: dict[str, dict]) -> None:
"""Handle data update from the API."""
updated_devices = {
devices = {
device_id: MieleDevice(device) for device_id, device in devices_json.items()
}
self.async_set_updated_data(
MieleCoordinatorData(
devices={**self.data.devices, **updated_devices},
actions=self.data.actions,
)
MieleCoordinatorData(devices=devices, actions=self.data.actions)
)
async def callback_update_actions(self, actions_json: dict[str, dict]) -> None:
"""Handle data update from the API."""
updated_actions = {
actions = {
device_id: MieleAction(action) for device_id, action in actions_json.items()
}
self.async_set_updated_data(
MieleCoordinatorData(
devices=self.data.devices,
actions={**self.data.actions, **updated_actions},
)
MieleCoordinatorData(devices=self.data.devices, actions=actions)
)
+10 -8
View File
@@ -11,10 +11,12 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
EntityCategory,
UnitOfEnergy,
UnitOfPower,
UnitOfRatio,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
@@ -63,7 +65,7 @@ HEATER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="control_signal",
translation_key="control_signal",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
)
@@ -78,26 +80,26 @@ SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=BATTERY,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
),
SensorEntityDescription(
key=ECO2,
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
translation_key="estimated_co2",
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
key=TVOC,
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
translation_key="tvoc",
state_class=SensorStateClass.MEASUREMENT,
),
@@ -107,7 +109,7 @@ LOCAL_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key="control_signal",
translation_key="control_signal",
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
SensorEntityDescription(
@@ -131,7 +133,7 @@ SOCKET_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=HUMIDITY,
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
*HEATER_SENSOR_TYPES,
+522 -3
View File
@@ -2,20 +2,539 @@
import logging
from homeassistant.const import SERVICE_RELOAD
import voluptuous as vol
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.components.cover import (
DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASSES_SCHEMA as SENSOR_DEVICE_CLASSES_SCHEMA,
STATE_CLASSES_SCHEMA as SENSOR_STATE_CLASSES_SCHEMA,
)
from homeassistant.components.switch import (
DEVICE_CLASSES_SCHEMA as SWITCH_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.const import (
CONF_ADDRESS,
CONF_BINARY_SENSORS,
CONF_COMMAND_OFF,
CONF_COMMAND_ON,
CONF_COUNT,
CONF_COVERS,
CONF_DELAY,
CONF_DEVICE_CLASS,
CONF_HOST,
CONF_LIGHTS,
CONF_METHOD,
CONF_NAME,
CONF_OFFSET,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SENSORS,
CONF_SLAVE,
CONF_STRUCTURE,
CONF_SWITCHES,
CONF_TEMPERATURE_UNIT,
CONF_TIMEOUT,
CONF_TYPE,
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT,
SERVICE_RELOAD,
)
from homeassistant.core import Event, HomeAssistant, ServiceCall
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import async_get_platforms
from homeassistant.helpers.reload import async_integration_yaml_config
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
from .const import (
CALL_TYPE_COIL,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
CALL_TYPE_X_COILS,
CALL_TYPE_X_REGISTER_HOLDINGS,
CONF_BAUDRATE,
CONF_BRIGHTNESS_REGISTER,
CONF_BYTESIZE,
CONF_CLIMATES,
CONF_COLOR_TEMP_REGISTER,
CONF_CURRENT_TEMP_OFFSET,
CONF_CURRENT_TEMP_SCALE,
CONF_DATA_TYPE,
CONF_DEVICE_ADDRESS,
CONF_FAN_MODE_AUTO,
CONF_FAN_MODE_DIFFUSE,
CONF_FAN_MODE_FOCUS,
CONF_FAN_MODE_HIGH,
CONF_FAN_MODE_LOW,
CONF_FAN_MODE_MEDIUM,
CONF_FAN_MODE_MIDDLE,
CONF_FAN_MODE_OFF,
CONF_FAN_MODE_ON,
CONF_FAN_MODE_REGISTER,
CONF_FAN_MODE_TOP,
CONF_FAN_MODE_VALUES,
CONF_FANS,
CONF_HVAC_ACTION_COOLING,
CONF_HVAC_ACTION_DEFROSTING,
CONF_HVAC_ACTION_DRYING,
CONF_HVAC_ACTION_FAN,
CONF_HVAC_ACTION_HEATING,
CONF_HVAC_ACTION_IDLE,
CONF_HVAC_ACTION_OFF,
CONF_HVAC_ACTION_PREHEATING,
CONF_HVAC_ACTION_REGISTER,
CONF_HVAC_ACTION_VALUES,
CONF_HVAC_MODE_AUTO,
CONF_HVAC_MODE_COOL,
CONF_HVAC_MODE_DRY,
CONF_HVAC_MODE_FAN_ONLY,
CONF_HVAC_MODE_HEAT,
CONF_HVAC_MODE_HEAT_COOL,
CONF_HVAC_MODE_OFF,
CONF_HVAC_MODE_REGISTER,
CONF_HVAC_MODE_VALUES,
CONF_HVAC_OFF_VALUE,
CONF_HVAC_ON_VALUE,
CONF_HVAC_ONOFF_COIL,
CONF_HVAC_ONOFF_REGISTER,
CONF_INPUT_TYPE,
CONF_MAX_TEMP,
CONF_MAX_VALUE,
CONF_MIN_TEMP,
CONF_MIN_VALUE,
CONF_MSG_WAIT,
CONF_NAN_VALUE,
CONF_PARITY,
CONF_PRECISION,
CONF_SCALE,
CONF_SLAVE_COUNT,
CONF_STATE_CLOSED,
CONF_STATE_CLOSING,
CONF_STATE_OFF,
CONF_STATE_ON,
CONF_STATE_OPEN,
CONF_STATE_OPENING,
CONF_STATUS_REGISTER,
CONF_STATUS_REGISTER_TYPE,
CONF_STEP,
CONF_STOPBITS,
CONF_SWAP,
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
CONF_SWAP_WORD_BYTE,
CONF_SWING_MODE_REGISTER,
CONF_SWING_MODE_SWING_BOTH,
CONF_SWING_MODE_SWING_HORIZ,
CONF_SWING_MODE_SWING_OFF,
CONF_SWING_MODE_SWING_ON,
CONF_SWING_MODE_SWING_VERT,
CONF_SWING_MODE_VALUES,
CONF_TARGET_TEMP,
CONF_TARGET_TEMP_OFFSET,
CONF_TARGET_TEMP_SCALE,
CONF_TARGET_TEMP_WRITE_REGISTERS,
CONF_VERIFY,
CONF_VIRTUAL_COUNT,
CONF_WRITE_REGISTERS,
CONF_WRITE_TYPE,
CONF_ZERO_SUPPRESS,
DEFAULT_HUB,
DEFAULT_HVAC_OFF_VALUE,
DEFAULT_HVAC_ON_VALUE,
DEFAULT_SCAN_INTERVAL,
DEFAULT_TEMP_UNIT,
DOMAIN,
RTUOVERTCP,
SERIAL,
TCP,
UDP,
DataType,
)
from .modbus import DATA_MODBUS_HUBS, ModbusHub, async_modbus_setup
from .schemas import CONFIG_SCHEMA # noqa: F401
from .validators import (
duplicate_fan_mode_validator,
duplicate_swing_mode_validator,
ensure_and_check_conflicting_scales_and_offsets,
hvac_fixedsize_reglist_validator,
nan_validator,
not_zero_value,
register_int_list_validator,
struct_validator,
)
_LOGGER = logging.getLogger(__name__)
BASE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string})
BASE_COMPONENT_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_ADDRESS): cv.positive_int,
vol.Exclusive(CONF_DEVICE_ADDRESS, "slave_addr"): cv.positive_int,
vol.Exclusive(CONF_SLAVE, "slave_addr"): cv.positive_int,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
BASE_STRUCT_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
]
),
vol.Optional(CONF_COUNT): cv.positive_int,
vol.Optional(CONF_DATA_TYPE, default=DataType.INT16): vol.In(
[
DataType.INT16,
DataType.INT32,
DataType.INT64,
DataType.UINT16,
DataType.UINT32,
DataType.UINT64,
DataType.FLOAT16,
DataType.FLOAT32,
DataType.FLOAT64,
DataType.STRING,
DataType.CUSTOM,
]
),
vol.Optional(CONF_STRUCTURE): cv.string,
vol.Optional(CONF_SCALE): vol.All(
vol.Coerce(float), lambda v: not_zero_value(v, "Scale cannot be zero.")
),
vol.Optional(CONF_OFFSET): vol.Coerce(float),
vol.Optional(CONF_PRECISION): cv.positive_int,
vol.Optional(
CONF_SWAP,
): vol.In(
[
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
CONF_SWAP_WORD_BYTE,
]
),
}
)
BASE_SWITCH_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(CONF_WRITE_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_COIL,
CALL_TYPE_X_COILS,
CALL_TYPE_X_REGISTER_HOLDINGS,
]
),
vol.Optional(CONF_COMMAND_OFF, default=0x00): cv.positive_int,
vol.Optional(CONF_COMMAND_ON, default=0x01): cv.positive_int,
vol.Optional(CONF_VERIFY): vol.Maybe(
{
vol.Optional(CONF_ADDRESS): cv.positive_int,
vol.Optional(CONF_INPUT_TYPE): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_INPUT,
CALL_TYPE_COIL,
CALL_TYPE_X_COILS,
CALL_TYPE_X_REGISTER_HOLDINGS,
]
),
vol.Optional(CONF_STATE_OFF): vol.All(
cv.ensure_list, [cv.positive_int]
),
vol.Optional(CONF_STATE_ON): vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
}
),
}
)
CLIMATE_SCHEMA = vol.All(
BASE_STRUCT_SCHEMA.extend(
{
vol.Required(CONF_TARGET_TEMP): hvac_fixedsize_reglist_validator,
vol.Optional(CONF_TARGET_TEMP_WRITE_REGISTERS, default=False): cv.boolean,
vol.Optional(CONF_MAX_TEMP, default=35): vol.Coerce(int),
vol.Optional(CONF_MIN_TEMP, default=5): vol.Coerce(int),
vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float),
vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string,
vol.Exclusive(CONF_HVAC_ONOFF_COIL, "hvac_onoff_type"): cv.positive_int,
vol.Exclusive(CONF_HVAC_ONOFF_REGISTER, "hvac_onoff_type"): cv.positive_int,
vol.Optional(CONF_CURRENT_TEMP_SCALE): vol.All(
vol.Coerce(float),
lambda v: not_zero_value(
v, "Current temperature scale cannot be zero."
),
),
vol.Optional(CONF_TARGET_TEMP_SCALE): vol.All(
vol.Coerce(float),
lambda v: not_zero_value(v, "Target temperature scale cannot be zero."),
),
vol.Optional(CONF_CURRENT_TEMP_OFFSET): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP_OFFSET): vol.Coerce(float),
vol.Optional(
CONF_HVAC_ON_VALUE, default=DEFAULT_HVAC_ON_VALUE
): cv.positive_int,
vol.Optional(
CONF_HVAC_OFF_VALUE, default=DEFAULT_HVAC_OFF_VALUE
): cv.positive_int,
vol.Optional(CONF_WRITE_REGISTERS, default=False): cv.boolean,
vol.Optional(CONF_HVAC_MODE_REGISTER): vol.Maybe(
{
CONF_ADDRESS: cv.positive_int,
CONF_HVAC_MODE_VALUES: {
vol.Optional(CONF_HVAC_MODE_OFF): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_HEAT): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_COOL): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_HEAT_COOL): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_AUTO): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_DRY): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_FAN_ONLY): vol.Any(
cv.positive_int, [cv.positive_int]
),
},
vol.Optional(CONF_WRITE_REGISTERS, default=False): cv.boolean,
}
),
vol.Optional(CONF_HVAC_ACTION_REGISTER): vol.Maybe(
{
CONF_ADDRESS: cv.positive_int,
CONF_HVAC_ACTION_VALUES: {
vol.Optional(CONF_HVAC_ACTION_COOLING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_DEFROSTING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_DRYING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_FAN): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_HEATING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_IDLE): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_OFF): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_PREHEATING): vol.Any(
cv.positive_int, [cv.positive_int]
),
},
vol.Optional(
CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING
): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
]
),
}
),
vol.Optional(CONF_FAN_MODE_REGISTER): vol.Maybe(
vol.All(
{
vol.Required(CONF_ADDRESS): register_int_list_validator,
CONF_FAN_MODE_VALUES: {
vol.Optional(CONF_FAN_MODE_ON): cv.positive_int,
vol.Optional(CONF_FAN_MODE_OFF): cv.positive_int,
vol.Optional(CONF_FAN_MODE_AUTO): cv.positive_int,
vol.Optional(CONF_FAN_MODE_LOW): cv.positive_int,
vol.Optional(CONF_FAN_MODE_MEDIUM): cv.positive_int,
vol.Optional(CONF_FAN_MODE_HIGH): cv.positive_int,
vol.Optional(CONF_FAN_MODE_TOP): cv.positive_int,
vol.Optional(CONF_FAN_MODE_MIDDLE): cv.positive_int,
vol.Optional(CONF_FAN_MODE_FOCUS): cv.positive_int,
vol.Optional(CONF_FAN_MODE_DIFFUSE): cv.positive_int,
},
},
duplicate_fan_mode_validator,
),
),
vol.Optional(CONF_SWING_MODE_REGISTER): vol.Maybe(
vol.All(
{
vol.Required(CONF_ADDRESS): register_int_list_validator,
CONF_SWING_MODE_VALUES: {
vol.Optional(CONF_SWING_MODE_SWING_ON): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_OFF): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_HORIZ): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_VERT): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_BOTH): cv.positive_int,
},
},
duplicate_swing_mode_validator,
)
),
},
),
ensure_and_check_conflicting_scales_and_offsets,
)
COVERS_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(
CONF_INPUT_TYPE,
default=CALL_TYPE_REGISTER_HOLDING,
): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_COIL,
]
),
vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLOSED, default=0): cv.positive_int,
vol.Optional(CONF_STATE_CLOSING, default=3): cv.positive_int,
vol.Optional(CONF_STATE_OPEN, default=1): cv.positive_int,
vol.Optional(CONF_STATE_OPENING, default=2): cv.positive_int,
vol.Optional(CONF_STATUS_REGISTER): cv.positive_int,
vol.Optional(
CONF_STATUS_REGISTER_TYPE,
default=CALL_TYPE_REGISTER_HOLDING,
): vol.In([CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT]),
}
)
SWITCH_SCHEMA = BASE_SWITCH_SCHEMA.extend(
{
vol.Optional(CONF_DEVICE_CLASS): SWITCH_DEVICE_CLASSES_SCHEMA,
}
)
LIGHT_SCHEMA = BASE_SWITCH_SCHEMA.extend(
{
vol.Optional(CONF_BRIGHTNESS_REGISTER): cv.positive_int,
vol.Optional(CONF_COLOR_TEMP_REGISTER): cv.positive_int,
vol.Optional(CONF_MIN_TEMP): cv.positive_int,
vol.Optional(CONF_MAX_TEMP): cv.positive_int,
}
)
FAN_SCHEMA = BASE_SWITCH_SCHEMA.extend({})
SENSOR_SCHEMA = vol.All(
BASE_STRUCT_SCHEMA.extend(
{
vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLASS): SENSOR_STATE_CLASSES_SCHEMA,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Exclusive(CONF_VIRTUAL_COUNT, "vir_sen_count"): cv.positive_int,
vol.Exclusive(CONF_SLAVE_COUNT, "vir_sen_count"): cv.positive_int,
vol.Optional(CONF_MIN_VALUE): vol.Coerce(float),
vol.Optional(CONF_MAX_VALUE): vol.Coerce(float),
vol.Optional(CONF_NAN_VALUE): nan_validator,
vol.Optional(CONF_ZERO_SUPPRESS): cv.positive_float,
}
),
)
BINARY_SENSOR_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_COIL): vol.In(
[
CALL_TYPE_COIL,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
]
),
vol.Exclusive(CONF_VIRTUAL_COUNT, "vir_bin_count"): cv.positive_int,
vol.Exclusive(CONF_SLAVE_COUNT, "vir_bin_count"): cv.positive_int,
}
)
MODBUS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string,
vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
vol.Optional(CONF_MSG_WAIT): cv.positive_int,
vol.Optional(CONF_BINARY_SENSORS): vol.All(
cv.ensure_list, [BINARY_SENSOR_SCHEMA]
),
vol.Optional(CONF_CLIMATES): vol.All(
cv.ensure_list, [vol.All(CLIMATE_SCHEMA, struct_validator)]
),
vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]),
vol.Optional(CONF_LIGHTS): vol.All(cv.ensure_list, [LIGHT_SCHEMA]),
vol.Optional(CONF_SENSORS): vol.All(
cv.ensure_list, [vol.All(SENSOR_SCHEMA, struct_validator)]
),
vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCH_SCHEMA]),
vol.Optional(CONF_FANS): vol.All(cv.ensure_list, [FAN_SCHEMA]),
},
extra=vol.ALLOW_EXTRA,
)
SERIAL_SCHEMA = MODBUS_SCHEMA.extend(
{
vol.Required(CONF_TYPE): SERIAL,
vol.Required(CONF_BAUDRATE): cv.positive_int,
vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8),
vol.Required(CONF_METHOD): vol.Any("rtu", "ascii"),
vol.Required(CONF_PORT): cv.string,
vol.Required(CONF_PARITY): vol.Any("E", "O", "N"),
vol.Required(CONF_STOPBITS): vol.Any(1, 2),
}
)
ETHERNET_SCHEMA = MODBUS_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Required(CONF_TYPE): vol.Any(TCP, UDP, RTUOVERTCP),
}
)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.ensure_list,
[
vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA),
],
),
},
extra=vol.ALLOW_EXTRA,
)
def get_hub(hass: HomeAssistant, name: str) -> ModbusHub:
"""Return modbus hub with name."""
return hass.data[DATA_MODBUS_HUBS][name]
-537
View File
@@ -1,537 +0,0 @@
"""Voluptuous schemas for the Modbus integration."""
import voluptuous as vol
from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA as BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.components.cover import (
DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
DEVICE_CLASSES_SCHEMA as SENSOR_DEVICE_CLASSES_SCHEMA,
STATE_CLASSES_SCHEMA as SENSOR_STATE_CLASSES_SCHEMA,
)
from homeassistant.components.switch import (
DEVICE_CLASSES_SCHEMA as SWITCH_DEVICE_CLASSES_SCHEMA,
)
from homeassistant.const import (
CONF_ADDRESS,
CONF_BINARY_SENSORS,
CONF_COMMAND_OFF,
CONF_COMMAND_ON,
CONF_COUNT,
CONF_COVERS,
CONF_DELAY,
CONF_DEVICE_CLASS,
CONF_HOST,
CONF_LIGHTS,
CONF_METHOD,
CONF_NAME,
CONF_OFFSET,
CONF_PORT,
CONF_SCAN_INTERVAL,
CONF_SENSORS,
CONF_SLAVE,
CONF_STRUCTURE,
CONF_SWITCHES,
CONF_TEMPERATURE_UNIT,
CONF_TIMEOUT,
CONF_TYPE,
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT,
Platform,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import VolSchemaType
from .const import (
CALL_TYPE_COIL,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
CALL_TYPE_X_COILS,
CALL_TYPE_X_REGISTER_HOLDINGS,
CONF_BAUDRATE,
CONF_BRIGHTNESS_REGISTER,
CONF_BYTESIZE,
CONF_CLIMATES,
CONF_COLOR_TEMP_REGISTER,
CONF_CURRENT_TEMP_OFFSET,
CONF_CURRENT_TEMP_SCALE,
CONF_DATA_TYPE,
CONF_DEVICE_ADDRESS,
CONF_FAN_MODE_AUTO,
CONF_FAN_MODE_DIFFUSE,
CONF_FAN_MODE_FOCUS,
CONF_FAN_MODE_HIGH,
CONF_FAN_MODE_LOW,
CONF_FAN_MODE_MEDIUM,
CONF_FAN_MODE_MIDDLE,
CONF_FAN_MODE_OFF,
CONF_FAN_MODE_ON,
CONF_FAN_MODE_REGISTER,
CONF_FAN_MODE_TOP,
CONF_FAN_MODE_VALUES,
CONF_FANS,
CONF_HVAC_ACTION_COOLING,
CONF_HVAC_ACTION_DEFROSTING,
CONF_HVAC_ACTION_DRYING,
CONF_HVAC_ACTION_FAN,
CONF_HVAC_ACTION_HEATING,
CONF_HVAC_ACTION_IDLE,
CONF_HVAC_ACTION_OFF,
CONF_HVAC_ACTION_PREHEATING,
CONF_HVAC_ACTION_REGISTER,
CONF_HVAC_ACTION_VALUES,
CONF_HVAC_MODE_AUTO,
CONF_HVAC_MODE_COOL,
CONF_HVAC_MODE_DRY,
CONF_HVAC_MODE_FAN_ONLY,
CONF_HVAC_MODE_HEAT,
CONF_HVAC_MODE_HEAT_COOL,
CONF_HVAC_MODE_OFF,
CONF_HVAC_MODE_REGISTER,
CONF_HVAC_MODE_VALUES,
CONF_HVAC_OFF_VALUE,
CONF_HVAC_ON_VALUE,
CONF_HVAC_ONOFF_COIL,
CONF_HVAC_ONOFF_REGISTER,
CONF_INPUT_TYPE,
CONF_MAX_TEMP,
CONF_MAX_VALUE,
CONF_MIN_TEMP,
CONF_MIN_VALUE,
CONF_MSG_WAIT,
CONF_NAN_VALUE,
CONF_PARITY,
CONF_PRECISION,
CONF_SCALE,
CONF_SLAVE_COUNT,
CONF_STATE_CLOSED,
CONF_STATE_CLOSING,
CONF_STATE_OFF,
CONF_STATE_ON,
CONF_STATE_OPEN,
CONF_STATE_OPENING,
CONF_STATUS_REGISTER,
CONF_STATUS_REGISTER_TYPE,
CONF_STEP,
CONF_STOPBITS,
CONF_SWAP,
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
CONF_SWAP_WORD_BYTE,
CONF_SWING_MODE_REGISTER,
CONF_SWING_MODE_SWING_BOTH,
CONF_SWING_MODE_SWING_HORIZ,
CONF_SWING_MODE_SWING_OFF,
CONF_SWING_MODE_SWING_ON,
CONF_SWING_MODE_SWING_VERT,
CONF_SWING_MODE_VALUES,
CONF_TARGET_TEMP,
CONF_TARGET_TEMP_OFFSET,
CONF_TARGET_TEMP_SCALE,
CONF_TARGET_TEMP_WRITE_REGISTERS,
CONF_VERIFY,
CONF_VIRTUAL_COUNT,
CONF_WRITE_REGISTERS,
CONF_WRITE_TYPE,
CONF_ZERO_SUPPRESS,
DEFAULT_HUB,
DEFAULT_HVAC_OFF_VALUE,
DEFAULT_HVAC_ON_VALUE,
DEFAULT_SCAN_INTERVAL,
DEFAULT_TEMP_UNIT,
DOMAIN,
RTUOVERTCP,
SERIAL,
TCP,
UDP,
DataType,
)
from .validators import (
duplicate_fan_mode_validator,
duplicate_swing_mode_validator,
ensure_and_check_conflicting_scales_and_offsets,
hvac_fixedsize_reglist_validator,
nan_validator,
not_zero_value,
register_int_list_validator,
struct_validator,
)
BASE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string})
BASE_COMPONENT_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_ADDRESS): cv.positive_int,
vol.Exclusive(CONF_DEVICE_ADDRESS, "slave_addr"): cv.positive_int,
vol.Exclusive(CONF_SLAVE, "slave_addr"): cv.positive_int,
vol.Optional(
CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL
): cv.positive_int,
vol.Optional(CONF_UNIQUE_ID): cv.string,
}
)
BASE_STRUCT_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
]
),
vol.Optional(CONF_COUNT): cv.positive_int,
vol.Optional(CONF_DATA_TYPE, default=DataType.INT16): vol.In(
[
DataType.INT16,
DataType.INT32,
DataType.INT64,
DataType.UINT16,
DataType.UINT32,
DataType.UINT64,
DataType.FLOAT16,
DataType.FLOAT32,
DataType.FLOAT64,
DataType.STRING,
DataType.CUSTOM,
]
),
vol.Optional(CONF_STRUCTURE): cv.string,
vol.Optional(CONF_SCALE): vol.All(
vol.Coerce(float), lambda v: not_zero_value(v, "Scale cannot be zero.")
),
vol.Optional(CONF_OFFSET): vol.Coerce(float),
vol.Optional(CONF_PRECISION): cv.positive_int,
vol.Optional(
CONF_SWAP,
): vol.In(
[
CONF_SWAP_BYTE,
CONF_SWAP_WORD,
CONF_SWAP_WORD_BYTE,
]
),
}
)
BASE_SWITCH_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(CONF_WRITE_TYPE, default=CALL_TYPE_REGISTER_HOLDING): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_COIL,
CALL_TYPE_X_COILS,
CALL_TYPE_X_REGISTER_HOLDINGS,
]
),
vol.Optional(CONF_COMMAND_OFF, default=0x00): cv.positive_int,
vol.Optional(CONF_COMMAND_ON, default=0x01): cv.positive_int,
vol.Optional(CONF_VERIFY): vol.Maybe(
{
vol.Optional(CONF_ADDRESS): cv.positive_int,
vol.Optional(CONF_INPUT_TYPE): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_INPUT,
CALL_TYPE_COIL,
CALL_TYPE_X_COILS,
CALL_TYPE_X_REGISTER_HOLDINGS,
]
),
vol.Optional(CONF_STATE_OFF): vol.All(
cv.ensure_list, [cv.positive_int]
),
vol.Optional(CONF_STATE_ON): vol.All(cv.ensure_list, [cv.positive_int]),
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
}
),
}
)
CLIMATE_SCHEMA = vol.All(
BASE_STRUCT_SCHEMA.extend(
{
vol.Required(CONF_TARGET_TEMP): hvac_fixedsize_reglist_validator,
vol.Optional(CONF_TARGET_TEMP_WRITE_REGISTERS, default=False): cv.boolean,
vol.Optional(CONF_MAX_TEMP, default=35): vol.Coerce(int),
vol.Optional(CONF_MIN_TEMP, default=5): vol.Coerce(int),
vol.Optional(CONF_STEP, default=0.5): vol.Coerce(float),
vol.Optional(CONF_TEMPERATURE_UNIT, default=DEFAULT_TEMP_UNIT): cv.string,
vol.Exclusive(CONF_HVAC_ONOFF_COIL, "hvac_onoff_type"): cv.positive_int,
vol.Exclusive(CONF_HVAC_ONOFF_REGISTER, "hvac_onoff_type"): cv.positive_int,
vol.Optional(CONF_CURRENT_TEMP_SCALE): vol.All(
vol.Coerce(float),
lambda v: not_zero_value(
v, "Current temperature scale cannot be zero."
),
),
vol.Optional(CONF_TARGET_TEMP_SCALE): vol.All(
vol.Coerce(float),
lambda v: not_zero_value(v, "Target temperature scale cannot be zero."),
),
vol.Optional(CONF_CURRENT_TEMP_OFFSET): vol.Coerce(float),
vol.Optional(CONF_TARGET_TEMP_OFFSET): vol.Coerce(float),
vol.Optional(
CONF_HVAC_ON_VALUE, default=DEFAULT_HVAC_ON_VALUE
): cv.positive_int,
vol.Optional(
CONF_HVAC_OFF_VALUE, default=DEFAULT_HVAC_OFF_VALUE
): cv.positive_int,
vol.Optional(CONF_WRITE_REGISTERS, default=False): cv.boolean,
vol.Optional(CONF_HVAC_MODE_REGISTER): vol.Maybe(
{
CONF_ADDRESS: cv.positive_int,
CONF_HVAC_MODE_VALUES: {
vol.Optional(CONF_HVAC_MODE_OFF): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_HEAT): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_COOL): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_HEAT_COOL): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_AUTO): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_DRY): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_MODE_FAN_ONLY): vol.Any(
cv.positive_int, [cv.positive_int]
),
},
vol.Optional(CONF_WRITE_REGISTERS, default=False): cv.boolean,
}
),
vol.Optional(CONF_HVAC_ACTION_REGISTER): vol.Maybe(
{
CONF_ADDRESS: cv.positive_int,
CONF_HVAC_ACTION_VALUES: {
vol.Optional(CONF_HVAC_ACTION_COOLING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_DEFROSTING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_DRYING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_FAN): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_HEATING): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_IDLE): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_OFF): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(CONF_HVAC_ACTION_PREHEATING): vol.Any(
cv.positive_int, [cv.positive_int]
),
},
vol.Optional(
CONF_INPUT_TYPE, default=CALL_TYPE_REGISTER_HOLDING
): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
]
),
}
),
vol.Optional(CONF_FAN_MODE_REGISTER): vol.Maybe(
vol.All(
{
vol.Required(CONF_ADDRESS): register_int_list_validator,
CONF_FAN_MODE_VALUES: {
vol.Optional(CONF_FAN_MODE_ON): cv.positive_int,
vol.Optional(CONF_FAN_MODE_OFF): cv.positive_int,
vol.Optional(CONF_FAN_MODE_AUTO): cv.positive_int,
vol.Optional(CONF_FAN_MODE_LOW): cv.positive_int,
vol.Optional(CONF_FAN_MODE_MEDIUM): cv.positive_int,
vol.Optional(CONF_FAN_MODE_HIGH): cv.positive_int,
vol.Optional(CONF_FAN_MODE_TOP): cv.positive_int,
vol.Optional(CONF_FAN_MODE_MIDDLE): cv.positive_int,
vol.Optional(CONF_FAN_MODE_FOCUS): cv.positive_int,
vol.Optional(CONF_FAN_MODE_DIFFUSE): cv.positive_int,
},
},
duplicate_fan_mode_validator,
),
),
vol.Optional(CONF_SWING_MODE_REGISTER): vol.Maybe(
vol.All(
{
vol.Required(CONF_ADDRESS): register_int_list_validator,
CONF_SWING_MODE_VALUES: {
vol.Optional(CONF_SWING_MODE_SWING_ON): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_OFF): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_HORIZ): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_VERT): cv.positive_int,
vol.Optional(CONF_SWING_MODE_SWING_BOTH): cv.positive_int,
},
},
duplicate_swing_mode_validator,
)
),
},
),
ensure_and_check_conflicting_scales_and_offsets,
)
COVERS_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(
CONF_INPUT_TYPE,
default=CALL_TYPE_REGISTER_HOLDING,
): vol.In(
[
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_COIL,
]
),
vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLOSED, default=0): cv.positive_int,
vol.Optional(CONF_STATE_CLOSING, default=3): cv.positive_int,
vol.Optional(CONF_STATE_OPEN, default=1): cv.positive_int,
vol.Optional(CONF_STATE_OPENING, default=2): cv.positive_int,
vol.Optional(CONF_STATUS_REGISTER): cv.positive_int,
vol.Optional(
CONF_STATUS_REGISTER_TYPE,
default=CALL_TYPE_REGISTER_HOLDING,
): vol.In([CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT]),
}
)
SWITCH_SCHEMA = BASE_SWITCH_SCHEMA.extend(
{
vol.Optional(CONF_DEVICE_CLASS): SWITCH_DEVICE_CLASSES_SCHEMA,
}
)
LIGHT_SCHEMA = BASE_SWITCH_SCHEMA.extend(
{
vol.Optional(CONF_BRIGHTNESS_REGISTER): cv.positive_int,
vol.Optional(CONF_COLOR_TEMP_REGISTER): cv.positive_int,
vol.Optional(CONF_MIN_TEMP): cv.positive_int,
vol.Optional(CONF_MAX_TEMP): cv.positive_int,
}
)
FAN_SCHEMA = BASE_SWITCH_SCHEMA.extend({})
SENSOR_SCHEMA = vol.All(
BASE_STRUCT_SCHEMA.extend(
{
vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_STATE_CLASS): SENSOR_STATE_CLASSES_SCHEMA,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Exclusive(CONF_VIRTUAL_COUNT, "vir_sen_count"): cv.positive_int,
vol.Exclusive(CONF_SLAVE_COUNT, "vir_sen_count"): cv.positive_int,
vol.Optional(CONF_MIN_VALUE): vol.Coerce(float),
vol.Optional(CONF_MAX_VALUE): vol.Coerce(float),
vol.Optional(CONF_NAN_VALUE): nan_validator,
vol.Optional(CONF_ZERO_SUPPRESS): cv.positive_float,
}
),
)
BINARY_SENSOR_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
{
vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INPUT_TYPE, default=CALL_TYPE_COIL): vol.In(
[
CALL_TYPE_COIL,
CALL_TYPE_DISCRETE,
CALL_TYPE_REGISTER_HOLDING,
CALL_TYPE_REGISTER_INPUT,
]
),
vol.Exclusive(CONF_VIRTUAL_COUNT, "vir_bin_count"): cv.positive_int,
vol.Exclusive(CONF_SLAVE_COUNT, "vir_bin_count"): cv.positive_int,
}
)
MODBUS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string,
vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout,
vol.Optional(CONF_DELAY, default=0): cv.positive_int,
vol.Optional(CONF_MSG_WAIT): cv.positive_int,
vol.Optional(CONF_BINARY_SENSORS): vol.All(
cv.ensure_list, [BINARY_SENSOR_SCHEMA]
),
vol.Optional(CONF_CLIMATES): vol.All(
cv.ensure_list, [vol.All(CLIMATE_SCHEMA, struct_validator)]
),
vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]),
vol.Optional(CONF_LIGHTS): vol.All(cv.ensure_list, [LIGHT_SCHEMA]),
vol.Optional(CONF_SENSORS): vol.All(
cv.ensure_list, [vol.All(SENSOR_SCHEMA, struct_validator)]
),
vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCH_SCHEMA]),
vol.Optional(CONF_FANS): vol.All(cv.ensure_list, [FAN_SCHEMA]),
},
extra=vol.ALLOW_EXTRA,
)
SERIAL_SCHEMA = MODBUS_SCHEMA.extend(
{
vol.Required(CONF_TYPE): SERIAL,
vol.Required(CONF_BAUDRATE): cv.positive_int,
vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8),
vol.Required(CONF_METHOD): vol.Any("rtu", "ascii"),
vol.Required(CONF_PORT): cv.string,
vol.Required(CONF_PARITY): vol.Any("E", "O", "N"),
vol.Required(CONF_STOPBITS): vol.Any(1, 2),
}
)
ETHERNET_SCHEMA = MODBUS_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PORT): cv.port,
vol.Required(CONF_TYPE): vol.Any(TCP, UDP, RTUOVERTCP),
}
)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.ensure_list,
[
vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA),
],
),
},
extra=vol.ALLOW_EXTRA,
)
# Per-platform schema for a single entity config, used to validate the entity
# lists stored in a device subentry.
PLATFORM_SCHEMAS: dict[Platform, VolSchemaType] = {
Platform.BINARY_SENSOR: BINARY_SENSOR_SCHEMA,
Platform.CLIMATE: vol.All(CLIMATE_SCHEMA, struct_validator),
Platform.COVER: COVERS_SCHEMA,
Platform.FAN: FAN_SCHEMA,
Platform.LIGHT: LIGHT_SCHEMA,
Platform.SENSOR: vol.All(SENSOR_SCHEMA, struct_validator),
Platform.SWITCH: SWITCH_SCHEMA,
}

Some files were not shown because too many files have changed in this diff Show More