diff --git a/custom_components/yandex_smart_home/__init__.py b/custom_components/yandex_smart_home/__init__.py
index 0e768a00..e91d9748 100644
--- a/custom_components/yandex_smart_home/__init__.py
+++ b/custom_components/yandex_smart_home/__init__.py
@@ -35,6 +35,7 @@
{
vol.Required(const.CONF_ENTITY_PROPERTY_TYPE): vol.Schema(vol.All(str, ycv.property_type)),
vol.Optional(const.CONF_ENTITY_PROPERTY_UNIT_OF_MEASUREMENT): cv.string,
+ vol.Optional(const.CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(const.CONF_ENTITY_PROPERTY_ENTITY): cv.entity_id,
vol.Optional(const.CONF_ENTITY_PROPERTY_ATTRIBUTE): cv.string,
vol.Optional(const.CONF_ENTITY_PROPERTY_VALUE_TEMPLATE): cv.template,
@@ -137,12 +138,13 @@
)
-SETTINGS_SCHEMA = vol.Schema(
+SETTINGS_SCHEMA = vol.All(
+ cv.deprecated(const.CONF_PRESSURE_UNIT),
{
- vol.Optional(const.CONF_PRESSURE_UNIT): vol.Schema(vol.All(str, ycv.pressure_unit)),
+ vol.Optional(const.CONF_PRESSURE_UNIT): cv.string,
vol.Optional(const.CONF_BETA): cv.boolean,
vol.Optional(const.CONF_CLOUD_STREAM): cv.boolean,
- }
+ },
)
diff --git a/custom_components/yandex_smart_home/config_validation.py b/custom_components/yandex_smart_home/config_validation.py
index d92e2caa..bd12a4e1 100644
--- a/custom_components/yandex_smart_home/config_validation.py
+++ b/custom_components/yandex_smart_home/config_validation.py
@@ -21,7 +21,7 @@
RangeCapabilityInstance,
ToggleCapabilityInstance,
)
-from .unit_conversion import UnitOfPressure
+from .unit_conversion import UnitOfPressure, UnitOfTemperature
if TYPE_CHECKING:
from homeassistant.helpers import ConfigType
@@ -75,6 +75,29 @@ def property_attributes(value: ConfigType) -> ConfigType:
if value_template and (entity or attribute):
raise vol.Invalid("entity/attribute and value_template are mutually exclusive")
+ property_type_value = value.get(const.CONF_ENTITY_PROPERTY_TYPE)
+ target_unit_of_measurement = value.get(const.CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT)
+ if target_unit_of_measurement:
+ try:
+ if property_type_value in [
+ FloatPropertyInstance.TEMPERATURE,
+ f"{PropertyInstanceType.FLOAT}.{FloatPropertyInstance.TEMPERATURE}",
+ ]:
+ assert UnitOfTemperature(target_unit_of_measurement).as_property_unit
+ elif property_type_value in [
+ FloatPropertyInstance.PRESSURE,
+ f"{PropertyInstanceType.FLOAT}.{FloatPropertyInstance.PRESSURE}",
+ ]:
+ assert UnitOfPressure(target_unit_of_measurement).as_property_unit
+ else:
+ raise ValueError
+ except ValueError:
+ raise vol.Invalid(
+ f"Target unit of measurement '{target_unit_of_measurement}' is not supported "
+ f"for {property_type_value} property, see valid values "
+ f"at https://docs.yaha-cloud.ru/master/devices/sensor/float/#property-target-unit-of-measurement"
+ )
+
return value
@@ -163,15 +186,6 @@ def device_type(value: str) -> str:
raise vol.Invalid(f"Device type '{value}' is not supported")
-def pressure_unit(value: str) -> str:
- try:
- UnitOfPressure(value)
- except ValueError:
- raise vol.Invalid(f"Pressure unit {value!r} is not supported")
-
- return value
-
-
def color_name(value: str) -> str:
try:
ColorName(value)
diff --git a/custom_components/yandex_smart_home/const.py b/custom_components/yandex_smart_home/const.py
index 208e635f..bc5c74dd 100644
--- a/custom_components/yandex_smart_home/const.py
+++ b/custom_components/yandex_smart_home/const.py
@@ -35,6 +35,7 @@
CONF_ENTITY_PROPERTY_ATTRIBUTE = "attribute"
CONF_ENTITY_PROPERTY_VALUE_TEMPLATE = "value_template"
CONF_ENTITY_PROPERTY_UNIT_OF_MEASUREMENT = "unit_of_measurement"
+CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT = "target_unit_of_measurement"
CONF_ENTITY_PROPERTIES = "properties"
CONF_ENTITY_RANGE = "range"
CONF_ENTITY_RANGE_MIN = "min"
diff --git a/custom_components/yandex_smart_home/entry_data.py b/custom_components/yandex_smart_home/entry_data.py
index 6ae2920b..b62626df 100644
--- a/custom_components/yandex_smart_home/entry_data.py
+++ b/custom_components/yandex_smart_home/entry_data.py
@@ -5,9 +5,10 @@
from typing import Any, Self, cast
from homeassistant.config_entries import ConfigEntry
-from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP, UnitOfPressure
+from homeassistant.const import EVENT_HOMEASSISTANT_STARTED, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
+from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.entityfilter import EntityFilter
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType
@@ -65,6 +66,19 @@ async def async_setup(self) -> Self:
else:
self._hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, self._async_setup_notifiers)
+ if self._yaml_config.get(const.CONF_SETTINGS, {}).get(const.CONF_PRESSURE_UNIT):
+ ir.async_create_issue(
+ self._hass,
+ DOMAIN,
+ "deprecated_pressure_unit",
+ is_fixable=False,
+ severity=ir.IssueSeverity.WARNING,
+ translation_key="deprecated_pressure_unit",
+ learn_more_url="https://docs.yaha-cloud.ru/master/devices/sensor/float/#unit-conversion",
+ )
+ else:
+ ir.async_delete_issue(self._hass, DOMAIN, "deprecated_pressure_unit")
+
return self
async def async_unload(self) -> None:
@@ -118,11 +132,6 @@ def user_id(self) -> str | None:
"""Return user id for service calls (used only when cloud connection)."""
return self.entry.options.get(const.CONF_USER_ID)
- @property
- def pressure_unit(self) -> str:
- settings = self._yaml_config.get(const.CONF_SETTINGS, {})
- return str(settings.get(const.CONF_PRESSURE_UNIT) or UnitOfPressure.MMHG.value)
-
@property
def color_profiles(self) -> ColorProfiles:
"""Return color profiles."""
diff --git a/custom_components/yandex_smart_home/property_custom.py b/custom_components/yandex_smart_home/property_custom.py
index 5f6459d5..6efe7956 100644
--- a/custom_components/yandex_smart_home/property_custom.py
+++ b/custom_components/yandex_smart_home/property_custom.py
@@ -12,6 +12,7 @@
from .const import (
CONF_ENTITY_PROPERTY_ATTRIBUTE,
CONF_ENTITY_PROPERTY_ENTITY,
+ CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT,
CONF_ENTITY_PROPERTY_TYPE,
CONF_ENTITY_PROPERTY_UNIT_OF_MEASUREMENT,
CONF_ENTITY_PROPERTY_VALUE_TEMPLATE,
@@ -51,6 +52,7 @@
WaterLevelPercentageProperty,
)
from .schema import PropertyType, ResponseCode
+from .unit_conversion import UnitOfPressure, UnitOfTemperature
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
@@ -202,7 +204,13 @@ def _native_unit_of_measurement(self) -> str | None:
@FLOAT_PROPERTIES_REGISTRY.register
class TemperatureCustomFloatProperty(TemperatureProperty, CustomFloatProperty):
- pass
+ @property
+ def unit_of_measurement(self) -> UnitOfTemperature:
+ """Return the unit the property value is expressed in."""
+ if unit := self._config.get(CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT):
+ return UnitOfTemperature(unit)
+
+ return super().unit_of_measurement
@FLOAT_PROPERTIES_REGISTRY.register
@@ -212,7 +220,13 @@ class HumidityCustomFloatProperty(HumidityProperty, CustomFloatProperty):
@FLOAT_PROPERTIES_REGISTRY.register
class PressureCustomFloatProperty(PressureProperty, CustomFloatProperty):
- pass
+ @property
+ def unit_of_measurement(self) -> UnitOfPressure:
+ """Return the unit the property value is expressed in."""
+ if unit := self._config.get(CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT):
+ return UnitOfPressure(unit)
+
+ return super().unit_of_measurement
@FLOAT_PROPERTIES_REGISTRY.register
diff --git a/custom_components/yandex_smart_home/property_float.py b/custom_components/yandex_smart_home/property_float.py
index 742500f4..369562dd 100644
--- a/custom_components/yandex_smart_home/property_float.py
+++ b/custom_components/yandex_smart_home/property_float.py
@@ -1,5 +1,6 @@
"""Implement the Yandex Smart Home float properties."""
from abc import ABC, abstractmethod
+from contextlib import suppress
from typing import Protocol, Self
from homeassistant.components import air_quality, climate, fan, humidifier, light, sensor, switch, water_heater
@@ -16,8 +17,8 @@
UnitOfElectricCurrent,
UnitOfElectricPotential,
UnitOfPower,
- UnitOfTemperature,
)
+from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
ElectricCurrentConverter,
@@ -48,12 +49,11 @@
PropertyType,
ResponseCode,
TemperatureFloatPropertyParameters,
- TemperatureUnit,
TVOCFloatPropertyParameters,
VoltageFloatPropertyParameters,
WaterLevelFloatPropertyParameters,
)
-from .unit_conversion import PressureConverter, TVOCConcentrationConverter, UnitOfPressure
+from .unit_conversion import PressureConverter, TVOCConcentrationConverter, UnitOfPressure, UnitOfTemperature
class FloatProperty(Property, Protocol):
@@ -90,9 +90,16 @@ def get_value(self) -> float | None:
raise APIError(ResponseCode.NOT_SUPPORTED_IN_CURRENT_MODE, f"Unsupported value '{value}' for {self}")
if self._native_unit_of_measurement and self.unit_of_measurement and self._unit_converter:
- float_value = self._unit_converter.convert(
- float_value, self._native_unit_of_measurement, self.unit_of_measurement
- )
+ try:
+ float_value = self._unit_converter.convert(
+ float_value, self._native_unit_of_measurement, self.unit_of_measurement
+ )
+ except HomeAssistantError as e:
+ raise APIError(
+ ResponseCode.INVALID_VALUE,
+ f"Failed to convert value from '{self._native_unit_of_measurement}' to "
+ f"'{self.unit_of_measurement}' for {self}: {e}",
+ )
lower_limit, upper_limit = self.parameters.range
if lower_limit is not None and float_value < lower_limit:
@@ -145,11 +152,17 @@ class TemperatureProperty(FloatProperty, ABC):
@property
def parameters(self) -> TemperatureFloatPropertyParameters:
"""Return parameters for a devices list request."""
- return TemperatureFloatPropertyParameters(unit=TemperatureUnit.CELSIUS)
+ return TemperatureFloatPropertyParameters(unit=self.unit_of_measurement.as_property_unit)
@property
- def unit_of_measurement(self) -> str:
+ def unit_of_measurement(self) -> UnitOfTemperature:
"""Return the unit the property value is expressed in."""
+ if self._native_unit_of_measurement:
+ with suppress(ValueError):
+ unit = UnitOfTemperature(self._native_unit_of_measurement)
+ if unit.as_property_unit:
+ return unit
+
return UnitOfTemperature.CELSIUS
@property
@@ -182,7 +195,13 @@ def parameters(self) -> PressureFloatPropertyParameters:
@property
def unit_of_measurement(self) -> UnitOfPressure:
"""Return the unit the property value is expressed in."""
- return UnitOfPressure(self._entry_data.pressure_unit)
+ if self._native_unit_of_measurement:
+ with suppress(ValueError):
+ unit = UnitOfPressure(self._native_unit_of_measurement)
+ if unit.as_property_unit:
+ return unit
+
+ return UnitOfPressure.MMHG
@property
def _unit_converter(self) -> PressureConverter:
@@ -442,7 +461,7 @@ def _get_native_value(self) -> float | str | None:
@property
def _native_unit_of_measurement(self) -> str:
"""Return the unit the native value is expressed in."""
- return str(self.state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, self.unit_of_measurement))
+ return str(self.state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, UnitOfPressure.MMHG))
@STATE_PROPERTIES_REGISTRY.register
@@ -620,7 +639,7 @@ def _native_unit_of_measurement(self) -> str | None:
if self.state.domain == sensor.DOMAIN:
return str(self.state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, self.unit_of_measurement))
- return self.unit_of_measurement
+ return None
@STATE_PROPERTIES_REGISTRY.register
@@ -646,12 +665,12 @@ def _get_native_value(self) -> float | str | None:
return self.state.attributes.get(const.ATTR_CURRENT)
@property
- def _native_unit_of_measurement(self) -> str:
+ def _native_unit_of_measurement(self) -> str | None:
"""Return the unit the native value is expressed in."""
if self.state.domain == sensor.DOMAIN:
return str(self.state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, self.unit_of_measurement))
- return self.unit_of_measurement
+ return None
@STATE_PROPERTIES_REGISTRY.register
@@ -681,12 +700,12 @@ def _get_native_value(self) -> float | str | None:
return self.state.state
@property
- def _native_unit_of_measurement(self) -> str:
+ def _native_unit_of_measurement(self) -> str | None:
"""Return the unit the native value is expressed in."""
if self.state.domain == sensor.DOMAIN:
return str(self.state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, self.unit_of_measurement))
- return self.unit_of_measurement
+ return None
@STATE_PROPERTIES_REGISTRY.register
diff --git a/custom_components/yandex_smart_home/translations/en.json b/custom_components/yandex_smart_home/translations/en.json
index 96f97ba5..7ac43897 100644
--- a/custom_components/yandex_smart_home/translations/en.json
+++ b/custom_components/yandex_smart_home/translations/en.json
@@ -75,5 +75,11 @@
"description": "Реквизиты для привязки Home Assistant к УДЯ:\n* ID: `{id}`\n* Пароль: `{password}`\n\nТеперь вы можете добавить Home Assistant в Умный дом Яндекса, для этого:\n * Откройте [квазар](https://yandex.ru/quasar/iot) или приложение [Дом с Алисой](https://mobile.yandex.ru/apps/smarthome)\n* Нажмите кнопку \"+\" в правом верхнем углу, выберите \"Устройство умного дома\"\n* Найдите в списке и выберите производителя \"Yaha Cloud\"\n* Нажмите кнопку \"Привязать к Яндексу\", откроется страница авторизации\n* Выполните привязку используя реквизиты выше"
}
}
+ },
+ "issues": {
+ "deprecated_pressure_unit": {
+ "description": "Параметр `pressure_unit` (раздел `settings`) больше не поддерживается, удалите его из YAML конфигурации.\n\nТеперь компонент автоматически пытается сохранить единицы измерения при передаче значений датчиков из Home Assistant в УДЯ ([подробнее о конвертации значений](https://docs.yaha-cloud.ru/master/devices/sensor/float/#unit-conversion))",
+ "title": "Устаревший параметр pressure_unit"
+ }
}
}
diff --git a/custom_components/yandex_smart_home/unit_conversion.py b/custom_components/yandex_smart_home/unit_conversion.py
index 1d093549..55f7b367 100644
--- a/custom_components/yandex_smart_home/unit_conversion.py
+++ b/custom_components/yandex_smart_home/unit_conversion.py
@@ -8,6 +8,8 @@
CONCENTRATION_PARTS_PER_BILLION,
CONCENTRATION_PARTS_PER_MILLION,
)
+
+# noinspection PyProtectedMember
from homeassistant.util.unit_conversion import (
_IN_TO_M,
_MERCURY_DENSITY,
@@ -16,7 +18,7 @@
BaseUnitConverter,
)
-from .schema import PressureUnit
+from .schema import PressureUnit, TemperatureUnit
class TVOCConcentrationConverter(BaseUnitConverter):
@@ -42,6 +44,25 @@ class TVOCConcentrationConverter(BaseUnitConverter):
}
+class UnitOfTemperature(StrEnum):
+ """Temperature units."""
+
+ CELSIUS = "°C"
+ FAHRENHEIT = "°F"
+ KELVIN = "K"
+
+ @property
+ def as_property_unit(self) -> TemperatureUnit:
+ """Return value as property unit."""
+ match self:
+ case self.CELSIUS:
+ return TemperatureUnit.CELSIUS
+ case self.KELVIN:
+ return TemperatureUnit.KELVIN
+
+ raise ValueError
+
+
class UnitOfPressure(StrEnum):
"""Extended pressure units."""
diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md
index ba3b8557..ea52f8ae 100644
--- a/docs/breaking-changes.md
+++ b/docs/breaking-changes.md
@@ -1,5 +1,5 @@
## Переход с 0.x на 1.x
-### Состояние для custom_toggles
+### Состояние для custom_toggles { id=v1-custom-toggles }
Пользовательские умения типа "Переключатели" теперь ожидают бинарные значения (`on/off/yes/no/True/False/1/0`) при определении своего состояния.
До версии 1.0 умение считалось включенным, когда состояние отличалось от `off`.
@@ -23,3 +23,8 @@
state_template: '{{ not is_state("select.humidifier_led_brightness", "off") }}'
```
+
+### Параметр pressure_unit { id=v1-pressure-unit }
+Параметр `pressure_unit` (раздел `settings`) больше не поддерживается, удалите его из YAML конфигурации.
+
+Теперь компонент автоматически пытается сохранить единицы измерения при передаче значений датчиков из Home Assistant в УДЯ ([подробнее о конвертации значений](devices/sensor/float.md#unit-conversion))
diff --git a/docs/devices/sensor/float.md b/docs/devices/sensor/float.md
index 29c8a2fb..8061d7f5 100644
--- a/docs/devices/sensor/float.md
+++ b/docs/devices/sensor/float.md
@@ -73,3 +73,89 @@
entity: switch.humidifer_socket
attribute: current_power
```
+
+### Единицы измерения в HA { id=property-unit-of-measurement }
+> Параметр: `unit_of_measurement`
+
+Задаёт единицы измерения, в котором находится значение датчика в Home Assistant. Следует задавать, **только** если автоматическая [конвертация значений](#unit-conversion) работает неверно. В большинстве случаев использовать этот параметр не требуется.
+
+Альтернативные способы задать единицы измерения:
+
+1. Параметр `device_class` при создании датчика на [шаблоне](https://www.home-assistant.io/integrations/template/#sensor)
+2. Настройки объекта на странице `Настройки` --> `Устройства и службы` --> [`Объекты`](https://my.home-assistant.io/redirect/entities/)
+
+!!! example "Пример"
+ ```yaml
+ yandex_smart_home:
+ entity_config:
+ humidifier.room:
+ properties:
+ - type: temperature
+ attribute: current_temperature
+ unit_of_measurement: °F
+ ```
+
+Возможные значения `unit_of_measurement` (регистр важен):
+
+| Тип | `unit_of_measurement` |
+|-------------|-------------------------------------------------------------------------|
+| amperage | `A`, `mA` |
+| power | `W`, `kW` |
+| pressure | `atm`, `Pa`, `hPa`, `kPa`, `bar`, `cbar`, `mbar`, `mmHg`, `inHg`, `psi` |
+| temperature | `°C`, `°F`, `K` |
+| tvoc | `µg/m³`, `mg/m³`, `μg/ft³`, `p/m³`, `ppm`, `ppb` |
+| voltage | `V`, `mV` |
+
+
+### Единицы измерения в УДЯ { id=property-target-unit-of-measurement }
+> Параметр: `target_unit_of_measurement`
+
+Задаёт единицы измерения, в которых значение датчика должно быть представлено в УДЯ. Поддерживается только для температуры и давления.
+
+Следует использовать в тех **редких** случаях, когда вам хочется, чтобы единицы измерения не совпадали между HA и УДЯ. Например: в HA давление в паскалях, а в УДЯ нужно в мм. рт. ст.
+
+!!! info "Пример"
+ ```yaml
+ yandex_smart_home:
+ entity_config:
+ sensor.temperature:
+ properties:
+ - type: temperature
+ entity: sensor.temperature
+ target_unit_of_measurement: °С # поддерживаются: °С и K
+ - type: pressure
+ entity_sensor.pressure
+ target_unit_of_measurement: mmHg # поддерживаются: mmHg, Pa, atm, bar
+ ```
+
+## Конвертация значений { id=unit-conversion }
+Единицы измерения значения датчика в УДЯ фиксированы и не могут быть произвольными:
+
+| Тип | Единицы измерения |
+|----------------------------------------------|------------------------------------------------|
+| amperage | Всегда амперы |
+| battery_level | Всегда проценты |
+| co2_level | Всегда миллионые доли (ppm) |
+| food_level | Всегда проценты |
+| humidity | Всегда проценты |
+| illumination | Всегда люксы |
+| pm1_density
pm2.5_density
pm10_density | Всегда мкг/м³ |
+| power | Всегда ватты |
+| pressure | На выбор: атмосферы, паскали, бары, мм рт. ст. |
+| temperature | На выбор: градусы по цельсию, кельвины |
+| tvoc | Всегда мкг/м³ |
+| voltage | Всегда вольты |
+| water_level | Всегда проценты |
+
+Компонент автоматически выполняет конвертацию значений из единиц измерения в HA в единицы измерения в УДЯ.
+
+Если в HA используется значение в единцах, поддерживаемых УДЯ - конвертация выполнена не будет (например если в HA давление в барах, то и в УДЯ оно будет передано в барах).
+
+### Изменение единиц измерения { id=select-unit-of-measurement }
+В некоторых случаях компонент не может сам определить, в каких единицах измерения находится значение датчика. В этом случае значение может быть сконвертировано неверно.
+
+Несколько способов исправить эту ситуацию:
+
+1. Задать верные единицы измерения через `device_class` при создании [датчика на шаблоне](https://www.home-assistant.io/integrations/template/#configuration-variables)
+2. Задать верные единицы измерения в настройках объекта на странице `Настройки` --> `Устройства и службы` --> [`Объекты`](https://my.home-assistant.io/redirect/entities/)
+3. Использовать параметр [`unit_of_measurement`](#property-unit-of-measurement) при ручной настройке датчика
diff --git a/docs/supported-devices.md b/docs/supported-devices.md
index 45bac65d..bf5066cc 100644
--- a/docs/supported-devices.md
+++ b/docs/supported-devices.md
@@ -113,7 +113,7 @@
| `device_tracker` | |
| `lawn_mower` | Поддержка запланирована |
| `notify` | |
-| `number`, `input_number` | Можно привязать к устройству через [пользовательские умения](advanced/capabilties.md) |
-| `remote` | Можно задействовать в [пользовательском умении](advanced/capabilties.md) |
+| `number`, `input_number` | Можно привязать к устройству через [пользовательские умения](advanced/capabilities.md) |
+| `remote` | Можно задействовать в [пользовательском умении](advanced/capabilities.md) |
| `select`, `input_select` | Нет ясности что делать при выборе разных значений
Можно задействовать в [пользовательском умении](advanced/capabilities.md) |
| `siren` | Поддержка запланирована |
diff --git a/tests/fixtures/valid-config.yaml b/tests/fixtures/valid-config.yaml
index 5d703d20..4b827e15 100644
--- a/tests/fixtures/valid-config.yaml
+++ b/tests/fixtures/valid-config.yaml
@@ -4,7 +4,6 @@ yandex_smart_home:
skill_id: d38d4c39-5846-ba53-67acc27e08bc
user_id: e8701ad48ba05a91604e480dd60899a3
settings:
- pressure_unit: mmHg
beta: true
color_profile:
test:
@@ -180,3 +179,13 @@ yandex_smart_home:
custom_modes:
input_source:
state_template: buz
+ sensor.sun:
+ properties:
+ - type: temperature
+ value_template: '{{ 15000000 }}'
+ unit_of_measurement: °C
+ target_unit_of_measurement: K
+ - type: pressure
+ value_template: '{{ 0 }}'
+ unit_of_measurement: mmHg
+ target_unit_of_measurement: bar
diff --git a/tests/test_config_validation.py b/tests/test_config_validation.py
index 6de318a8..5b89cea0 100644
--- a/tests/test_config_validation.py
+++ b/tests/test_config_validation.py
@@ -67,6 +67,68 @@ async def test_invalid_float_property_type(hass, caplog):
) in caplog.messages[-1]
+async def test_invalid_property_target_unit_of_measurement(hass, caplog):
+ files = {
+ YAML_CONFIG_FILE: """
+yandex_smart_home:
+ entity_config:
+ sensor.test:
+ properties:
+ - type: battery_level
+ entity: sensor.test
+ target_unit_of_measurement: foo
+"""
+ }
+ with patch_yaml_files(files):
+ assert await async_integration_yaml_config(hass, DOMAIN) is None
+
+ assert (
+ "Target unit of measurement 'foo' is not supported for battery_level property, see valid values at "
+ "https://docs.yaha-cloud.ru/master/devices/sensor/float/#property-target-unit-of-measurement"
+ ) in caplog.messages[-1]
+
+ for type_prefix in ["", "float."]:
+ files = {
+ YAML_CONFIG_FILE: f"""
+ yandex_smart_home:
+ entity_config:
+ sensor.test:
+ properties:
+ - type: {type_prefix}temperature
+ entity: sensor.test
+ target_unit_of_measurement: °F
+ """
+ }
+ with patch_yaml_files(files):
+ assert await async_integration_yaml_config(hass, DOMAIN) is None
+
+ assert (
+ f"Target unit of measurement '°F' is not supported for {type_prefix}temperature property, "
+ f"see valid values "
+ f"at https://docs.yaha-cloud.ru/master/devices/sensor/float/#property-target-unit-of-measurement"
+ ) in caplog.messages[-1]
+
+ files = {
+ YAML_CONFIG_FILE: f"""
+ yandex_smart_home:
+ entity_config:
+ sensor.test:
+ properties:
+ - type: {type_prefix}pressure
+ entity: sensor.test
+ target_unit_of_measurement: psi
+ """
+ }
+ with patch_yaml_files(files):
+ assert await async_integration_yaml_config(hass, DOMAIN) is None
+
+ assert (
+ f"Target unit of measurement 'psi' is not supported for {type_prefix}pressure property, "
+ f"see valid values "
+ f"at https://docs.yaha-cloud.ru/master/devices/sensor/float/#property-target-unit-of-measurement"
+ ) in caplog.messages[-1]
+
+
async def test_invalid_property(hass, caplog):
files = {
YAML_CONFIG_FILE: """
@@ -220,18 +282,6 @@ async def test_invalid_device_type(hass, caplog):
assert "Device type 'unsupported' is not supported" in caplog.messages[-1]
-async def test_invalid_pressure_unit(hass):
- files = {
- YAML_CONFIG_FILE: """
-yandex_smart_home:
- settings:
- pressure_unit: invalid
-"""
- }
- with patch_yaml_files(files):
- assert await async_integration_yaml_config(hass, DOMAIN) is None
-
-
async def test_invalid_color_name(hass, caplog):
files = {
YAML_CONFIG_FILE: """
diff --git a/tests/test_entry_data.py b/tests/test_entry_data.py
index be57d5d0..57cafe83 100644
--- a/tests/test_entry_data.py
+++ b/tests/test_entry_data.py
@@ -1,8 +1,9 @@
from unittest.mock import patch
+from homeassistant.helpers import issue_registry as ir
from pytest_homeassistant_custom_component.common import MockConfigEntry
-from custom_components.yandex_smart_home import const
+from custom_components.yandex_smart_home import DOMAIN, YandexSmartHome, const
from custom_components.yandex_smart_home.entry_data import ConfigEntryData
from custom_components.yandex_smart_home.helpers import APIError
from custom_components.yandex_smart_home.schema import ResponseCode
@@ -34,3 +35,24 @@ def test_entry_data_trackable_states(hass, caplog):
):
assert entry_data._get_trackable_states() == {}
assert caplog.messages == ["Failed to track custom capability: foo"]
+
+
+async def test_deprecated_pressure_unit(hass, config_entry_direct):
+ issue_registry = ir.async_get(hass)
+
+ config_entry_direct.add_to_hass(hass)
+ await hass.config_entries.async_setup(config_entry_direct.entry_id)
+ await hass.async_block_till_done()
+ assert issue_registry.async_get_issue(DOMAIN, "deprecated_pressure_unit") is None
+ await hass.config_entries.async_unload(config_entry_direct.entry_id)
+
+ component: YandexSmartHome = hass.data[DOMAIN]
+ component._yaml_config = {const.CONF_SETTINGS: {const.CONF_PRESSURE_UNIT: "foo"}}
+ await hass.config_entries.async_setup(config_entry_direct.entry_id)
+ assert issue_registry.async_get_issue(DOMAIN, "deprecated_pressure_unit") is not None
+ await hass.config_entries.async_unload(config_entry_direct.entry_id)
+
+ component._yaml_config = {const.CONF_SETTINGS: {}}
+ await hass.config_entries.async_setup(config_entry_direct.entry_id)
+ assert issue_registry.async_get_issue(DOMAIN, "deprecated_pressure_unit") is None
+ await hass.config_entries.async_unload(config_entry_direct.entry_id)
diff --git a/tests/test_init.py b/tests/test_init.py
index 53a55cae..756f2a36 100644
--- a/tests/test_init.py
+++ b/tests/test_init.py
@@ -35,7 +35,7 @@ async def test_valid_config(hass):
"user_id": "e8701ad48ba05a91604e480dd60899a3",
}
]
- assert config[DOMAIN]["settings"] == {"pressure_unit": "mmHg", "beta": True}
+ assert config[DOMAIN]["settings"] == {"beta": True}
assert config[DOMAIN]["color_profile"] == {"test": {"red": 16711680, "green": 65280, "warm_white": 3000}}
assert config[DOMAIN]["filter"] == {
"include_domains": ["switch", "light", "climate"],
@@ -47,7 +47,7 @@ async def test_valid_config(hass):
}
entity_config = config[DOMAIN]["entity_config"]
- assert len(entity_config) == 15
+ assert len(entity_config) == 16
assert entity_config["switch.kitchen"] == {
"name": "Выключатель",
@@ -184,6 +184,23 @@ async def test_valid_config(hass):
"custom_toggles": {"backlight": {"state_template": Template("bar", hass)}},
}
+ assert entity_config["sensor.sun"] == {
+ "properties": [
+ {
+ "target_unit_of_measurement": "K",
+ "type": "temperature",
+ "unit_of_measurement": "°C",
+ "value_template": Template("{{ 15000000 }}", hass),
+ },
+ {
+ "target_unit_of_measurement": "bar",
+ "type": "pressure",
+ "unit_of_measurement": "mmHg",
+ "value_template": Template("{{ 0 }}", hass),
+ },
+ ]
+ }
+
async def test_empty_dict_config(hass):
files = {
diff --git a/tests/test_property_custom.py b/tests/test_property_custom.py
index 0a34d14a..29898926 100644
--- a/tests/test_property_custom.py
+++ b/tests/test_property_custom.py
@@ -276,20 +276,29 @@ async def test_property_custom_value_float_limit(hass):
@pytest.mark.parametrize(
- "instance,unit_of_measurement,unit,value",
+ "instance,unit_of_measurement,unit,fallback_unit,assert_value",
[
- ("pressure", "bar", "unit.pressure.mmhg", 75006.16),
- ("tvoc", "ppb", "unit.density.mcg_m3", 449.63),
- ("amperage", "mA", "unit.ampere", 0.1),
- ("voltage", "mV", "unit.volt", 0.1),
- ("temperature", "K", "unit.temperature.celsius", -173.15),
- ("humidity", "x", "unit.percent", 100),
+ ("pressure", "bar", "unit.pressure.bar", "unit.pressure.mmhg", None),
+ ("pressure", "Pa", "unit.pressure.pascal", "unit.pressure.mmhg", None),
+ ("pressure", "mmHg", "unit.pressure.mmhg", None, None),
+ ("pressure", "atm", "unit.pressure.atm", "unit.pressure.mmhg", None),
+ ("pressure", "psi", "unit.pressure.mmhg", None, 5171.49),
+ ("tvoc", "ppb", "unit.density.mcg_m3", None, 449.63),
+ ("amperage", "mA", "unit.ampere", None, 0.1),
+ ("voltage", "mV", "unit.volt", None, 0.1),
+ ("temperature", "°F", "unit.temperature.celsius", None, 37.78),
+ ("temperature", "°C", "unit.temperature.celsius", None, None),
+ ("temperature", "K", "unit.temperature.kelvin", "unit.temperature.celsius", None),
+ ("humidity", "x", "unit.percent", None, None),
],
)
-async def test_property_custom_get_value_float_conversion(hass, instance, unit_of_measurement, unit, value):
- state = State("sensor.test", "100")
+async def test_property_custom_get_value_float_conversion(
+ hass, instance, unit_of_measurement, unit, fallback_unit, assert_value
+):
+ value = 100
+ state = State("sensor.test", str(value))
hass.states.async_set(state.entity_id, state.state)
- hass.states.async_set("climate.test", STATE_ON, {ATTR_UNIT_OF_MEASUREMENT: unit_of_measurement, "t": 100})
+ hass.states.async_set("climate.test", STATE_ON, {ATTR_UNIT_OF_MEASUREMENT: unit_of_measurement, "t": str(value)})
prop = get_custom_property(
hass,
@@ -301,7 +310,7 @@ async def test_property_custom_get_value_float_conversion(hass, instance, unit_o
state.entity_id,
)
assert prop.parameters.dict()["unit"] == unit
- assert prop.get_value() == value
+ assert prop.get_value() == (value if assert_value is None else assert_value)
hass.states.async_set(state.entity_id, STATE_UNAVAILABLE)
assert prop.get_value() is None
@@ -310,7 +319,7 @@ async def test_property_custom_get_value_float_conversion(hass, instance, unit_o
hass.states.async_set(state.entity_id, state.state, state.attributes)
prop = get_custom_property(hass, BASIC_ENTRY_DATA, {const.CONF_ENTITY_PROPERTY_TYPE: instance}, state.entity_id)
assert prop.parameters.dict()["unit"] == unit
- assert prop.get_value() == value
+ assert prop.get_value() == (value if assert_value is None else assert_value)
# ignore unit_of_measurement when use attribute
prop = get_custom_property(
@@ -323,8 +332,8 @@ async def test_property_custom_get_value_float_conversion(hass, instance, unit_o
},
state.entity_id,
)
- assert prop.parameters.dict()["unit"] == unit
- assert prop.get_value() == 100
+ assert prop.parameters.dict()["unit"] == (unit if fallback_unit is None else fallback_unit)
+ assert prop.get_value() == value
# override unit_of_measurement when use attribute
prop = get_custom_property(
@@ -339,4 +348,38 @@ async def test_property_custom_get_value_float_conversion(hass, instance, unit_o
state.entity_id,
)
assert prop.parameters.dict()["unit"] == unit
- assert prop.get_value() == value
+ assert prop.get_value() == (value if assert_value is None else assert_value)
+
+
+@pytest.mark.parametrize(
+ "instance,unit_of_measurement,target_unit_of_measurement,target_unit,assert_value",
+ [
+ ("pressure", "bar", "Pa", "unit.pressure.pascal", 10000000.0),
+ ("pressure", "bar", "mmHg", "unit.pressure.mmhg", 75006.16),
+ ("pressure", "bar", "atm", "unit.pressure.atm", 98.69),
+ ("pressure", "atm", "bar", "unit.pressure.bar", 101.33),
+ ("temperature", "°F", "K", "unit.temperature.kelvin", 310.93),
+ ("temperature", "°C", "K", "unit.temperature.kelvin", 373.15),
+ ("temperature", "K", "°C", "unit.temperature.celsius", -173.15),
+ ],
+)
+async def test_property_custom_get_value_float_conversion_override_target_unit(
+ hass, instance, unit_of_measurement, target_unit_of_measurement, target_unit, assert_value
+):
+ value = 100
+ state = State("sensor.test", str(value), {ATTR_UNIT_OF_MEASUREMENT: unit_of_measurement})
+ hass.states.async_set(state.entity_id, state.state, state.attributes)
+
+ prop = get_custom_property(
+ hass,
+ BASIC_ENTRY_DATA,
+ {
+ const.CONF_ENTITY_PROPERTY_TYPE: instance,
+ const.CONF_ENTITY_PROPERTY_ENTITY: state.entity_id,
+ const.CONF_ENTITY_PROPERTY_TARGET_UNIT_OF_MEASUREMENT: target_unit_of_measurement,
+ },
+ state.entity_id,
+ )
+
+ assert prop.parameters.dict()["unit"] == target_unit
+ assert prop.get_value() == assert_value
diff --git a/tests/test_property_float.py b/tests/test_property_float.py
index 5f46f2fa..26899249 100644
--- a/tests/test_property_float.py
+++ b/tests/test_property_float.py
@@ -24,12 +24,12 @@
from homeassistant.exceptions import HomeAssistantError
import pytest
-from custom_components.yandex_smart_home import const
+from custom_components.yandex_smart_home.helpers import APIError
from custom_components.yandex_smart_home.property_float import PropertyType
-from custom_components.yandex_smart_home.schema import FloatPropertyInstance
+from custom_components.yandex_smart_home.schema import FloatPropertyInstance, ResponseCode
from custom_components.yandex_smart_home.unit_conversion import UnitOfPressure
-from . import BASIC_ENTRY_DATA, MockConfigEntryData
+from . import BASIC_ENTRY_DATA
from .test_property import assert_no_properties, get_exact_one_property
@@ -156,6 +156,20 @@ async def test_property_float_temperature_convertion(hass):
assert prop.parameters == {"instance": "temperature", "unit": "unit.temperature.celsius"}
assert prop.get_value() == 34.76
+ state = State(
+ "sensor.test",
+ "34.756",
+ {
+ ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
+ ATTR_UNIT_OF_MEASUREMENT: UnitOfTemperature.KELVIN,
+ },
+ )
+ prop = get_exact_one_property(hass, BASIC_ENTRY_DATA, state, PropertyType.FLOAT, FloatPropertyInstance.TEMPERATURE)
+
+ assert prop.retrievable is True
+ assert prop.parameters == {"instance": "temperature", "unit": "unit.temperature.kelvin"}
+ assert prop.get_value() == 34.76
+
state = State(
"sensor.test",
"50.10",
@@ -170,84 +184,57 @@ async def test_property_float_temperature_convertion(hass):
assert prop.parameters == {"instance": "temperature", "unit": "unit.temperature.celsius"}
assert prop.get_value() == 10.06
-
-@pytest.mark.parametrize("device_class", [SensorDeviceClass.PRESSURE, SensorDeviceClass.ATMOSPHERIC_PRESSURE])
-@pytest.mark.parametrize(
- "unit_of_measurement,property_unit,v",
- [
- (UnitOfPressure.PA, "unit.pressure.pascal", 98658.57),
- (UnitOfPressure.MMHG, "unit.pressure.mmhg", 740),
- (UnitOfPressure.ATM, "unit.pressure.atm", 0.97),
- (UnitOfPressure.BAR, "unit.pressure.bar", 0.99),
- ],
-)
-def test_property_float_pressure_from_mmhg(
- hass, config_entry_direct, device_class, unit_of_measurement, property_unit, v
-):
- entry_data = MockConfigEntryData(
- entry=config_entry_direct, yaml_config={const.CONF_SETTINGS: {const.CONF_PRESSURE_UNIT: unit_of_measurement}}
- )
state = State(
"sensor.test",
- "740",
- {ATTR_DEVICE_CLASS: device_class, ATTR_UNIT_OF_MEASUREMENT: UnitOfPressure.MMHG},
+ "50.10",
+ {
+ ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE,
+ ATTR_UNIT_OF_MEASUREMENT: "foo",
+ },
)
- prop = get_exact_one_property(hass, entry_data, state, PropertyType.FLOAT, FloatPropertyInstance.PRESSURE)
- assert prop.retrievable is True
- assert prop.parameters == {"instance": "pressure", "unit": property_unit}
- assert prop.get_value() == v
+ prop = get_exact_one_property(hass, BASIC_ENTRY_DATA, state, PropertyType.FLOAT, FloatPropertyInstance.TEMPERATURE)
- prop.state = State(
- "sensor.test",
- "-5",
- {ATTR_DEVICE_CLASS: device_class, ATTR_UNIT_OF_MEASUREMENT: UnitOfPressure.MMHG},
+ assert prop.retrievable is True
+ assert prop.parameters == {"instance": "temperature", "unit": "unit.temperature.celsius"}
+ with pytest.raises(APIError) as e:
+ prop.get_value()
+ assert e.value.code == ResponseCode.INVALID_VALUE
+ assert e.value.message == (
+ "Failed to convert value from 'foo' to '°C' for instance temperature of float property of sensor.test: "
+ "foo is not a recognized temperature unit."
)
- assert prop.get_value() == 0
@pytest.mark.parametrize("device_class", [SensorDeviceClass.PRESSURE, SensorDeviceClass.ATMOSPHERIC_PRESSURE])
@pytest.mark.parametrize(
- "unit_of_measurement,property_unit,v",
+ "unit_of_measurement,property_unit,assert_value",
[
- (UnitOfPressure.PA, "unit.pressure.pascal", 106868.73),
- (UnitOfPressure.MMHG, "unit.pressure.mmhg", 801.58),
- (UnitOfPressure.ATM, "unit.pressure.atm", 1.05),
- (UnitOfPressure.BAR, "unit.pressure.bar", 1.07),
+ (None, "unit.pressure.mmhg", None),
+ (UnitOfPressure.PA, "unit.pressure.pascal", None),
+ (UnitOfPressure.MMHG, "unit.pressure.mmhg", None),
+ (UnitOfPressure.ATM, "unit.pressure.atm", None),
+ (UnitOfPressure.BAR, "unit.pressure.bar", None),
+ (UnitOfPressure.PSI, "unit.pressure.mmhg", 38294.9),
],
)
-def test_property_float_pressure_from_psi(
- hass, config_entry_direct, device_class, unit_of_measurement, property_unit, v
-):
- entry_data = MockConfigEntryData(
- entry=config_entry_direct, yaml_config={const.CONF_SETTINGS: {const.CONF_PRESSURE_UNIT: unit_of_measurement}}
- )
- state = State(
- "sensor.test",
- "15.5",
- {ATTR_DEVICE_CLASS: device_class, ATTR_UNIT_OF_MEASUREMENT: UnitOfPressure.PSI},
- )
- prop = get_exact_one_property(hass, entry_data, state, PropertyType.FLOAT, FloatPropertyInstance.PRESSURE)
+def test_property_float_pressure(hass, device_class, unit_of_measurement, property_unit, assert_value):
+ value = 740.5
+ attributes = {ATTR_DEVICE_CLASS: device_class}
+ if unit_of_measurement:
+ attributes[ATTR_UNIT_OF_MEASUREMENT] = unit_of_measurement
+
+ state = State("sensor.test", str(value), attributes)
+ prop = get_exact_one_property(hass, BASIC_ENTRY_DATA, state, PropertyType.FLOAT, FloatPropertyInstance.PRESSURE)
assert prop.retrievable is True
assert prop.parameters == {"instance": "pressure", "unit": property_unit}
- assert prop.get_value() == v
+ if assert_value:
+ assert prop.get_value() == assert_value
+ else:
+ assert prop.get_value() == value
-def test_property_float_pressure_unsupported_target(hass, config_entry_direct):
- entry_data = MockConfigEntryData(
- entry=config_entry_direct, yaml_config={const.CONF_SETTINGS: {const.CONF_PRESSURE_UNIT: "kPa"}}
- )
- state = State(
- "sensor.test",
- "15.5",
- {ATTR_DEVICE_CLASS: SensorDeviceClass.PRESSURE, ATTR_UNIT_OF_MEASUREMENT: UnitOfPressure.PSI},
- )
- prop = get_exact_one_property(hass, entry_data, state, PropertyType.FLOAT, FloatPropertyInstance.PRESSURE)
- assert prop.retrievable is True
- with pytest.raises(ValueError):
- assert prop.parameters
-
- with pytest.raises(ValueError):
- prop.get_value()
+ prop.state = State("sensor.test", "-5", attributes)
+ assert prop.get_value() == 0
@pytest.mark.parametrize(