mirror of
https://github.com/home-assistant/core.git
synced 2026-06-26 16:45:29 +02:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e2343243d | |||
| de8ed15ea8 | |||
| 9a21d92908 | |||
| 12ea0c4d77 | |||
| 94c4483735 | |||
| 053efdf662 | |||
| 3c9f55f7b2 | |||
| a46b434930 | |||
| 031e764957 | |||
| d9f0faf365 | |||
| a14e5d8a0c | |||
| f356a1cd0d | |||
| 50c12d85f8 | |||
| a88b43d845 | |||
| b9e59522e3 | |||
| 1484384d63 | |||
| 1d1ab798df | |||
| 926d2f1e21 | |||
| b341228b4b | |||
| 241f850e90 | |||
| 257040ac51 | |||
| e3c17026d0 | |||
| 9e4550dd14 | |||
| 37f441d3da | |||
| 0099100d14 | |||
| e20f74dac5 |
+4
-4
@@ -31,7 +31,7 @@
|
||||
# - GITHUB_TOKEN
|
||||
#
|
||||
# Custom actions used:
|
||||
# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
# - 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
# --- Threat Detection ---
|
||||
|
||||
@@ -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=10)
|
||||
WEATHER_UPDATE_INTERVAL = timedelta(minutes=20)
|
||||
|
||||
type AemetConfigEntry = ConfigEntry[AemetData]
|
||||
|
||||
|
||||
@@ -12,11 +12,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_TIME,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
)
|
||||
from homeassistant.const import ATTR_TIME, UnitOfDensity, UnitOfRatio
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -95,7 +91,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_PM10,
|
||||
translation_key="pm10",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
value_fn=lambda data: data.get(ATTR_API_PM10),
|
||||
@@ -104,7 +100,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_PM25,
|
||||
translation_key="pm25",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
value_fn=lambda data: data.get(ATTR_API_PM25),
|
||||
@@ -113,7 +109,7 @@ SENSOR_TYPES: tuple[AirNowEntityDescription, ...] = (
|
||||
AirNowEntityDescription(
|
||||
key=ATTR_API_O3,
|
||||
translation_key="o3",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=lambda data: data.get(ATTR_API_O3),
|
||||
extra_state_attributes_fn=None,
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import override
|
||||
from aioairq.core import AirQ
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.const import UnitOfRatio
|
||||
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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
value=lambda data: data["brightness"],
|
||||
set_value=lambda device, value: device.set_current_brightness(value),
|
||||
)
|
||||
|
||||
@@ -12,13 +12,9 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfDensity,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSoundPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -44,70 +40,70 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="c2h4o",
|
||||
translation_key="acetaldehyde",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ash3"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="br2",
|
||||
translation_key="bromine",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("br2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="ch4s",
|
||||
translation_key="methanethiol",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MILLIGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("co2"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="cs2",
|
||||
translation_key="carbon_disulfide",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("cs2"),
|
||||
),
|
||||
@@ -122,182 +118,182 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="ethanol",
|
||||
translation_key="ethanol",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ethanol"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="c2h4",
|
||||
translation_key="ethylene",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("humidity"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="humidity_abs",
|
||||
device_class=SensorDeviceClass.ABSOLUTE_HUMIDITY,
|
||||
native_unit_of_measurement=CONCENTRATION_GRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("ch4_MIPEX"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="mold",
|
||||
translation_key="mold",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("mold"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="n2o",
|
||||
device_class=SensorDeviceClass.NITROUS_OXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("acid_M100"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="oxygen",
|
||||
translation_key="oxygen",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("oxygen"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="o3",
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("pm10"),
|
||||
),
|
||||
@@ -319,42 +315,42 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="c3h8_MIPEX",
|
||||
translation_key="propane",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("c3h8_MIPEX"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="r32",
|
||||
translation_key="r32",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("r32"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="r454b",
|
||||
translation_key="r454b",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("r454b"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="r454c",
|
||||
translation_key="r454c",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("r454c"),
|
||||
),
|
||||
AirQEntityDescription(
|
||||
key="sih4",
|
||||
translation_key="silicon_hydride",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("so2"),
|
||||
),
|
||||
@@ -391,7 +387,7 @@ SENSOR_TYPES: list[AirQEntityDescription] = [
|
||||
AirQEntityDescription(
|
||||
key="tvoc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("tvoc"),
|
||||
),
|
||||
@@ -399,14 +395,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=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda data: data.get("virus", 0.0),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Support for airthings ble sensors."""
|
||||
|
||||
from collections.abc import Callable
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import override
|
||||
|
||||
@@ -13,13 +15,11 @@ 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,87 +46,108 @@ CONNECTIVITY_MODE_MAP = {
|
||||
AirthingsConnectivityMode.NOT_CONFIGURED.value: "not_configured",
|
||||
}
|
||||
|
||||
SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
"radon_1day_avg": SensorEntityDescription(
|
||||
|
||||
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(
|
||||
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": SensorEntityDescription(
|
||||
"radon_longterm_avg": AirthingsBLESensorEntityDescription(
|
||||
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": SensorEntityDescription(
|
||||
"radon_1day_level": AirthingsBLESensorEntityDescription(
|
||||
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": SensorEntityDescription(
|
||||
"radon_longterm_level": AirthingsBLESensorEntityDescription(
|
||||
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": SensorEntityDescription(
|
||||
"temperature": AirthingsBLESensorEntityDescription(
|
||||
key="temperature",
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
"humidity": SensorEntityDescription(
|
||||
"humidity": AirthingsBLESensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
"pressure": SensorEntityDescription(
|
||||
"pressure": AirthingsBLESensorEntityDescription(
|
||||
key="pressure",
|
||||
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
|
||||
native_unit_of_measurement=UnitOfPressure.MBAR,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=1,
|
||||
),
|
||||
"battery": SensorEntityDescription(
|
||||
"battery": AirthingsBLESensorEntityDescription(
|
||||
key="battery",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"co2": SensorEntityDescription(
|
||||
"co2": AirthingsBLESensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"voc": SensorEntityDescription(
|
||||
"voc": AirthingsBLESensorEntityDescription(
|
||||
key="voc",
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"illuminance": SensorEntityDescription(
|
||||
"illuminance": AirthingsBLESensorEntityDescription(
|
||||
key="illuminance",
|
||||
translation_key="illuminance",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"lux": SensorEntityDescription(
|
||||
"lux": AirthingsBLESensorEntityDescription(
|
||||
key="lux",
|
||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||
native_unit_of_measurement=LIGHT_LUX,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"noise": SensorEntityDescription(
|
||||
"noise": AirthingsBLESensorEntityDescription(
|
||||
key="noise",
|
||||
translation_key="ambient_noise",
|
||||
device_class=SensorDeviceClass.SOUND_PRESSURE,
|
||||
@@ -134,13 +155,14 @@ SENSORS_MAPPING_TEMPLATE: dict[str, SensorEntityDescription] = {
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
suggested_display_precision=0,
|
||||
),
|
||||
"connectivity_mode": SensorEntityDescription(
|
||||
"connectivity_mode": AirthingsBLESensorEntityDescription(
|
||||
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,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -228,12 +250,13 @@ 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: SensorEntityDescription,
|
||||
entity_description: AirthingsBLESensorEntityDescription,
|
||||
) -> None:
|
||||
"""Populate the airthings entity with relevant data."""
|
||||
super().__init__(coordinator)
|
||||
@@ -272,11 +295,4 @@ class AirthingsSensor(
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the value reported by the sensor."""
|
||||
value = self.coordinator.data.sensors[self.entity_description.key]
|
||||
|
||||
# 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
|
||||
return self.entity_description.value_fn(value)
|
||||
|
||||
@@ -45,13 +45,23 @@
|
||||
"name": "Radon 1-day average"
|
||||
},
|
||||
"radon_1day_level": {
|
||||
"name": "Radon 1-day level"
|
||||
"name": "Radon 1-day level",
|
||||
"state": {
|
||||
"fair": "Fair",
|
||||
"good": "Good",
|
||||
"poor": "Poor"
|
||||
}
|
||||
},
|
||||
"radon_longterm_avg": {
|
||||
"name": "Radon longterm average"
|
||||
},
|
||||
"radon_longterm_level": {
|
||||
"name": "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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -11,15 +11,14 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_NAME,
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfDensity,
|
||||
UnitOfIrradiance,
|
||||
UnitOfLength,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolumetricFlux,
|
||||
@@ -157,13 +156,13 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN_AQIN,
|
||||
translation_key="pm25_indoor_aqin",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN_24H_AQIN,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
translation_key="pm25_indoor_24h_aqin",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -171,28 +170,28 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM10_IN_AQIN,
|
||||
translation_key="pm10_indoor_aqin",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.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=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -206,7 +205,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM_IN_HUMIDITY_AQIN,
|
||||
translation_key="pm_indoor_humidity_aqin",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -250,7 +249,7 @@ SENSOR_DESCRIPTIONS = (
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -291,83 +290,83 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY10,
|
||||
translation_key="humidity_10",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY1,
|
||||
translation_key="humidity_1",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY2,
|
||||
translation_key="humidity_2",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY3,
|
||||
translation_key="humidity_3",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY4,
|
||||
translation_key="humidity_4",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY5,
|
||||
translation_key="humidity_5",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY6,
|
||||
translation_key="humidity_6",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY7,
|
||||
translation_key="humidity_7",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY8,
|
||||
translation_key="humidity_8",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY9,
|
||||
translation_key="humidity_9",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_HUMIDITYIN,
|
||||
translation_key="humidity_indoor",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -417,95 +416,95 @@ SENSOR_DESCRIPTIONS = (
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_24H,
|
||||
translation_key="pm25_24h_average",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25_IN,
|
||||
translation_key="pm25_indoor",
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM1,
|
||||
translation_key="soil_humidity_1",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM2,
|
||||
translation_key="soil_humidity_2",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM3,
|
||||
translation_key="soil_humidity_3",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM4,
|
||||
translation_key="soil_humidity_4",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM5,
|
||||
translation_key="soil_humidity_5",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM6,
|
||||
translation_key="soil_humidity_6",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM7,
|
||||
translation_key="soil_humidity_7",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM8,
|
||||
translation_key="soil_humidity_8",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key=TYPE_SOILHUM9,
|
||||
translation_key="soil_humidity_9",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
|
||||
@@ -14,18 +14,17 @@ 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,
|
||||
@@ -99,7 +98,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.HUMIDITY: SensorEntityDescription(
|
||||
key="HUMIDITY",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.DEGREE: SensorEntityDescription(
|
||||
@@ -122,19 +121,19 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.PM25: SensorEntityDescription(
|
||||
key="PM25",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.PM10: SensorEntityDescription(
|
||||
key="PM10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.BATTERY_PERCENTAGE: SensorEntityDescription(
|
||||
key="BATTERY_PERCENTAGE",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
@@ -149,7 +148,7 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.CO2_PPM: SensorEntityDescription(
|
||||
key="CO2_PPM",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.LUX: SensorEntityDescription(
|
||||
@@ -263,13 +262,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
),
|
||||
EcoWittSensorTypes.PERCENTAGE: SensorEntityDescription(
|
||||
key="PERCENTAGE",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.SOIL_MOISTURE: SensorEntityDescription(
|
||||
key="SOIL_MOISTURE",
|
||||
device_class=SensorDeviceClass.MOISTURE,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.DISTANCE_MM: SensorEntityDescription(
|
||||
@@ -286,13 +285,13 @@ ECOWITT_SENSORS_MAPPING: Final = {
|
||||
EcoWittSensorTypes.PM1: SensorEntityDescription(
|
||||
key="PM1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
EcoWittSensorTypes.PM4: SensorEntityDescription(
|
||||
key="PM4",
|
||||
device_class=SensorDeviceClass.PM4,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -65,6 +65,14 @@ 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:
|
||||
@@ -134,16 +142,15 @@ 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,
|
||||
|
||||
@@ -12,7 +12,6 @@ from aioesphomeapi import (
|
||||
EntityCategory as EsphomeEntityCategory,
|
||||
EntityInfo,
|
||||
EntityState,
|
||||
build_device_unique_id,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -32,7 +31,12 @@ 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
|
||||
from .entry_data import (
|
||||
DeviceEntityKey,
|
||||
ESPHomeConfigEntry,
|
||||
RuntimeEntryData,
|
||||
build_device_unique_id,
|
||||
)
|
||||
from .enum_mapper import EsphomeEnumMapper
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,7 +45,7 @@ from aioesphomeapi import (
|
||||
UserService,
|
||||
ValveInfo,
|
||||
WaterHeaterInfo,
|
||||
build_device_unique_id,
|
||||
build_unique_id,
|
||||
)
|
||||
from aioesphomeapi.model import ButtonInfo
|
||||
from bleak_esphome.backend.device import ESPHomeBluetoothDevice
|
||||
@@ -103,6 +103,22 @@ 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."""
|
||||
|
||||
@@ -294,31 +310,11 @@ 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",
|
||||
|
||||
@@ -1417,14 +1417,13 @@ 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):
|
||||
# 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
|
||||
)
|
||||
# <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)
|
||||
|
||||
domain_data = DomainData.get(hass)
|
||||
store = domain_data.get_or_create_store(hass, entry)
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
|
||||
from homeassistant.const import UnitOfDensity
|
||||
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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.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=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
GiosSensorEntityDescription(
|
||||
|
||||
@@ -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:
|
||||
return subscription.event_callback(match)
|
||||
subscription.event_callback(match)
|
||||
except Exception as err: # noqa: BLE001
|
||||
_LOGGER.error(
|
||||
"Error encountered processing Supervisor Job (%s %s %s) - %s",
|
||||
|
||||
@@ -161,5 +161,7 @@ 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,6 +27,7 @@ from homeassistant.const import (
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -45,6 +46,7 @@ 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 = (
|
||||
@@ -242,6 +244,26 @@ 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",
|
||||
@@ -948,6 +970,14 @@ 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,6 +365,12 @@
|
||||
"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"
|
||||
},
|
||||
|
||||
@@ -16,13 +16,12 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfDensity,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
@@ -97,7 +96,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_BME280_HUMIDITY,
|
||||
translation_key="bme280_humidity",
|
||||
suggested_display_precision=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.bme280_humidity,
|
||||
@@ -169,7 +168,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_HECA_HUMIDITY,
|
||||
translation_key="heca_humidity",
|
||||
suggested_display_precision=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.heca_humidity,
|
||||
@@ -187,7 +186,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_MHZ14A_CARBON_DIOXIDE,
|
||||
translation_key="mhz14a_carbon_dioxide",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.mhz14a_carbon_dioxide,
|
||||
@@ -208,7 +207,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_PMSX003_P0,
|
||||
translation_key="pmsx003_pm1",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.pms_p0,
|
||||
@@ -217,7 +216,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_PMSX003_P1,
|
||||
translation_key="pmsx003_pm10",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.pms_p1,
|
||||
@@ -226,7 +225,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_PMSX003_P2,
|
||||
translation_key="pmsx003_pm25",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.pms_p2,
|
||||
@@ -247,7 +246,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SDS011_P1,
|
||||
translation_key="sds011_pm10",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sds011_p1,
|
||||
@@ -256,7 +255,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SDS011_P2,
|
||||
translation_key="sds011_pm25",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sds011_p2,
|
||||
@@ -265,7 +264,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SHT3X_HUMIDITY,
|
||||
translation_key="sht3x_humidity",
|
||||
suggested_display_precision=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sht3x_humidity,
|
||||
@@ -295,7 +294,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SPS30_P0,
|
||||
translation_key="sps30_pm1",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sps30_p0,
|
||||
@@ -304,7 +303,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SPS30_P1,
|
||||
translation_key="sps30_pm10",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sps30_p1,
|
||||
@@ -313,7 +312,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SPS30_P2,
|
||||
translation_key="sps30_pm25",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sps30_p2,
|
||||
@@ -322,7 +321,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_SPS30_P4,
|
||||
translation_key="sps30_pm4",
|
||||
suggested_display_precision=0,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM4,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.sps30_p4,
|
||||
@@ -331,7 +330,7 @@ SENSORS: tuple[NAMSensorEntityDescription, ...] = (
|
||||
key=ATTR_DHT22_HUMIDITY,
|
||||
translation_key="dht22_humidity",
|
||||
suggested_display_precision=1,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value=lambda sensors: sensors.dht22_humidity,
|
||||
|
||||
@@ -12,13 +12,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
StateType,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_BILLION,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
UnitOfPressure,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.const import UnitOfPressure, UnitOfRatio, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
@@ -54,13 +48,13 @@ ENTITIES: tuple[PranaSensorEntityDescription, ...] = (
|
||||
PranaSensorEntityDescription(
|
||||
key=PranaSensorType.HUMIDITY,
|
||||
value_fn=lambda coord: coord.data.humidity,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
),
|
||||
PranaSensorEntityDescription(
|
||||
key=PranaSensorType.VOC,
|
||||
value_fn=lambda coord: coord.data.voc,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_BILLION,
|
||||
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
),
|
||||
PranaSensorEntityDescription(
|
||||
@@ -72,7 +66,7 @@ ENTITIES: tuple[PranaSensorEntityDescription, ...] = (
|
||||
PranaSensorEntityDescription(
|
||||
key=PranaSensorType.CO2,
|
||||
value_fn=lambda coord: coord.data.co2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
),
|
||||
PranaSensorEntityDescription(
|
||||
|
||||
@@ -286,7 +286,10 @@ async def register_callbacks(
|
||||
return async_camera_wake
|
||||
|
||||
host.api.baichuan.register_callback(
|
||||
"privacy_mode_change", async_privacy_mode_change, 623
|
||||
"privacy_mode_change_623", async_privacy_mode_change, 623
|
||||
)
|
||||
host.api.baichuan.register_callback(
|
||||
"privacy_mode_change_574", async_privacy_mode_change, 574
|
||||
)
|
||||
for channel in host.api.channels:
|
||||
if host.api.supported(channel, "battery"):
|
||||
@@ -306,7 +309,8 @@ async def async_unload_entry(
|
||||
|
||||
await host.stop()
|
||||
|
||||
host.api.baichuan.unregister_callback("privacy_mode_change")
|
||||
host.api.baichuan.unregister_callback("privacy_mode_change_623")
|
||||
host.api.baichuan.unregister_callback("privacy_mode_change_574")
|
||||
for channel in host.api.channels:
|
||||
if host.api.supported(channel, "battery"):
|
||||
host.api.baichuan.unregister_callback(f"camera_{channel}_wake")
|
||||
|
||||
@@ -75,6 +75,7 @@ LIGHT_ENTITIES = (
|
||||
ReolinkLightEntityDescription(
|
||||
key="status_led",
|
||||
cmd_key="GetPowerLed",
|
||||
cmd_id=208,
|
||||
translation_key="status_led",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "power_led"),
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["reolink_aio"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["reolink-aio==0.21.2"]
|
||||
"requirements": ["reolink-aio==0.21.3"]
|
||||
}
|
||||
|
||||
@@ -195,6 +195,7 @@ NUMBER_ENTITIES = (
|
||||
key="volume",
|
||||
cmd_key="GetAudioCfg",
|
||||
translation_key="volume",
|
||||
cmd_id=264,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
native_min_value=0,
|
||||
@@ -206,6 +207,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="volume_speak",
|
||||
cmd_key="GetAudioCfg",
|
||||
cmd_id=264,
|
||||
translation_key="volume_speak",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -218,6 +220,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="volume_doorbell",
|
||||
cmd_key="GetAudioCfg",
|
||||
cmd_id=264,
|
||||
translation_key="volume_doorbell",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -269,6 +272,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="pir_sensitivity",
|
||||
cmd_key="GetPirInfo",
|
||||
cmd_id=212,
|
||||
translation_key="pir_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -281,6 +285,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="pir_interval",
|
||||
cmd_key="GetPirInfo",
|
||||
cmd_id=212,
|
||||
translation_key="pir_interval",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -296,6 +301,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_face_sensititvity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_face_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -310,6 +316,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_person_sensititvity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_person_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -324,6 +331,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_vehicle_sensititvity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_vehicle_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -338,6 +346,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_non_motor_vehicle_sensitivity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_non_motor_vehicle_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -355,6 +364,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_package_sensititvity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_package_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -369,6 +379,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_pet_sensititvity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_pet_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -385,6 +396,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_pet_sensititvity",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_animal_sensitivity",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_step=1,
|
||||
@@ -411,6 +423,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_face_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_face_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -428,6 +441,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_person_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_person_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -445,6 +459,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_non_motor_vehicle_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_non_motor_vehicle_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -464,6 +479,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_vehicle_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_vehicle_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -481,6 +497,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_package_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_package_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -498,6 +515,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_pet_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_pet_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
@@ -517,6 +535,7 @@ NUMBER_ENTITIES = (
|
||||
ReolinkNumberEntityDescription(
|
||||
key="ai_pet_delay",
|
||||
cmd_key="GetAiAlarm",
|
||||
cmd_id=342,
|
||||
translation_key="ai_animal_delay",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
device_class=NumberDeviceClass.DURATION,
|
||||
|
||||
@@ -185,6 +185,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="status_led",
|
||||
cmd_key="GetPowerLed",
|
||||
cmd_id=208,
|
||||
translation_key="doorbell_led",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
get_options=lambda api, ch: api.doorbell_led_list(ch),
|
||||
@@ -232,6 +233,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="main_frame_rate",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="main_frame_rate",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -244,6 +246,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="sub_frame_rate",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="sub_frame_rate",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -256,6 +259,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="main_bit_rate",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="main_bit_rate",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -268,6 +272,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="sub_bit_rate",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="sub_bit_rate",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -280,6 +285,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="main_encoding",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="main_encoding",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -291,6 +297,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="sub_encoding",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="sub_encoding",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -316,6 +323,7 @@ SELECT_ENTITIES = (
|
||||
ReolinkSelectEntityDescription(
|
||||
key="post_rec_time",
|
||||
cmd_key="GetRec",
|
||||
cmd_id=54,
|
||||
translation_key="post_rec_time",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -340,6 +348,7 @@ HOST_SELECT_ENTITIES = (
|
||||
ReolinkHostSelectEntityDescription(
|
||||
key="packing_time",
|
||||
cmd_key="GetRec",
|
||||
cmd_id=54,
|
||||
translation_key="packing_time",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
|
||||
@@ -74,6 +74,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="ir_lights",
|
||||
cmd_key="GetIrLights",
|
||||
cmd_id=208,
|
||||
translation_key="ir_lights",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "ir_lights"),
|
||||
@@ -83,6 +84,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="record_audio",
|
||||
cmd_key="GetEnc",
|
||||
cmd_id=56,
|
||||
translation_key="record_audio",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "audio"),
|
||||
@@ -92,6 +94,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="siren_on_event",
|
||||
cmd_key="GetAudioAlarm",
|
||||
cmd_id=232,
|
||||
translation_key="siren_on_event",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "siren"),
|
||||
@@ -136,6 +139,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="email",
|
||||
cmd_key="GetEmail",
|
||||
cmd_id=217,
|
||||
translation_key="email",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "email") and api.is_nvr,
|
||||
@@ -145,6 +149,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="ftp_upload",
|
||||
cmd_key="GetFtp",
|
||||
cmd_id=70,
|
||||
translation_key="ftp_upload",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "ftp") and api.is_nvr,
|
||||
@@ -163,6 +168,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="record",
|
||||
cmd_key="GetRec",
|
||||
cmd_id=81,
|
||||
translation_key="record",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "rec_enable") and api.is_nvr,
|
||||
@@ -200,6 +206,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="doorbell_button_sound",
|
||||
cmd_key="GetAudioCfg",
|
||||
cmd_id=264,
|
||||
translation_key="doorbell_button_sound",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api, ch: api.supported(ch, "doorbell_button_sound"),
|
||||
@@ -209,6 +216,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="pir_enabled",
|
||||
cmd_key="GetPirInfo",
|
||||
cmd_id=212,
|
||||
translation_key="pir_enabled",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -219,6 +227,7 @@ SWITCH_ENTITIES = (
|
||||
ReolinkSwitchEntityDescription(
|
||||
key="pir_reduce_alarm",
|
||||
cmd_key="GetPirInfo",
|
||||
cmd_id=212,
|
||||
translation_key="pir_reduce_alarm",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
@@ -260,6 +269,7 @@ HOST_SWITCH_ENTITIES = (
|
||||
ReolinkHostSwitchEntityDescription(
|
||||
key="email",
|
||||
cmd_key="GetEmail",
|
||||
cmd_id=217,
|
||||
translation_key="email",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api: api.supported(None, "email") and not api.is_hub,
|
||||
@@ -269,6 +279,7 @@ HOST_SWITCH_ENTITIES = (
|
||||
ReolinkHostSwitchEntityDescription(
|
||||
key="ftp_upload",
|
||||
cmd_key="GetFtp",
|
||||
cmd_id=70,
|
||||
translation_key="ftp_upload",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api: api.supported(None, "ftp") and not api.is_hub,
|
||||
@@ -287,6 +298,7 @@ HOST_SWITCH_ENTITIES = (
|
||||
ReolinkHostSwitchEntityDescription(
|
||||
key="record",
|
||||
cmd_key="GetRec",
|
||||
cmd_id=81,
|
||||
translation_key="record",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
supported=lambda api: api.supported(None, "rec_enable") and not api.is_hub,
|
||||
|
||||
@@ -15,14 +15,12 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_DEVICES,
|
||||
CONF_NAME,
|
||||
CONF_SENSOR_TYPE,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UV_INDEX,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
@@ -30,6 +28,7 @@ from homeassistant.const import (
|
||||
UnitOfPower,
|
||||
UnitOfPrecipitationDepth,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
UnitOfVolumetricFlux,
|
||||
@@ -85,7 +84,7 @@ SENSOR_TYPES = (
|
||||
name="CO2 air quality",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="command",
|
||||
@@ -140,7 +139,7 @@ SENSOR_TYPES = (
|
||||
name="Humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="humidity_status",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"loggers": ["roborock"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": [
|
||||
"python-roborock==5.14.2",
|
||||
"python-roborock==5.21.0",
|
||||
"vacuum-map-parser-roborock==0.1.5"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class RoborockNumberDescription(NumberEntityDescription):
|
||||
trait: Callable[[PropertiesApi], Any | None]
|
||||
"""Function to determine if number entity is supported by the device."""
|
||||
|
||||
get_value: Callable[[Any], float]
|
||||
get_value: Callable[[Any], float | None]
|
||||
"""Function to get the value from the trait."""
|
||||
|
||||
set_value: Callable[[Any, float], Coroutine[Any, Any, None]]
|
||||
@@ -51,7 +51,9 @@ NUMBER_DESCRIPTIONS: list[RoborockNumberDescription] = [
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
trait=lambda api: api.sound_volume,
|
||||
get_value=lambda trait: float(trait.volume),
|
||||
get_value=lambda trait: (
|
||||
float(trait.volume) if trait.volume is not None else None
|
||||
),
|
||||
set_value=lambda trait, value: trait.set_volume(int(value)),
|
||||
)
|
||||
]
|
||||
|
||||
@@ -30,6 +30,13 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def _build_time(hour: int | None, minute: int | None) -> datetime.time | None:
|
||||
"""Build a time, returning None when either component is missing."""
|
||||
if hour is None or minute is None:
|
||||
return None
|
||||
return datetime.time(hour=hour, minute=minute)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RoborockTimeDescription(TimeEntityDescription):
|
||||
"""Class to describe a Roborock time entity."""
|
||||
@@ -37,7 +44,7 @@ class RoborockTimeDescription(TimeEntityDescription):
|
||||
trait: Callable[[Any], Any | None]
|
||||
"""Function to determine if time entity is supported by the device."""
|
||||
|
||||
get_value: Callable[[Any], datetime.time]
|
||||
get_value: Callable[[Any], datetime.time | None]
|
||||
"""Function to get the value from the trait."""
|
||||
|
||||
update_value: Callable[[Any, datetime.time], Coroutine[Any, Any, None]]
|
||||
@@ -58,9 +65,7 @@ TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
|
||||
end_minute=trait.end_minute,
|
||||
)
|
||||
),
|
||||
get_value=lambda trait: datetime.time(
|
||||
hour=trait.start_hour, minute=trait.start_minute
|
||||
),
|
||||
get_value=lambda trait: _build_time(trait.start_hour, trait.start_minute),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
RoborockTimeDescription(
|
||||
@@ -76,9 +81,7 @@ TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
|
||||
end_minute=desired_time.minute,
|
||||
)
|
||||
),
|
||||
get_value=lambda trait: datetime.time(
|
||||
hour=trait.end_hour, minute=trait.end_minute
|
||||
),
|
||||
get_value=lambda trait: _build_time(trait.end_hour, trait.end_minute),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
RoborockTimeDescription(
|
||||
@@ -94,9 +97,7 @@ TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
|
||||
end_minute=trait.end_minute,
|
||||
)
|
||||
),
|
||||
get_value=lambda trait: datetime.time(
|
||||
hour=trait.start_hour, minute=trait.start_minute
|
||||
),
|
||||
get_value=lambda trait: _build_time(trait.start_hour, trait.start_minute),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
@@ -113,9 +114,7 @@ TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
|
||||
end_minute=desired_time.minute,
|
||||
)
|
||||
),
|
||||
get_value=lambda trait: datetime.time(
|
||||
hour=trait.end_hour, minute=trait.end_minute
|
||||
),
|
||||
get_value=lambda trait: _build_time(trait.end_hour, trait.end_minute),
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
|
||||
@@ -17,7 +17,7 @@ from homeassistant.components.number import (
|
||||
NumberMode,
|
||||
RestoreNumber,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
|
||||
from homeassistant.const import EntityCategory, UnitOfRatio, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -183,7 +183,7 @@ BLOCK_NUMBERS: dict[tuple[str, str], BlockNumberDescription] = {
|
||||
("device", "valvePos"): BlockNumberDescription(
|
||||
key="device|valvepos",
|
||||
translation_key="valve_position",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
available=lambda block: cast(int, block.valveError) != 1,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_min_value=0,
|
||||
@@ -291,7 +291,7 @@ RPC_NUMBERS: Final = {
|
||||
native_max_value=100,
|
||||
native_step=1,
|
||||
mode=NumberMode.SLIDER,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
method="blu_trv_set_valve_position",
|
||||
removal_condition=lambda config, _, key: (
|
||||
config[key].get("enable", True) is True
|
||||
@@ -307,7 +307,7 @@ RPC_NUMBERS: Final = {
|
||||
native_max_value=100,
|
||||
native_step=1,
|
||||
mode=NumberMode.SLIDER,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
method="cury_set",
|
||||
slot="left",
|
||||
available=lambda status: (
|
||||
@@ -325,7 +325,7 @@ RPC_NUMBERS: Final = {
|
||||
native_max_value=100,
|
||||
native_step=1,
|
||||
mode=NumberMode.SLIDER,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
method="cury_set",
|
||||
slot="right",
|
||||
available=lambda status: (
|
||||
|
||||
@@ -17,10 +17,8 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
DEGREE,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfApparentPower,
|
||||
@@ -30,6 +28,7 @@ from homeassistant.const import (
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
UnitOfTime,
|
||||
UnitOfVolume,
|
||||
@@ -203,7 +202,7 @@ class RpcBluTrvSensor(RpcSensor):
|
||||
BLOCK_SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
|
||||
("device", "battery"): BlockSensorDescription(
|
||||
key="device|battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
removal_condition=lambda settings, _: settings.get("external_power") == 1,
|
||||
@@ -350,7 +349,7 @@ BLOCK_SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
|
||||
("sensor", "concentration"): BlockSensorDescription(
|
||||
key="sensor|concentration",
|
||||
translation_key="gas_concentration",
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
("sensor", "temp"): BlockSensorDescription(
|
||||
@@ -373,7 +372,7 @@ BLOCK_SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
|
||||
),
|
||||
("sensor", "humidity"): BlockSensorDescription(
|
||||
key="sensor|humidity",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
suggested_display_precision=1,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -398,7 +397,7 @@ BLOCK_SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
|
||||
("relay", "totalWorkTime"): BlockSensorDescription(
|
||||
key="relay|totalWorkTime",
|
||||
translation_key="lamp_life",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
value=get_shelly_air_lamp_life,
|
||||
suggested_display_precision=1,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -1255,7 +1254,7 @@ RPC_SENSORS: Final = {
|
||||
"humidity_rh": RpcSensorDescription(
|
||||
key="humidity",
|
||||
sub_key="rh",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
suggested_display_precision=1,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -1266,7 +1265,7 @@ RPC_SENSORS: Final = {
|
||||
"battery": RpcSensorDescription(
|
||||
key="devicepower",
|
||||
sub_key="battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
value=lambda status, _: status["percent"],
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -1296,7 +1295,7 @@ RPC_SENSORS: Final = {
|
||||
key="input",
|
||||
sub_key="percent",
|
||||
translation_key="analog",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
removal_condition=lambda config, _, key: (
|
||||
config[key]["type"] != "analog" or config[key]["enable"] is False
|
||||
@@ -1389,7 +1388,7 @@ RPC_SENSORS: Final = {
|
||||
key="blutrv",
|
||||
sub_key="pos",
|
||||
translation_key="valve_position",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
removal_condition=lambda config, _, key: (
|
||||
@@ -1400,7 +1399,7 @@ RPC_SENSORS: Final = {
|
||||
"blutrv_battery": RpcSensorDescription(
|
||||
key="blutrv",
|
||||
sub_key="battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -1448,7 +1447,7 @@ RPC_SENSORS: Final = {
|
||||
"number_current_humidity": RpcSensorDescription(
|
||||
key="number",
|
||||
sub_key="value",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
suggested_display_precision=1,
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -1686,7 +1685,7 @@ RPC_SENSORS: Final = {
|
||||
translation_key="left_slot_level",
|
||||
value=lambda status, _: status["left"]["vial"]["level"],
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
available=lambda status: (
|
||||
(left := status["left"]) is not None
|
||||
@@ -1710,7 +1709,7 @@ RPC_SENSORS: Final = {
|
||||
translation_key="right_slot_level",
|
||||
value=lambda status, _: status["right"]["vial"]["level"],
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
available=lambda status: (
|
||||
(right := status["right"]) is not None
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""The thermoworks_smoke component."""
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"domain": "thermoworks_smoke",
|
||||
"name": "ThermoWorks Smoke",
|
||||
"codeowners": [],
|
||||
"disabled": "This integration is disabled because it creates an unresolvable dependency conflict.",
|
||||
"documentation": "https://www.home-assistant.io/integrations/thermoworks_smoke",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["thermoworks_smoke"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["stringcase==1.2.0", "thermoworks-smoke==0.1.8"]
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
"""Support for getting the state of a Thermoworks Smoke Thermometer.
|
||||
|
||||
Requires Smoke Gateway Wifi with an internet connection.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from requests import RequestException
|
||||
from requests.exceptions import HTTPError
|
||||
from stringcase import camelcase
|
||||
import thermoworks_smoke
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL,
|
||||
CONF_EMAIL,
|
||||
CONF_EXCLUDE,
|
||||
CONF_MONITORED_CONDITIONS,
|
||||
CONF_PASSWORD,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
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 snakecase
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PROBE_1 = "probe1"
|
||||
PROBE_2 = "probe2"
|
||||
PROBE_1_MIN = "probe1_min"
|
||||
PROBE_1_MAX = "probe1_max"
|
||||
PROBE_2_MIN = "probe2_min"
|
||||
PROBE_2_MAX = "probe2_max"
|
||||
BATTERY_LEVEL = "battery"
|
||||
FIRMWARE = "firmware"
|
||||
|
||||
SERIAL_REGEX = "^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$"
|
||||
|
||||
# map types to labels
|
||||
SENSOR_TYPES = {
|
||||
PROBE_1: "Probe 1",
|
||||
PROBE_2: "Probe 2",
|
||||
PROBE_1_MIN: "Probe 1 Min",
|
||||
PROBE_1_MAX: "Probe 1 Max",
|
||||
PROBE_2_MIN: "Probe 2 Min",
|
||||
PROBE_2_MAX: "Probe 2 Max",
|
||||
}
|
||||
|
||||
# exclude these keys from thermoworks data
|
||||
EXCLUDE_KEYS = [FIRMWARE]
|
||||
|
||||
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_EMAIL): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=[PROBE_1, PROBE_2]): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]
|
||||
),
|
||||
vol.Optional(CONF_EXCLUDE, default=[]): vol.All(
|
||||
cv.ensure_list, [cv.matches_regex(SERIAL_REGEX)]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the thermoworks sensor."""
|
||||
|
||||
email = config[CONF_EMAIL]
|
||||
password = config[CONF_PASSWORD]
|
||||
monitored_variables = config[CONF_MONITORED_CONDITIONS]
|
||||
excluded = config[CONF_EXCLUDE]
|
||||
|
||||
try:
|
||||
mgr = thermoworks_smoke.initialize_app(email, password, True, excluded)
|
||||
except HTTPError as error:
|
||||
msg = f"{error.strerror}"
|
||||
if "EMAIL_NOT_FOUND" in msg or "INVALID_PASSWORD" in msg:
|
||||
_LOGGER.error("Invalid email and password combination")
|
||||
else:
|
||||
_LOGGER.error(msg)
|
||||
else:
|
||||
add_entities(
|
||||
(
|
||||
ThermoworksSmokeSensor(variable, serial, mgr)
|
||||
for serial in mgr.serials()
|
||||
for variable in monitored_variables
|
||||
),
|
||||
True,
|
||||
)
|
||||
|
||||
|
||||
class ThermoworksSmokeSensor(SensorEntity):
|
||||
"""Implementation of a thermoworks smoke sensor."""
|
||||
|
||||
def __init__(self, sensor_type, serial, mgr):
|
||||
"""Initialize the sensor."""
|
||||
self.type = sensor_type
|
||||
self.serial = serial
|
||||
self.mgr = mgr
|
||||
self._attr_name = f"{mgr.name(serial)} {SENSOR_TYPES[sensor_type]}"
|
||||
self._attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT
|
||||
self._attr_unique_id = f"{serial}-{sensor_type}"
|
||||
self._attr_device_class = SensorDeviceClass.TEMPERATURE
|
||||
self.update_unit()
|
||||
|
||||
def update_unit(self):
|
||||
"""Set the units from the data."""
|
||||
if PROBE_2 in self.type:
|
||||
self._attr_native_unit_of_measurement = self.mgr.units(self.serial, PROBE_2)
|
||||
else:
|
||||
self._attr_native_unit_of_measurement = self.mgr.units(self.serial, PROBE_1)
|
||||
|
||||
def update(self) -> None:
|
||||
"""Get the monitored data from firebase."""
|
||||
|
||||
try:
|
||||
values = self.mgr.data(self.serial)
|
||||
|
||||
# set state from data based on type of sensor
|
||||
self._attr_native_value = values.get(camelcase(self.type))
|
||||
|
||||
# set units
|
||||
self.update_unit()
|
||||
|
||||
# set basic attributes for all sensors
|
||||
self._attr_extra_state_attributes = {
|
||||
"time": values["time"],
|
||||
"localtime": values["localtime"],
|
||||
}
|
||||
|
||||
# set extended attributes for main probe sensors
|
||||
if self.type in (PROBE_1, PROBE_2):
|
||||
for key, val in values.items():
|
||||
# add all attributes that don't contain any probe name
|
||||
# or contain a matching probe name
|
||||
if (self.type == PROBE_1 and key.find(PROBE_2) == -1) or (
|
||||
self.type == PROBE_2 and key.find(PROBE_1) == -1
|
||||
):
|
||||
if key == BATTERY_LEVEL:
|
||||
key = ATTR_BATTERY_LEVEL
|
||||
else:
|
||||
# strip probe label and convert to snake_case
|
||||
key = snakecase(key.replace(self.type, ""))
|
||||
# add to attrs
|
||||
if key and key not in EXCLUDE_KEYS:
|
||||
self._attr_extra_state_attributes[key] = val
|
||||
# store actual unit because attributes are not converted
|
||||
self._attr_extra_state_attributes["unit_of_min_max"] = (
|
||||
self._attr_native_unit_of_measurement
|
||||
)
|
||||
|
||||
except RequestException, ValueError, KeyError:
|
||||
_LOGGER.warning("Could not update status for %s", self.name)
|
||||
@@ -21,13 +21,12 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
CONF_API_KEY,
|
||||
PERCENTAGE,
|
||||
UnitOfDensity,
|
||||
UnitOfIrradiance,
|
||||
UnitOfLength,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfSpeed,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
@@ -174,7 +173,7 @@ SENSOR_TYPES = (
|
||||
key="cloud_cover",
|
||||
translation_key="cloud_cover",
|
||||
attribute=TMRW_ATTR_CLOUD_COVER,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
),
|
||||
# Data comes in as m/s, convert to mi/h for imperial
|
||||
TomorrowioSensorEntityDescription(
|
||||
@@ -200,7 +199,7 @@ SENSOR_TYPES = (
|
||||
TomorrowioSensorEntityDescription(
|
||||
key="ozone",
|
||||
attribute=TMRW_ATTR_OZONE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
multiplication_factor=convert_ppb_to_ugm3(48),
|
||||
device_class=SensorDeviceClass.OZONE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -208,14 +207,14 @@ SENSOR_TYPES = (
|
||||
TomorrowioSensorEntityDescription(
|
||||
key="particulate_matter_2_5_mm",
|
||||
attribute=TMRW_ATTR_PARTICULATE_MATTER_25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
TomorrowioSensorEntityDescription(
|
||||
key="particulate_matter_10_mm",
|
||||
attribute=TMRW_ATTR_PARTICULATE_MATTER_10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
@@ -224,7 +223,7 @@ SENSOR_TYPES = (
|
||||
TomorrowioSensorEntityDescription(
|
||||
key="nitrogen_dioxide",
|
||||
attribute=TMRW_ATTR_NITROGEN_DIOXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
multiplication_factor=convert_ppb_to_ugm3(46.01),
|
||||
device_class=SensorDeviceClass.NITROGEN_DIOXIDE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -233,7 +232,7 @@ SENSOR_TYPES = (
|
||||
TomorrowioSensorEntityDescription(
|
||||
key="carbon_monoxide",
|
||||
attribute=TMRW_ATTR_CARBON_MONOXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
multiplication_factor=1 / 1000,
|
||||
device_class=SensorDeviceClass.CO,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
@@ -243,7 +242,7 @@ SENSOR_TYPES = (
|
||||
TomorrowioSensorEntityDescription(
|
||||
key="sulphur_dioxide",
|
||||
attribute=TMRW_ATTR_SULPHUR_DIOXIDE,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
multiplication_factor=convert_ppb_to_ugm3(64.07),
|
||||
device_class=SensorDeviceClass.SULPHUR_DIOXIDE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
|
||||
@@ -145,9 +145,7 @@ class VodafoneStationRouter(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except (
|
||||
exceptions.CannotConnect,
|
||||
exceptions.AlreadyLogged,
|
||||
exceptions.GenericLoginError,
|
||||
exceptions.VodafoneError,
|
||||
JSONDecodeError,
|
||||
) as err:
|
||||
if isinstance(err, JSONDecodeError):
|
||||
|
||||
@@ -7201,12 +7201,6 @@
|
||||
"config_flow": true,
|
||||
"iot_class": "local_push"
|
||||
},
|
||||
"thermoworks_smoke": {
|
||||
"name": "ThermoWorks Smoke",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"thethingsnetwork": {
|
||||
"name": "The Things Network",
|
||||
"integration_type": "hub",
|
||||
|
||||
@@ -71,7 +71,7 @@ typing-extensions>=4.15.0,<5.0
|
||||
ulid-transform==2.2.9
|
||||
urllib3>=2.0
|
||||
uv==0.11.21
|
||||
voluptuous-openapi==0.3.0
|
||||
voluptuous-openapi==0.4.1
|
||||
voluptuous-serialize==2.7.0
|
||||
voluptuous==0.15.2
|
||||
webrtc-models==0.3.0
|
||||
|
||||
+1
-1
@@ -77,7 +77,7 @@ dependencies = [
|
||||
"uv==0.11.21",
|
||||
"voluptuous==0.15.2",
|
||||
"voluptuous-serialize==2.7.0",
|
||||
"voluptuous-openapi==0.3.0",
|
||||
"voluptuous-openapi==0.4.1",
|
||||
"yarl==1.24.2",
|
||||
"webrtc-models==0.3.0",
|
||||
"zeroconf==0.150.0",
|
||||
|
||||
Generated
+1
-1
@@ -56,7 +56,7 @@ typing-extensions>=4.15.0,<5.0
|
||||
ulid-transform==2.2.9
|
||||
urllib3>=2.0
|
||||
uv==0.11.21
|
||||
voluptuous-openapi==0.3.0
|
||||
voluptuous-openapi==0.4.1
|
||||
voluptuous-serialize==2.7.0
|
||||
voluptuous==0.15.2
|
||||
webrtc-models==0.3.0
|
||||
|
||||
Generated
+2
-2
@@ -2730,7 +2730,7 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==5.14.2
|
||||
python-roborock==5.21.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.47
|
||||
@@ -2899,7 +2899,7 @@ renault-api==0.5.12
|
||||
renson-endura-delta==1.7.2
|
||||
|
||||
# homeassistant.components.reolink
|
||||
reolink-aio==0.21.2
|
||||
reolink-aio==0.21.3
|
||||
|
||||
# homeassistant.components.radio_frequency
|
||||
rf-protocols==4.3.0
|
||||
|
||||
@@ -27,7 +27,7 @@ pytest-asyncio==1.4.0
|
||||
pytest-aiohttp==1.1.1
|
||||
pytest-cov==7.1.0
|
||||
pytest-freezer==0.4.9
|
||||
pytest-github-actions-annotate-failures==0.4.0
|
||||
pytest-github-actions-annotate-failures==0.4.1
|
||||
pytest-socket==0.8.0
|
||||
pytest-sugar==1.1.1
|
||||
pytest-timeout==2.4.0
|
||||
|
||||
@@ -913,7 +913,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"tesla_wall_connector",
|
||||
"thermobeacon",
|
||||
"thermopro",
|
||||
"thermoworks_smoke",
|
||||
"thethingsnetwork",
|
||||
"thingspeak",
|
||||
"thinkingcleaner",
|
||||
@@ -1896,7 +1895,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"tesla_wall_connector",
|
||||
"thermobeacon",
|
||||
"thermopro",
|
||||
"thermoworks_smoke",
|
||||
"thethingsnetwork",
|
||||
"thingspeak",
|
||||
"thinkingcleaner",
|
||||
|
||||
@@ -257,8 +257,8 @@ WAVE_DEVICE_INFO = AirthingsDevice(
|
||||
"temperature": 21.0,
|
||||
"co2": 500.0,
|
||||
"voc": 155.0,
|
||||
"radon_1day_level": "very low",
|
||||
"radon_longterm_level": "very low",
|
||||
"radon_1day_level": "good",
|
||||
"radon_longterm_level": "poor",
|
||||
"pressure": 1020,
|
||||
},
|
||||
address="cc:cc:cc:cc:cc:cc",
|
||||
|
||||
@@ -853,12 +853,11 @@
|
||||
}),
|
||||
]),
|
||||
'envoy_model_data': dict({
|
||||
'ctmeter_consumption': None,
|
||||
'ctmeter_consumption_phases': None,
|
||||
'ctmeter_production': None,
|
||||
'ctmeter_production_phases': None,
|
||||
'ctmeter_storage': None,
|
||||
'ctmeter_storage_phases': None,
|
||||
'acb_inventory': None,
|
||||
'acb_power': None,
|
||||
'battery_aggregate': None,
|
||||
'c6cc': None,
|
||||
'collar': None,
|
||||
'ctmeters': dict({
|
||||
}),
|
||||
'ctmeters_phases': dict({
|
||||
@@ -1768,12 +1767,11 @@
|
||||
}),
|
||||
]),
|
||||
'envoy_model_data': dict({
|
||||
'ctmeter_consumption': None,
|
||||
'ctmeter_consumption_phases': None,
|
||||
'ctmeter_production': None,
|
||||
'ctmeter_production_phases': None,
|
||||
'ctmeter_storage': None,
|
||||
'ctmeter_storage_phases': None,
|
||||
'acb_inventory': None,
|
||||
'acb_power': None,
|
||||
'battery_aggregate': None,
|
||||
'c6cc': None,
|
||||
'collar': None,
|
||||
'ctmeters': dict({
|
||||
}),
|
||||
'ctmeters_phases': dict({
|
||||
@@ -1822,8 +1820,14 @@
|
||||
]),
|
||||
}),
|
||||
'fixtures': dict({
|
||||
'/admin/lib/acb_config': 'Testing request replies.',
|
||||
'/admin/lib/acb_config_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/admin/lib/network_display': 'Testing request replies.',
|
||||
'/admin/lib/network_display_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/admin/lib/tariff': 'Testing request replies.',
|
||||
'/admin/lib/tariff_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/admin/lib/wireless_display': 'Testing request replies.',
|
||||
'/admin/lib/wireless_display_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/api/v1/production': 'Testing request replies.',
|
||||
'/api/v1/production/inverters': 'Testing request replies.',
|
||||
'/api/v1/production/inverters_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
@@ -1832,6 +1836,8 @@
|
||||
'/home_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/info': 'Testing request replies.',
|
||||
'/info_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/inventory.json?deleted=1': 'Testing request replies.',
|
||||
'/inventory.json?deleted=1_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ensemble/dry_contacts': 'Testing request replies.',
|
||||
'/ivp/ensemble/dry_contacts_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ensemble/generator': 'Testing request replies.',
|
||||
@@ -1840,18 +1846,26 @@
|
||||
'/ivp/ensemble/inventory_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ensemble/power': 'Testing request replies.',
|
||||
'/ivp/ensemble/power_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ensemble/relay': 'Testing request replies.',
|
||||
'/ivp/ensemble/relay_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ensemble/secctrl': 'Testing request replies.',
|
||||
'/ivp/ensemble/secctrl_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ensemble/status': 'Testing request replies.',
|
||||
'/ivp/ensemble/status_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/livedata/status': 'Testing request replies.',
|
||||
'/ivp/livedata/status_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/meters': 'Testing request replies.',
|
||||
'/ivp/meters/readings': 'Testing request replies.',
|
||||
'/ivp/meters/readings_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/meters_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/pdm/device_data': 'Testing request replies.',
|
||||
'/ivp/pdm/device_data_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/pdm/energy': 'Testing request replies.',
|
||||
'/ivp/pdm/energy_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/sc/pvlimit': 'Testing request replies.',
|
||||
'/ivp/sc/pvlimit_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/sc/sched': 'Testing request replies.',
|
||||
'/ivp/sc/sched_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ss/dry_contact_settings': 'Testing request replies.',
|
||||
'/ivp/ss/dry_contact_settings_log': '{"headers":{"Hello":"World"},"code":200}',
|
||||
'/ivp/ss/gen_config': 'Testing request replies.',
|
||||
@@ -2727,12 +2741,11 @@
|
||||
}),
|
||||
]),
|
||||
'envoy_model_data': dict({
|
||||
'ctmeter_consumption': None,
|
||||
'ctmeter_consumption_phases': None,
|
||||
'ctmeter_production': None,
|
||||
'ctmeter_production_phases': None,
|
||||
'ctmeter_storage': None,
|
||||
'ctmeter_storage_phases': None,
|
||||
'acb_inventory': None,
|
||||
'acb_power': None,
|
||||
'battery_aggregate': None,
|
||||
'c6cc': None,
|
||||
'collar': None,
|
||||
'ctmeters': dict({
|
||||
}),
|
||||
'ctmeters_phases': dict({
|
||||
@@ -2781,9 +2794,18 @@
|
||||
]),
|
||||
}),
|
||||
'fixtures': dict({
|
||||
'/admin/lib/acb_config_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/admin/lib/network_display_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/admin/lib/tariff_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/admin/lib/wireless_display_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/api/v1/production/inverters_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
@@ -2796,6 +2818,9 @@
|
||||
'/info_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/inventory.json?deleted=1_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/ensemble/dry_contacts_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
@@ -2808,12 +2833,18 @@
|
||||
'/ivp/ensemble/power_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/ensemble/relay_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/ensemble/secctrl_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/ensemble/status_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/livedata/status_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/meters/readings_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
@@ -2823,9 +2854,15 @@
|
||||
'/ivp/pdm/device_data_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/pdm/energy_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/sc/pvlimit_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/sc/sched_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
'/ivp/ss/dry_contact_settings_log': dict({
|
||||
'Error': "EnvoyError('Test')",
|
||||
}),
|
||||
@@ -3711,12 +3748,11 @@
|
||||
}),
|
||||
]),
|
||||
'envoy_model_data': dict({
|
||||
'ctmeter_consumption': None,
|
||||
'ctmeter_consumption_phases': None,
|
||||
'ctmeter_production': None,
|
||||
'ctmeter_production_phases': None,
|
||||
'ctmeter_storage': None,
|
||||
'ctmeter_storage_phases': None,
|
||||
'acb_inventory': None,
|
||||
'acb_power': None,
|
||||
'battery_aggregate': None,
|
||||
'c6cc': None,
|
||||
'collar': None,
|
||||
'ctmeters': dict({
|
||||
}),
|
||||
'ctmeters_phases': dict({
|
||||
@@ -19745,59 +19781,16 @@
|
||||
}),
|
||||
]),
|
||||
'envoy_model_data': dict({
|
||||
'ctmeter_consumption': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000020', timestamp=1708006120, energy_delivered=21234, energy_received=22345, active_power=101, power_factor=0.21, voltage=112, current=0.3, frequency=50.2, state='enabled', measurement_type='net-consumption', metering_status='normal', status_flags=[])",
|
||||
'acb_inventory': None,
|
||||
'acb_power': None,
|
||||
'battery_aggregate': None,
|
||||
'c6cc': dict({
|
||||
'__type': "<class 'pyenphase.models.c6combiner.EnvoyC6CC'>",
|
||||
'repr': "EnvoyC6CC(admin_state=82, admin_state_str='ENCMN_C6_CC_READY', firmware_loaded_date=1752945451, firmware_version='0.1.20-D1', installed_date=1752945451, last_report_date=1752945451, communicating=True, part_number='800-02403-r08', serial_number='482523040549', dmir_version='0.1.20-D1')",
|
||||
}),
|
||||
'ctmeter_consumption_phases': dict({
|
||||
'L1': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000021', timestamp=1708006121, energy_delivered=212341, energy_received=223451, active_power=21, power_factor=0.22, voltage=112, current=0.3, frequency=50.2, state='enabled', measurement_type='net-consumption', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
'L2': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000022', timestamp=1708006122, energy_delivered=212342, energy_received=223452, active_power=31, power_factor=0.23, voltage=112, current=0.3, frequency=50.2, state='enabled', measurement_type='net-consumption', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
'L3': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000023', timestamp=1708006123, energy_delivered=212343, energy_received=223453, active_power=51, power_factor=0.24, voltage=112, current=0.3, frequency=50.2, state='enabled', measurement_type='net-consumption', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
}),
|
||||
'ctmeter_production': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000010', timestamp=1708006110, energy_delivered=11234, energy_received=12345, active_power=100, power_factor=0.11, voltage=111, current=0.2, frequency=50.1, state='enabled', measurement_type='production', metering_status='normal', status_flags=['production-imbalance', 'power-on-unused-phase'])",
|
||||
}),
|
||||
'ctmeter_production_phases': dict({
|
||||
'L1': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000011', timestamp=1708006111, energy_delivered=112341, energy_received=123451, active_power=20, power_factor=0.12, voltage=111, current=0.2, frequency=50.1, state='enabled', measurement_type='production', metering_status='normal', status_flags=['production-imbalance'])",
|
||||
}),
|
||||
'L2': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000012', timestamp=1708006112, energy_delivered=112342, energy_received=123452, active_power=30, power_factor=0.13, voltage=111, current=0.2, frequency=50.1, state='enabled', measurement_type='production', metering_status='normal', status_flags=['power-on-unused-phase'])",
|
||||
}),
|
||||
'L3': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000013', timestamp=1708006113, energy_delivered=112343, energy_received=123453, active_power=50, power_factor=0.14, voltage=111, current=0.2, frequency=50.1, state='enabled', measurement_type='production', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
}),
|
||||
'ctmeter_storage': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000030', timestamp=1708006120, energy_delivered=31234, energy_received=32345, active_power=103, power_factor=0.23, voltage=113, current=0.4, frequency=50.3, state='enabled', measurement_type='storage', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
'ctmeter_storage_phases': dict({
|
||||
'L1': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000031', timestamp=1708006121, energy_delivered=312341, energy_received=323451, active_power=22, power_factor=0.32, voltage=113, current=0.4, frequency=50.3, state='enabled', measurement_type='storage', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
'L2': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000032', timestamp=1708006122, energy_delivered=312342, energy_received=323452, active_power=33, power_factor=0.23, voltage=112, current=0.3, frequency=50.2, state='enabled', measurement_type='storage', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
'L3': dict({
|
||||
'__type': "<class 'pyenphase.models.meters.EnvoyMeterData'>",
|
||||
'repr': "EnvoyMeterData(eid='100000033', timestamp=1708006123, energy_delivered=312343, energy_received=323453, active_power=53, power_factor=0.24, voltage=112, current=0.3, frequency=50.2, state='enabled', measurement_type='storage', metering_status='normal', status_flags=[])",
|
||||
}),
|
||||
'collar': dict({
|
||||
'__type': "<class 'pyenphase.models.collar.EnvoyCollar'>",
|
||||
'repr': "EnvoyCollar(admin_state=88, admin_state_str='ENCMN_MDE_ON_GRID', firmware_loaded_date=1752939759, firmware_version='3.0.6-D0', installed_date=1752939759, last_report_date=1752939759, communicating=True, mid_state='close', grid_state='on_grid', part_number='865-00400-r22', serial_number='482520020939', temperature=42, temperature_unit='C', control_error=0, collar_state='Installed')",
|
||||
}),
|
||||
'ctmeters': dict({
|
||||
'backfeed': dict({
|
||||
|
||||
@@ -607,7 +607,7 @@ async def test_entity_id_preserved_on_upgrade_when_in_storage(
|
||||
ent_reg_entry = entity_registry.async_get_or_create(
|
||||
Platform.BINARY_SENSOR,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA/0/binary_sensor/my",
|
||||
"11:22:33:44:55:AA-binary_sensor-my",
|
||||
)
|
||||
entity_registry.async_update_entity(
|
||||
ent_reg_entry.entity_id,
|
||||
@@ -1161,58 +1161,6 @@ async def test_entity_id_with_empty_sub_device_name(
|
||||
assert hass.states.get("binary_sensor.main_device_sensor") is not None
|
||||
|
||||
|
||||
async def test_legacy_unique_id_migrated_to_v3_sub_device(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_client: APIClient,
|
||||
mock_esphome_device: MockESPHomeDeviceType,
|
||||
) -> None:
|
||||
"""Test a legacy sub-device unique id is migrated to the version 3 format."""
|
||||
sub_devices = [
|
||||
SubDeviceInfo(device_id=22222222, name="kitchen_controller", area_id=0),
|
||||
]
|
||||
device_info = {"name": "test", "devices": sub_devices}
|
||||
entity_info = [
|
||||
BinarySensorInfo(
|
||||
object_id="temperature",
|
||||
key=1,
|
||||
name="Temperature",
|
||||
device_id=22222222,
|
||||
),
|
||||
]
|
||||
states = [BinarySensorState(key=1, state=True, missing_state=False)]
|
||||
|
||||
# Seed a registry entry in the legacy format with the @device_id suffix
|
||||
legacy_entry = entity_registry.async_get_or_create(
|
||||
Platform.BINARY_SENSOR,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA-binary_sensor-temperature@22222222",
|
||||
suggested_object_id="kitchen_controller_temperature",
|
||||
)
|
||||
|
||||
await mock_esphome_device(
|
||||
mock_client=mock_client,
|
||||
device_info=device_info,
|
||||
entity_info=entity_info,
|
||||
states=states,
|
||||
)
|
||||
|
||||
entity_entry = entity_registry.async_get(legacy_entry.entity_id)
|
||||
assert entity_entry is not None
|
||||
# The legacy id is renamed to version 3, keeping the same entity
|
||||
assert (
|
||||
entity_entry.unique_id == "11:22:33:44:55:AA/22222222/binary_sensor/Temperature"
|
||||
)
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(
|
||||
Platform.BINARY_SENSOR,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA-binary_sensor-temperature@22222222",
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
async def test_unique_id_migration_when_entity_moves_between_devices(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
@@ -1256,8 +1204,8 @@ async def test_unique_id_migration_when_entity_moves_between_devices(
|
||||
entity_entry = entity_registry.async_get("binary_sensor.test_temperature")
|
||||
assert entity_entry is not None
|
||||
initial_unique_id = entity_entry.unique_id
|
||||
# Main device entities use device_id 0 in the unique id
|
||||
assert "/0/" in initial_unique_id
|
||||
# Initial unique_id should not have @device_id suffix since it's on main device
|
||||
assert "@" not in initial_unique_id
|
||||
|
||||
# Add sub-device to device info
|
||||
sub_devices = [
|
||||
@@ -1312,8 +1260,8 @@ async def test_unique_id_migration_when_entity_moves_between_devices(
|
||||
# Wait for entity to be updated
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# The entity_id doesn't change when moving between devices,
|
||||
# only the device segment of the unique_id changes
|
||||
# The entity_id doesn't change when moving between devices
|
||||
# Only the unique_id gets updated with @device_id suffix
|
||||
state = hass.states.get("binary_sensor.test_temperature")
|
||||
assert state is not None
|
||||
|
||||
@@ -1321,8 +1269,9 @@ async def test_unique_id_migration_when_entity_moves_between_devices(
|
||||
entity_entry = entity_registry.async_get("binary_sensor.test_temperature")
|
||||
assert entity_entry is not None
|
||||
|
||||
# Unique ID device segment should now be the sub-device id
|
||||
expected_unique_id = initial_unique_id.replace("/0/", "/22222222/")
|
||||
# Unique ID should have been migrated to include @device_id
|
||||
# This is done by our build_device_unique_id wrapper
|
||||
expected_unique_id = f"{initial_unique_id}@22222222"
|
||||
assert entity_entry.unique_id == expected_unique_id
|
||||
|
||||
# Entity should now be associated with the sub-device
|
||||
@@ -1382,8 +1331,8 @@ async def test_unique_id_migration_sub_device_to_main_device(
|
||||
)
|
||||
assert entity_entry is not None
|
||||
initial_unique_id = entity_entry.unique_id
|
||||
# Sub-device entities carry the sub-device id in the unique id
|
||||
assert "/22222222/" in initial_unique_id
|
||||
# Initial unique_id should have @device_id suffix since it's on sub-device
|
||||
assert "@22222222" in initial_unique_id
|
||||
|
||||
# Update entity info - move to main device
|
||||
new_entity_info = [
|
||||
@@ -1416,8 +1365,8 @@ async def test_unique_id_migration_sub_device_to_main_device(
|
||||
)
|
||||
assert entity_entry is not None
|
||||
|
||||
# Unique ID device segment should now be the main device id 0
|
||||
expected_unique_id = initial_unique_id.replace("/22222222/", "/0/")
|
||||
# Unique ID should have been migrated to remove @device_id suffix
|
||||
expected_unique_id = initial_unique_id.replace("@22222222", "")
|
||||
assert entity_entry.unique_id == expected_unique_id
|
||||
|
||||
# Entity should now be associated with the main device
|
||||
@@ -1478,8 +1427,8 @@ async def test_unique_id_migration_between_sub_devices(
|
||||
)
|
||||
assert entity_entry is not None
|
||||
initial_unique_id = entity_entry.unique_id
|
||||
# Sub-device entities carry the sub-device id in the unique id
|
||||
assert "/22222222/" in initial_unique_id
|
||||
# Initial unique_id should have @22222222 suffix
|
||||
assert "@22222222" in initial_unique_id
|
||||
|
||||
# Update entity info - move to second sub-device
|
||||
new_entity_info = [
|
||||
@@ -1512,8 +1461,8 @@ async def test_unique_id_migration_between_sub_devices(
|
||||
)
|
||||
assert entity_entry is not None
|
||||
|
||||
# Unique ID device segment should have moved from 22222222 to 33333333
|
||||
expected_unique_id = initial_unique_id.replace("/22222222/", "/33333333/")
|
||||
# Unique ID should have been migrated from @22222222 to @33333333
|
||||
expected_unique_id = initial_unique_id.replace("@22222222", "@33333333")
|
||||
assert entity_entry.unique_id == expected_unique_id
|
||||
|
||||
# Entity should now be associated with the second sub-device
|
||||
@@ -1575,8 +1524,8 @@ async def test_entity_device_id_rename_in_yaml(
|
||||
entity_entry = entity_registry.async_get("binary_sensor.old_device_sensor")
|
||||
assert entity_entry is not None
|
||||
initial_unique_id = entity_entry.unique_id
|
||||
# Sub-device entities carry the sub-device id in the unique id
|
||||
assert "/11111111/" in initial_unique_id
|
||||
# Should have @11111111 suffix
|
||||
assert "@11111111" in initial_unique_id
|
||||
|
||||
# Simulate user renaming device_id in YAML config
|
||||
# The device_id hash changes from 11111111 to 99999999
|
||||
@@ -1638,8 +1587,9 @@ async def test_entity_device_id_rename_in_yaml(
|
||||
entity_entry = entity_registry.async_get("binary_sensor.renamed_device_sensor")
|
||||
assert entity_entry is not None
|
||||
|
||||
# Unique ID device segment should have the new device_id
|
||||
expected_unique_id = initial_unique_id.replace("/11111111/", "/99999999/")
|
||||
# Unique ID should have the new device_id
|
||||
base_unique_id = initial_unique_id.replace("@11111111", "")
|
||||
expected_unique_id = f"{base_unique_id}@99999999"
|
||||
assert entity_entry.unique_id == expected_unique_id
|
||||
|
||||
# Entity should be associated with the new device
|
||||
|
||||
@@ -21,53 +21,6 @@ from homeassistant.helpers.service_info.esphome import ESPHomeServiceInfo
|
||||
from .conftest import MockGenericDeviceEntryType
|
||||
|
||||
|
||||
async def test_migrate_entity_unique_id(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_client: APIClient,
|
||||
mock_generic_device_entry: MockGenericDeviceEntryType,
|
||||
) -> None:
|
||||
"""Test a legacy unique id is migrated to the version 3 format."""
|
||||
entity_registry.async_get_or_create(
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA-sensor-mysensor",
|
||||
suggested_object_id="my_sensor",
|
||||
disabled_by=None,
|
||||
)
|
||||
entity_info = [
|
||||
SensorInfo(
|
||||
object_id="mysensor",
|
||||
key=1,
|
||||
name="my sensor",
|
||||
entity_category=ESPHomeEntityCategory.DIAGNOSTIC,
|
||||
icon="mdi:leaf",
|
||||
)
|
||||
]
|
||||
states = [SensorState(key=1, state=50)]
|
||||
user_service = []
|
||||
await mock_generic_device_entry(
|
||||
mock_client=mock_client,
|
||||
entity_info=entity_info,
|
||||
user_service=user_service,
|
||||
states=states,
|
||||
)
|
||||
state = hass.states.get("sensor.my_sensor")
|
||||
assert state is not None
|
||||
assert state.state == "50"
|
||||
entry = entity_registry.async_get("sensor.my_sensor")
|
||||
assert entry is not None
|
||||
# The legacy unique id should have been renamed to the version 3 format,
|
||||
# keeping the entity (and its entity_id) instead of creating a new one
|
||||
assert entry.unique_id == "11:22:33:44:55:AA/0/sensor/my sensor"
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, "11:22:33:44:55:AA-sensor-mysensor"
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
async def test_migrate_entity_unique_id_downgrade_upgrade(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
@@ -75,20 +28,18 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
|
||||
mock_generic_device_entry: MockGenericDeviceEntryType,
|
||||
) -> None:
|
||||
"""Test unique id migration prefers the original entity on downgrade upgrade."""
|
||||
# The original entity, already in the version 3 format
|
||||
entity_registry.async_get_or_create(
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA/0/sensor/my sensor",
|
||||
suggested_object_id="new_sensor",
|
||||
"my_sensor",
|
||||
suggested_object_id="old_sensor",
|
||||
disabled_by=None,
|
||||
)
|
||||
# A duplicate left behind in the legacy format by a downgrade
|
||||
entity_registry.async_get_or_create(
|
||||
SENSOR_DOMAIN,
|
||||
DOMAIN,
|
||||
"11:22:33:44:55:AA-sensor-mysensor",
|
||||
suggested_object_id="old_sensor",
|
||||
suggested_object_id="new_sensor",
|
||||
disabled_by=None,
|
||||
)
|
||||
entity_info = [
|
||||
@@ -113,17 +64,17 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
|
||||
assert state.state == "50"
|
||||
entry = entity_registry.async_get("sensor.new_sensor")
|
||||
assert entry is not None
|
||||
# Confirm we did not touch the legacy entity that was created
|
||||
# Confirm we did not touch the entity that was created
|
||||
# on downgrade so when they upgrade again they can delete the
|
||||
# entity that was only created on downgrade and they keep
|
||||
# the original one.
|
||||
assert (
|
||||
entity_registry.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, "11:22:33:44:55:AA-sensor-mysensor"
|
||||
)
|
||||
entity_registry.async_get_entity_id(SENSOR_DOMAIN, DOMAIN, "my_sensor")
|
||||
is not None
|
||||
)
|
||||
assert entry.unique_id == "11:22:33:44:55:AA/0/sensor/my sensor"
|
||||
# Note that ESPHome includes the EntityInfo type in the unique id
|
||||
# as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
|
||||
assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
|
||||
|
||||
|
||||
async def test_discover_zwave() -> None:
|
||||
|
||||
@@ -139,15 +139,13 @@ async def test_device_conflict_migration(
|
||||
|
||||
ent_reg_entry = entity_registry.async_get("binary_sensor.test_my_binary_sensor")
|
||||
assert ent_reg_entry
|
||||
assert (
|
||||
ent_reg_entry.unique_id == "11:22:33:44:55:AA/0/binary_sensor/my binary_sensor"
|
||||
)
|
||||
assert ent_reg_entry.unique_id == "11:22:33:44:55:AA-binary_sensor-mybinary_sensor"
|
||||
entries = er.async_entries_for_config_entry(
|
||||
entity_registry, mock_config_entry.entry_id
|
||||
)
|
||||
assert entries is not None
|
||||
for entry in entries:
|
||||
assert entry.unique_id.startswith("11:22:33:44:55:AA/")
|
||||
assert entry.unique_id.startswith("11:22:33:44:55:AA-")
|
||||
disconnect_done = hass.loop.create_future()
|
||||
|
||||
async def async_disconnect(*args, **kwargs) -> None:
|
||||
@@ -203,16 +201,14 @@ async def test_device_conflict_migration(
|
||||
assert mock_config_entry.unique_id == "11:22:33:44:55:ab"
|
||||
ent_reg_entry = entity_registry.async_get("binary_sensor.test_my_binary_sensor")
|
||||
assert ent_reg_entry
|
||||
assert (
|
||||
ent_reg_entry.unique_id == "11:22:33:44:55:AB/0/binary_sensor/my binary_sensor"
|
||||
)
|
||||
assert ent_reg_entry.unique_id == "11:22:33:44:55:AB-binary_sensor-mybinary_sensor"
|
||||
|
||||
entries = er.async_entries_for_config_entry(
|
||||
entity_registry, mock_config_entry.entry_id
|
||||
)
|
||||
assert entries is not None
|
||||
for entry in entries:
|
||||
assert entry.unique_id.startswith("11:22:33:44:55:AB/")
|
||||
assert entry.unique_id.startswith("11:22:33:44:55:AB-")
|
||||
|
||||
dev_entry = device_registry.async_get_device(
|
||||
identifiers={}, connections={(dr.CONNECTION_NETWORK_MAC, "11:22:33:44:55:ab")}
|
||||
|
||||
@@ -129,7 +129,7 @@ async def test_generic_numeric_sensor_with_entity_category_and_icon(
|
||||
assert entry is not None
|
||||
# Note that ESPHome includes the EntityInfo type in the unique id
|
||||
# as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
|
||||
assert entry.unique_id == "11:22:33:44:55:AA/0/sensor/my sensor"
|
||||
assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
|
||||
assert entry.entity_category is EntityCategory.DIAGNOSTIC
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ async def test_generic_numeric_sensor_state_class_measurement(
|
||||
assert entry is not None
|
||||
# Note that ESPHome includes the EntityInfo type in the unique id
|
||||
# as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
|
||||
assert entry.unique_id == "11:22:33:44:55:AA/0/sensor/my sensor"
|
||||
assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
|
||||
assert entry.entity_category is None
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ async def test_generic_numeric_sensor_state_class_measurement_angle(
|
||||
assert entry is not None
|
||||
# Note that ESPHome includes the EntityInfo type in the unique id
|
||||
# as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
|
||||
assert entry.unique_id == "11:22:33:44:55:AA/0/sensor/my sensor"
|
||||
assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
|
||||
assert entry.entity_category is None
|
||||
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'c6h6',
|
||||
'unique_id': '123-c6h6',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_benzene-state]
|
||||
@@ -116,7 +116,7 @@
|
||||
<EntityStateAttribute.ATTRIBUTION: 'attribution'>: 'Data provided by GIOŚ',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Benzene',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_benzene',
|
||||
@@ -165,7 +165,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-co',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_carbon_monoxide-state]
|
||||
@@ -175,7 +175,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_monoxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Carbon monoxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_carbon_monoxide',
|
||||
@@ -224,7 +224,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-no2',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_nitrogen_dioxide-state]
|
||||
@@ -234,7 +234,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'nitrogen_dioxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Nitrogen dioxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_nitrogen_dioxide',
|
||||
@@ -352,7 +352,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-no',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_nitrogen_monoxide-state]
|
||||
@@ -362,7 +362,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'nitrogen_monoxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Nitrogen monoxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_nitrogen_monoxide',
|
||||
@@ -411,7 +411,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nox',
|
||||
'unique_id': '123-nox',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_nitrogen_oxides-state]
|
||||
@@ -420,7 +420,7 @@
|
||||
<EntityStateAttribute.ATTRIBUTION: 'attribution'>: 'Data provided by GIOŚ',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Nitrogen oxides',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_nitrogen_oxides',
|
||||
@@ -469,7 +469,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-o3',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_ozone-state]
|
||||
@@ -479,7 +479,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'ozone',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Ozone',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_ozone',
|
||||
@@ -597,7 +597,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-pm10',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_pm10-state]
|
||||
@@ -607,7 +607,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home PM10',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_pm10',
|
||||
@@ -725,7 +725,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-pm25',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_pm2_5-state]
|
||||
@@ -735,7 +735,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home PM2.5',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_pm2_5',
|
||||
@@ -853,7 +853,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-so2',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.home_sulphur_dioxide-state]
|
||||
@@ -863,7 +863,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'sulphur_dioxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Home Sulphur dioxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_sulphur_dioxide',
|
||||
|
||||
@@ -107,12 +107,12 @@ async def test_job_manager_ws_updates(
|
||||
job_data: Job | None = None
|
||||
|
||||
@callback
|
||||
def mock_subcription_callback(job: Job) -> None:
|
||||
def mock_subscription_callback(job: Job) -> None:
|
||||
nonlocal job_data
|
||||
job_data = job
|
||||
|
||||
subscription = JobSubscription(
|
||||
mock_subcription_callback, name="test_job", reference="test"
|
||||
mock_subscription_callback, name="test_job", reference="test"
|
||||
)
|
||||
unsubscribe = data_coordinator.jobs.subscribe(subscription)
|
||||
|
||||
@@ -318,11 +318,11 @@ async def test_job_manager_reload_on_supervisor_restart(
|
||||
job_data: Job | None = None
|
||||
|
||||
@callback
|
||||
def mock_subcription_callback(job: Job) -> None:
|
||||
def mock_subscription_callback(job: Job) -> None:
|
||||
nonlocal job_data
|
||||
job_data = job
|
||||
|
||||
subscription = JobSubscription(mock_subcription_callback, name="test_job")
|
||||
subscription = JobSubscription(mock_subscription_callback, name="test_job")
|
||||
data_coordinator.jobs.subscribe(subscription)
|
||||
|
||||
# Send supervisor restart signal
|
||||
@@ -347,3 +347,78 @@ async def test_job_manager_reload_on_supervisor_restart(
|
||||
assert job_data.reference == "test"
|
||||
assert job_data.done is True
|
||||
assert not data_coordinator.jobs.current_jobs
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("all_setup_requests")
|
||||
async def test_subscribe_returns_unsubscribe_when_job_already_matches(
|
||||
hass: HomeAssistant,
|
||||
jobs_info: AsyncMock,
|
||||
hass_supervisor_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test subscribe returns a working unsubscribe even if a job already matches."""
|
||||
jobs_info.return_value = JobsInfo(
|
||||
ignore_conditions=[],
|
||||
jobs=[
|
||||
Job(
|
||||
name="test_job",
|
||||
reference="test",
|
||||
uuid=uuid4(),
|
||||
progress=0,
|
||||
stage=None,
|
||||
done=False,
|
||||
errors=[],
|
||||
created=datetime.now(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
extra=None,
|
||||
child_jobs=[],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
result = await async_setup_component(hass, DOMAIN, {})
|
||||
assert result
|
||||
|
||||
client = await hass_supervisor_ws_client()
|
||||
data_coordinator: HassioMainDataUpdateCoordinator = hass.data[MAIN_COORDINATOR]
|
||||
|
||||
received: list[Job] = []
|
||||
|
||||
@callback
|
||||
def mock_subscription_callback(job: Job) -> None:
|
||||
received.append(job)
|
||||
|
||||
subscription = JobSubscription(mock_subscription_callback, name="test_job")
|
||||
unsubscribe = data_coordinator.jobs.subscribe(subscription)
|
||||
|
||||
# Existing matching job is delivered immediately, and a callable unsubscribe
|
||||
# is returned (not the None result of the callback)
|
||||
assert len(received) == 1
|
||||
assert received[0].name == "test_job"
|
||||
assert callable(unsubscribe)
|
||||
|
||||
# After unsubscribing, a new matching job update is no longer delivered
|
||||
unsubscribe()
|
||||
await client.send_json(
|
||||
{
|
||||
"id": 1,
|
||||
"type": "supervisor/event",
|
||||
"data": {
|
||||
"event": "job",
|
||||
"data": {
|
||||
"name": "test_job",
|
||||
"reference": "test",
|
||||
"uuid": uuid4().hex,
|
||||
"progress": 50,
|
||||
"stage": None,
|
||||
"done": False,
|
||||
"errors": [],
|
||||
"created": datetime.now().isoformat(), # pylint: disable=home-assistant-enforce-naive-now
|
||||
"extra": None,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["success"]
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(received) == 1
|
||||
|
||||
@@ -96,5 +96,7 @@
|
||||
"8646": 150,
|
||||
"11005": 35.5,
|
||||
"7171": 1,
|
||||
"680": 0
|
||||
"680": 0,
|
||||
"11019": 45,
|
||||
"11020": 0
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@
|
||||
'11010': 52.3,
|
||||
'11011': 85,
|
||||
'11016': 0,
|
||||
'11019': 45,
|
||||
'11020': 0,
|
||||
'11034': 100,
|
||||
'11042': 32.1,
|
||||
'142': 1.79,
|
||||
|
||||
@@ -6635,6 +6635,122 @@
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_remaining_charging_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.cms_sf2000_remaining_charging_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Remaining charging time',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Remaining charging time',
|
||||
'platform': 'indevolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'remaining_charging_time',
|
||||
'unique_id': 'SolidFlex2000-87654321_11019',
|
||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_remaining_charging_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'duration',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'CMS-SF2000 Remaining charging time',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTime.MINUTES: 'min'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.cms_sf2000_remaining_charging_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_remaining_discharging_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.cms_sf2000_remaining_discharging_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Remaining discharging time',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 2,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Remaining discharging time',
|
||||
'platform': 'indevolt',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'remaining_discharging_time',
|
||||
'unique_id': 'SolidFlex2000-87654321_11020',
|
||||
'unit_of_measurement': <UnitOfTime.MINUTES: 'min'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_remaining_discharging_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'duration',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'CMS-SF2000 Remaining discharging time',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfTime.MINUTES: 'min'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.cms_sf2000_remaining_discharging_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unavailable',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_total_ac_input_energy-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -157,6 +157,68 @@ async def test_inverter_sensor_temperature_availability(
|
||||
assert hass.states.get("sensor.bk1600_inverter_temperature").state == "0"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("generation", [2], indirect=True)
|
||||
async def test_remaining_time_sensor_charge_state_availability(
|
||||
hass: HomeAssistant,
|
||||
mock_indevolt: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test remaining time sensors are only available in the matching charge/discharge state."""
|
||||
with patch("homeassistant.components.indevolt.PLATFORMS", [Platform.SENSOR]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
# Default charge / discharge state is static (1000), both sensors should be unavailable
|
||||
assert (
|
||||
hass.states.get("sensor.cms_sf2000_remaining_charging_time").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.cms_sf2000_remaining_discharging_time").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
|
||||
# Switch to charging (1001), only remaining charging time should be available
|
||||
mock_indevolt.fetch_data.return_value[IndevoltBattery.CHARGE_DISCHARGE_STATE] = 1001
|
||||
freezer.tick(delta=timedelta(seconds=SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("sensor.cms_sf2000_remaining_charging_time").state == "45"
|
||||
assert (
|
||||
hass.states.get("sensor.cms_sf2000_remaining_discharging_time").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
|
||||
# Switch to discharging (1002), only remaining discharging time should be available
|
||||
mock_indevolt.fetch_data.return_value[IndevoltBattery.CHARGE_DISCHARGE_STATE] = 1002
|
||||
freezer.tick(delta=timedelta(seconds=SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
hass.states.get("sensor.cms_sf2000_remaining_charging_time").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
assert hass.states.get("sensor.cms_sf2000_remaining_discharging_time").state == "0"
|
||||
|
||||
# Switch back to static (1000), both sensors become unavailable again
|
||||
mock_indevolt.fetch_data.return_value[IndevoltBattery.CHARGE_DISCHARGE_STATE] = 1000
|
||||
freezer.tick(delta=timedelta(seconds=SCAN_INTERVAL))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (
|
||||
hass.states.get("sensor.cms_sf2000_remaining_charging_time").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
assert (
|
||||
hass.states.get("sensor.cms_sf2000_remaining_discharging_time").state
|
||||
== STATE_UNAVAILABLE
|
||||
)
|
||||
|
||||
|
||||
# In individual tests, you can override the mock behavior
|
||||
async def test_battery_pack_filtering(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'bme280_humidity',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-bme280_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_bme280_humidity-state]
|
||||
@@ -105,7 +105,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor BME280 humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_bme280_humidity',
|
||||
@@ -502,7 +502,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dht22_humidity',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-dht22_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_dht22_humidity-state]
|
||||
@@ -511,7 +511,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor DHT22 humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_dht22_humidity',
|
||||
@@ -676,7 +676,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'heca_humidity',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-heca_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_heca_humidity-state]
|
||||
@@ -685,7 +685,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor HECA humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_heca_humidity',
|
||||
@@ -792,7 +792,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'mhz14a_carbon_dioxide',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-mhz14a_carbon_dioxide',
|
||||
'unit_of_measurement': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_mh_z14a_carbon_dioxide-state]
|
||||
@@ -801,7 +801,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor MH-Z14A carbon dioxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_mh_z14a_carbon_dioxide',
|
||||
@@ -966,7 +966,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'pmsx003_pm1',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-pms_p0',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_pmsx003_pm1-state]
|
||||
@@ -975,7 +975,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm1',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor PMSx003 PM1',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_pmsx003_pm1',
|
||||
@@ -1024,7 +1024,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'pmsx003_pm10',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-pms_p1',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_pmsx003_pm10-state]
|
||||
@@ -1033,7 +1033,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor PMSx003 PM10',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_pmsx003_pm10',
|
||||
@@ -1082,7 +1082,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'pmsx003_pm25',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-pms_p2',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_pmsx003_pm2_5-state]
|
||||
@@ -1091,7 +1091,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor PMSx003 PM2.5',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_pmsx003_pm2_5',
|
||||
@@ -1256,7 +1256,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sds011_pm10',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sds011_p1',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sds011_pm10-state]
|
||||
@@ -1265,7 +1265,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SDS011 PM10',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sds011_pm10',
|
||||
@@ -1314,7 +1314,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sds011_pm25',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sds011_p2',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sds011_pm2_5-state]
|
||||
@@ -1323,7 +1323,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SDS011 PM2.5',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sds011_pm2_5',
|
||||
@@ -1372,7 +1372,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sht3x_humidity',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sht3x_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sht3x_humidity-state]
|
||||
@@ -1381,7 +1381,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SHT3X humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sht3x_humidity',
|
||||
@@ -1662,7 +1662,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sps30_pm1',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sps30_p0',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sps30_pm1-state]
|
||||
@@ -1671,7 +1671,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm1',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SPS30 PM1',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sps30_pm1',
|
||||
@@ -1720,7 +1720,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sps30_pm10',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sps30_p1',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sps30_pm10-state]
|
||||
@@ -1729,7 +1729,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm10',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SPS30 PM10',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sps30_pm10',
|
||||
@@ -1778,7 +1778,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sps30_pm25',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sps30_p2',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sps30_pm2_5-state]
|
||||
@@ -1787,7 +1787,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm25',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SPS30 PM2.5',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sps30_pm2_5',
|
||||
@@ -1836,7 +1836,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sps30_pm4',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff-sps30_p4',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
'unit_of_measurement': <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[sensor.nettigo_air_monitor_sps30_pm4-state]
|
||||
@@ -1845,7 +1845,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'pm4',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Nettigo Air Monitor SPS30 PM4',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'μg/m³',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfDensity.MICROGRAMS_PER_CUBIC_METER: 'μg/m³'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.nettigo_air_monitor_sps30_pm4',
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'ECC9FFE0E574_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.prana_recuperator_humidity-state]
|
||||
@@ -44,7 +44,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'PRANA RECUPERATOR Humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.prana_recuperator_humidity',
|
||||
|
||||
@@ -1076,7 +1076,7 @@ async def test_privacy_mode_change_callback(
|
||||
def register_callback(
|
||||
self, callback_id: str, callback: Callable[[], None], *args, **key_args
|
||||
) -> None:
|
||||
if callback_id == "privacy_mode_change":
|
||||
if callback_id == "privacy_mode_change_623":
|
||||
self.callback_func = callback
|
||||
|
||||
callback_mock = callback_mock_class()
|
||||
|
||||
@@ -4,9 +4,10 @@ import pytest
|
||||
from roborock.exceptions import RoborockTimeout
|
||||
|
||||
from homeassistant.components.number import ATTR_VALUE, SERVICE_SET_VALUE
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_component import async_update_entity
|
||||
|
||||
from .conftest import FakeDevice
|
||||
|
||||
@@ -49,6 +50,22 @@ async def test_update_sound_volume(
|
||||
assert state.state == "3.0"
|
||||
|
||||
|
||||
async def test_volume_unknown_value(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
fake_vacuum: FakeDevice,
|
||||
) -> None:
|
||||
"""Test the entity reports unknown when the trait value is None."""
|
||||
assert fake_vacuum.v1_properties is not None
|
||||
fake_vacuum.v1_properties.sound_volume.volume = None
|
||||
|
||||
await async_update_entity(hass, "number.roborock_s7_maxv_volume")
|
||||
|
||||
state = hass.states.get("number.roborock_s7_maxv_volume")
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
async def test_volume_update_failed(
|
||||
hass: HomeAssistant,
|
||||
setup_entry: MockConfigEntry,
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
"""Test Roborock Time platform."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from datetime import time
|
||||
from datetime import time, timedelta
|
||||
from typing import Any
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
import roborock
|
||||
from roborock.data import DnDTimer, RoborockBaseTimer, ValleyElectricityTimer
|
||||
|
||||
from homeassistant.components.time import SERVICE_SET_VALUE
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .conftest import FakeDevice
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -127,3 +128,53 @@ async def test_update_failure(
|
||||
target={"entity_id": entity_id},
|
||||
)
|
||||
assert fake_vacuum.v1_properties.dnd.set_dnd_timer.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "trait", "missing_attribute"),
|
||||
[
|
||||
(
|
||||
"time.roborock_s7_maxv_do_not_disturb_begin",
|
||||
lambda x: x.v1_properties.dnd,
|
||||
"start_hour",
|
||||
),
|
||||
(
|
||||
"time.roborock_s7_maxv_do_not_disturb_begin",
|
||||
lambda x: x.v1_properties.dnd,
|
||||
"start_minute",
|
||||
),
|
||||
(
|
||||
"time.roborock_s7_maxv_do_not_disturb_end",
|
||||
lambda x: x.v1_properties.dnd,
|
||||
"end_hour",
|
||||
),
|
||||
(
|
||||
"time.roborock_s7_maxv_off_peak_start",
|
||||
lambda x: x.v1_properties.valley_electricity_timer,
|
||||
"start_minute",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_missing_value(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
setup_entry: MockConfigEntry,
|
||||
fake_vacuum: FakeDevice,
|
||||
entity_id: str,
|
||||
trait: Callable[[FakeDevice], Any],
|
||||
missing_attribute: str,
|
||||
) -> None:
|
||||
"""Test that a missing time component reports as unknown instead of raising."""
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state != STATE_UNKNOWN
|
||||
|
||||
setattr(trait(fake_vacuum), missing_attribute, None)
|
||||
freezer.tick(timedelta(seconds=31))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
@@ -293,7 +293,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'left_slot_intensity',
|
||||
'unique_id': '123456789ABC-cury:0-left_slot_intensity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_device[cury_gen4][number.test_name_left_slot_intensity-state]
|
||||
@@ -304,7 +304,7 @@
|
||||
<NumberEntityCapabilityAttribute.MIN: 'min'>: 0,
|
||||
<NumberEntityCapabilityAttribute.MODE: 'mode'>: <NumberMode.SLIDER: 'slider'>,
|
||||
<NumberEntityCapabilityAttribute.STEP: 'step'>: 1,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_name_left_slot_intensity',
|
||||
@@ -353,7 +353,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'right_slot_intensity',
|
||||
'unique_id': '123456789ABC-cury:0-right_slot_intensity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_device[cury_gen4][number.test_name_right_slot_intensity-state]
|
||||
@@ -364,7 +364,7 @@
|
||||
<NumberEntityCapabilityAttribute.MIN: 'min'>: 0,
|
||||
<NumberEntityCapabilityAttribute.MODE: 'mode'>: <NumberMode.SLIDER: 'slider'>,
|
||||
<NumberEntityCapabilityAttribute.STEP: 'step'>: 1,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_name_right_slot_intensity',
|
||||
@@ -479,7 +479,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'left_slot_level',
|
||||
'unique_id': '123456789ABC-cury:0-cury_left_level',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_device[cury_gen4][sensor.test_name_left_slot_level-state]
|
||||
@@ -487,7 +487,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Test name Left slot level',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_name_left_slot_level',
|
||||
@@ -583,7 +583,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'right_slot_level',
|
||||
'unique_id': '123456789ABC-cury:0-cury_right_level',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_device[cury_gen4][sensor.test_name_right_slot_level-state]
|
||||
@@ -591,7 +591,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Test name Right slot level',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_name_right_slot_level',
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'valve_position',
|
||||
'unique_id': '123456789ABC-blutrv:200-valve_position',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_number_entity[number.trv_name_valve_position-state]
|
||||
@@ -110,7 +110,7 @@
|
||||
<NumberEntityCapabilityAttribute.MIN: 'min'>: 0,
|
||||
<NumberEntityCapabilityAttribute.MODE: 'mode'>: <NumberMode.SLIDER: 'slider'>,
|
||||
<NumberEntityCapabilityAttribute.STEP: 'step'>: 1,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.trv_name_valve_position',
|
||||
@@ -159,7 +159,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'left_slot_intensity',
|
||||
'unique_id': '123456789ABC-cury:0-left_slot_intensity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_cury_number_entity[number.test_name_left_slot_intensity-state]
|
||||
@@ -170,7 +170,7 @@
|
||||
<NumberEntityCapabilityAttribute.MIN: 'min'>: 0,
|
||||
<NumberEntityCapabilityAttribute.MODE: 'mode'>: <NumberMode.SLIDER: 'slider'>,
|
||||
<NumberEntityCapabilityAttribute.STEP: 'step'>: 1,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_name_left_slot_intensity',
|
||||
@@ -219,7 +219,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'right_slot_intensity',
|
||||
'unique_id': '123456789ABC-cury:0-right_slot_intensity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_cury_number_entity[number.test_name_right_slot_intensity-state]
|
||||
@@ -230,7 +230,7 @@
|
||||
<NumberEntityCapabilityAttribute.MIN: 'min'>: 0,
|
||||
<NumberEntityCapabilityAttribute.MODE: 'mode'>: <NumberMode.SLIDER: 'slider'>,
|
||||
<NumberEntityCapabilityAttribute.STEP: 'step'>: 1,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_name_right_slot_intensity',
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123456789ABC-blutrv:200-blutrv_battery',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_battery-state]
|
||||
@@ -44,7 +44,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'battery',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'TRV-Name Battery',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.trv_name_battery',
|
||||
@@ -145,7 +145,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'valve_position',
|
||||
'unique_id': '123456789ABC-blutrv:200-valve_position',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_valve_position-state]
|
||||
@@ -153,7 +153,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'TRV-Name Valve position',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.trv_name_valve_position',
|
||||
@@ -199,7 +199,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'left_slot_level',
|
||||
'unique_id': '123456789ABC-cury:0-cury_left_level',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_cury_sensor_entity[sensor.test_name_left_slot_level-state]
|
||||
@@ -207,7 +207,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Test name Left slot level',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_name_left_slot_level',
|
||||
@@ -303,7 +303,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'right_slot_level',
|
||||
'unique_id': '123456789ABC-cury:0-cury_right_level',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_cury_sensor_entity[sensor.test_name_right_slot_level-state]
|
||||
@@ -311,7 +311,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Test name Right slot level',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.test_name_right_slot_level',
|
||||
|
||||
@@ -6,10 +6,12 @@ from unittest.mock import AsyncMock, create_autospec, patch
|
||||
|
||||
from aiohttp import ClientSession
|
||||
from aiovodafone.api import VodafoneStationDevice
|
||||
from aiovodafone.exceptions import VodafoneError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.vodafone_station.const import DOMAIN, SCAN_INTERVAL
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
@@ -69,37 +71,45 @@ async def test_coordinator_device_cleanup(
|
||||
assert f"Removing device: {DEVICE_1_HOST}" in caplog.text
|
||||
|
||||
|
||||
async def test_coordinator_json_decode_error(
|
||||
@pytest.mark.parametrize(
|
||||
("error", "expected_init_calls", "expected_session_calls"),
|
||||
[
|
||||
(VodafoneError("Generic error"), 1, 1),
|
||||
(
|
||||
JSONDecodeError("Invalid JSON", "<html>stale session</html>", 0),
|
||||
2,
|
||||
2,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_coordinator_exceptions(
|
||||
hass: HomeAssistant,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
mock_vodafone_station_router: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
error: Exception,
|
||||
expected_init_calls: int,
|
||||
expected_session_calls: int,
|
||||
) -> None:
|
||||
"""Test stale-session JSON response reinitializes API session."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
mock_vodafone_station_router.get_devices_data.side_effect = JSONDecodeError(
|
||||
"Invalid JSON",
|
||||
"<html>stale session</html>",
|
||||
0,
|
||||
)
|
||||
"""Test exception handling during update: setup retry, plus session reinit for stale sessions."""
|
||||
mock_vodafone_station_router.get_devices_data.side_effect = error
|
||||
|
||||
new_session = create_autospec(ClientSession, instance=True)
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.vodafone_station.coordinator.init_device_class",
|
||||
return_value=mock_vodafone_station_router,
|
||||
) as mock_init_device_class,
|
||||
patch(
|
||||
"homeassistant.components.vodafone_station.coordinator.async_client_session",
|
||||
AsyncMock(return_value=new_session),
|
||||
) as mock_async_client_session,
|
||||
):
|
||||
mock_init_device_class.return_value = mock_vodafone_station_router
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_async_client_session.assert_awaited_once_with(hass)
|
||||
mock_init_device_class.assert_called_once()
|
||||
assert mock_init_device_class.call_args.args[2] == mock_config_entry.data
|
||||
assert mock_init_device_class.call_args.args[3] == new_session
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert mock_init_device_class.call_count == expected_init_calls
|
||||
assert mock_async_client_session.await_count == expected_session_calls
|
||||
assert mock_init_device_class.call_args.args[2] == mock_config_entry.data
|
||||
assert mock_init_device_class.call_args.args[3] == new_session
|
||||
|
||||
Reference in New Issue
Block a user