mirror of
https://github.com/home-assistant/core.git
synced 2025-08-08 15:15:09 +02:00
Merge branch 'dev' into AddClimate_MideaCCM15
This commit is contained in:
@@ -637,7 +637,7 @@ class BluetoothManager:
|
|||||||
else:
|
else:
|
||||||
# We could write out every item in the typed dict here
|
# We could write out every item in the typed dict here
|
||||||
# but that would be a bit inefficient and verbose.
|
# but that would be a bit inefficient and verbose.
|
||||||
callback_matcher.update(matcher) # type: ignore[typeddict-item]
|
callback_matcher.update(matcher)
|
||||||
callback_matcher[CONNECTABLE] = matcher.get(CONNECTABLE, True)
|
callback_matcher[CONNECTABLE] = matcher.get(CONNECTABLE, True)
|
||||||
|
|
||||||
connectable = callback_matcher[CONNECTABLE]
|
connectable = callback_matcher[CONNECTABLE]
|
||||||
|
@@ -45,6 +45,7 @@ class BroadlinkLight(BroadlinkEntity, LightEntity):
|
|||||||
"""Representation of a Broadlink light."""
|
"""Representation of a Broadlink light."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(self, device):
|
def __init__(self, device):
|
||||||
"""Initialize the light."""
|
"""Initialize the light."""
|
||||||
|
@@ -217,6 +217,9 @@ def async_log_errors(
|
|||||||
class DenonDevice(MediaPlayerEntity):
|
class DenonDevice(MediaPlayerEntity):
|
||||||
"""Representation of a Denon Media Player Device."""
|
"""Representation of a Denon Media Player Device."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
_attr_name = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
receiver: DenonAVR,
|
receiver: DenonAVR,
|
||||||
@@ -225,7 +228,6 @@ class DenonDevice(MediaPlayerEntity):
|
|||||||
update_audyssey: bool,
|
update_audyssey: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the device."""
|
"""Initialize the device."""
|
||||||
self._attr_name = receiver.name
|
|
||||||
self._attr_unique_id = unique_id
|
self._attr_unique_id = unique_id
|
||||||
assert config_entry.unique_id
|
assert config_entry.unique_id
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
@@ -234,7 +236,7 @@ class DenonDevice(MediaPlayerEntity):
|
|||||||
identifiers={(DOMAIN, config_entry.unique_id)},
|
identifiers={(DOMAIN, config_entry.unique_id)},
|
||||||
manufacturer=config_entry.data[CONF_MANUFACTURER],
|
manufacturer=config_entry.data[CONF_MANUFACTURER],
|
||||||
model=config_entry.data[CONF_MODEL],
|
model=config_entry.data[CONF_MODEL],
|
||||||
name=config_entry.title,
|
name=receiver.name,
|
||||||
)
|
)
|
||||||
self._attr_sound_mode_list = receiver.sound_mode_list
|
self._attr_sound_mode_list = receiver.sound_mode_list
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
|||||||
from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME
|
from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME
|
||||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
from homeassistant.helpers.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
@@ -117,3 +117,8 @@ class GardenaBluetoothRemainSensor(GardenaBluetoothEntity, SensorEntity):
|
|||||||
self._attr_native_value = time
|
self._attr_native_value = time
|
||||||
super()._handle_coordinator_update()
|
super()._handle_coordinator_update()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Sensor only available when open."""
|
||||||
|
return super().available and self._attr_native_value is not None
|
||||||
|
@@ -16,11 +16,12 @@ from .coordinator import MicroBotDataUpdateCoordinator
|
|||||||
class MicroBotEntity(PassiveBluetoothCoordinatorEntity[MicroBotDataUpdateCoordinator]):
|
class MicroBotEntity(PassiveBluetoothCoordinatorEntity[MicroBotDataUpdateCoordinator]):
|
||||||
"""Generic entity for all MicroBots."""
|
"""Generic entity for all MicroBots."""
|
||||||
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
def __init__(self, coordinator, config_entry):
|
def __init__(self, coordinator, config_entry):
|
||||||
"""Initialise the entity."""
|
"""Initialise the entity."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
self._address = self.coordinator.ble_device.address
|
self._address = self.coordinator.ble_device.address
|
||||||
self._attr_name = "Push"
|
|
||||||
self._attr_unique_id = self._address
|
self._attr_unique_id = self._address
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
connections={(dr.CONNECTION_BLUETOOTH, self._address)},
|
connections={(dr.CONNECTION_BLUETOOTH, self._address)},
|
||||||
|
@@ -24,6 +24,13 @@
|
|||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"entity": {
|
||||||
|
"switch": {
|
||||||
|
"push": {
|
||||||
|
"name": "Push"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"calibrate": {
|
"calibrate": {
|
||||||
"name": "Calibrate",
|
"name": "Calibrate",
|
||||||
|
@@ -43,7 +43,7 @@ async def async_setup_entry(
|
|||||||
class MicroBotBinarySwitch(MicroBotEntity, SwitchEntity):
|
class MicroBotBinarySwitch(MicroBotEntity, SwitchEntity):
|
||||||
"""MicroBot switch class."""
|
"""MicroBot switch class."""
|
||||||
|
|
||||||
_attr_has_entity_name = True
|
_attr_translation_key = "push"
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn on the switch."""
|
"""Turn on the switch."""
|
||||||
|
@@ -48,7 +48,7 @@ class LitterRobotBinarySensorEntity(LitterRobotEntity[_RobotT], BinarySensorEnti
|
|||||||
|
|
||||||
|
|
||||||
BINARY_SENSOR_MAP: dict[type[Robot], tuple[RobotBinarySensorEntityDescription, ...]] = {
|
BINARY_SENSOR_MAP: dict[type[Robot], tuple[RobotBinarySensorEntityDescription, ...]] = {
|
||||||
LitterRobot: (
|
LitterRobot: ( # type: ignore[type-abstract] # only used for isinstance check
|
||||||
RobotBinarySensorEntityDescription[LitterRobot](
|
RobotBinarySensorEntityDescription[LitterRobot](
|
||||||
key="sleeping",
|
key="sleeping",
|
||||||
translation_key="sleeping",
|
translation_key="sleeping",
|
||||||
@@ -66,7 +66,7 @@ BINARY_SENSOR_MAP: dict[type[Robot], tuple[RobotBinarySensorEntityDescription, .
|
|||||||
is_on_fn=lambda robot: robot.sleep_mode_enabled,
|
is_on_fn=lambda robot: robot.sleep_mode_enabled,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Robot: (
|
Robot: ( # type: ignore[type-abstract] # only used for isinstance check
|
||||||
RobotBinarySensorEntityDescription[Robot](
|
RobotBinarySensorEntityDescription[Robot](
|
||||||
key="power_status",
|
key="power_status",
|
||||||
translation_key="power_status",
|
translation_key="power_status",
|
||||||
|
@@ -48,7 +48,7 @@ class RobotSelectEntityDescription(
|
|||||||
|
|
||||||
|
|
||||||
ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
|
ROBOT_SELECT_MAP: dict[type[Robot], RobotSelectEntityDescription] = {
|
||||||
LitterRobot: RobotSelectEntityDescription[LitterRobot, int](
|
LitterRobot: RobotSelectEntityDescription[LitterRobot, int]( # type: ignore[type-abstract] # only used for isinstance check
|
||||||
key="cycle_delay",
|
key="cycle_delay",
|
||||||
translation_key="cycle_delay",
|
translation_key="cycle_delay",
|
||||||
icon="mdi:timer-outline",
|
icon="mdi:timer-outline",
|
||||||
|
@@ -66,7 +66,7 @@ class LitterRobotSensorEntity(LitterRobotEntity[_RobotT], SensorEntity):
|
|||||||
|
|
||||||
|
|
||||||
ROBOT_SENSOR_MAP: dict[type[Robot], list[RobotSensorEntityDescription]] = {
|
ROBOT_SENSOR_MAP: dict[type[Robot], list[RobotSensorEntityDescription]] = {
|
||||||
LitterRobot: [
|
LitterRobot: [ # type: ignore[type-abstract] # only used for isinstance check
|
||||||
RobotSensorEntityDescription[LitterRobot](
|
RobotSensorEntityDescription[LitterRobot](
|
||||||
key="waste_drawer_level",
|
key="waste_drawer_level",
|
||||||
translation_key="waste_drawer",
|
translation_key="waste_drawer",
|
||||||
|
@@ -75,6 +75,7 @@ FAN_INV_MODES = list(FAN_INV_MODE_MAP)
|
|||||||
MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API
|
MAX_FAN_DURATION = 43200 # 15 hours is the max in the SDM API
|
||||||
MIN_TEMP = 10
|
MIN_TEMP = 10
|
||||||
MAX_TEMP = 32
|
MAX_TEMP = 32
|
||||||
|
MIN_TEMP_RANGE = 1.66667
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@@ -313,6 +314,13 @@ class ThermostatEntity(ClimateEntity):
|
|||||||
try:
|
try:
|
||||||
if self.preset_mode == PRESET_ECO or hvac_mode == HVACMode.HEAT_COOL:
|
if self.preset_mode == PRESET_ECO or hvac_mode == HVACMode.HEAT_COOL:
|
||||||
if low_temp and high_temp:
|
if low_temp and high_temp:
|
||||||
|
if high_temp - low_temp < MIN_TEMP_RANGE:
|
||||||
|
# Ensure there is a minimum gap from the new temp. Pick
|
||||||
|
# the temp that is not changing as the one to move.
|
||||||
|
if abs(high_temp - self.target_temperature_high) < 0.01:
|
||||||
|
high_temp = low_temp + MIN_TEMP_RANGE
|
||||||
|
else:
|
||||||
|
low_temp = high_temp - MIN_TEMP_RANGE
|
||||||
await trait.set_range(low_temp, high_temp)
|
await trait.set_range(low_temp, high_temp)
|
||||||
elif hvac_mode == HVACMode.COOL and temp:
|
elif hvac_mode == HVACMode.COOL and temp:
|
||||||
await trait.set_cool(temp)
|
await trait.set_cool(temp)
|
||||||
|
@@ -12,8 +12,7 @@ from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
|||||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_RADIUS
|
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_RADIUS
|
||||||
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||||
from homeassistant.helpers.entity import DeviceInfo
|
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
@@ -5,7 +5,6 @@ import logging
|
|||||||
import ssl
|
import ssl
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from jsonpath import jsonpath
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
@@ -39,13 +38,13 @@ from homeassistant.helpers.template_entity import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.util.json import json_loads
|
|
||||||
|
|
||||||
from . import async_get_config_and_coordinator, create_rest_data_from_config
|
from . import async_get_config_and_coordinator, create_rest_data_from_config
|
||||||
from .const import CONF_JSON_ATTRS, CONF_JSON_ATTRS_PATH, DEFAULT_SENSOR_NAME
|
from .const import CONF_JSON_ATTRS, CONF_JSON_ATTRS_PATH, DEFAULT_SENSOR_NAME
|
||||||
from .data import RestData
|
from .data import RestData
|
||||||
from .entity import RestEntity
|
from .entity import RestEntity
|
||||||
from .schema import RESOURCE_SCHEMA, SENSOR_SCHEMA
|
from .schema import RESOURCE_SCHEMA, SENSOR_SCHEMA
|
||||||
|
from .util import parse_json_attributes
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -163,32 +162,9 @@ class RestSensor(ManualTriggerSensorEntity, RestEntity, SensorEntity):
|
|||||||
value = self.rest.data_without_xml()
|
value = self.rest.data_without_xml()
|
||||||
|
|
||||||
if self._json_attrs:
|
if self._json_attrs:
|
||||||
if value:
|
self._attr_extra_state_attributes = parse_json_attributes(
|
||||||
try:
|
value, self._json_attrs, self._json_attrs_path
|
||||||
json_dict = json_loads(value)
|
|
||||||
if self._json_attrs_path is not None:
|
|
||||||
json_dict = jsonpath(json_dict, self._json_attrs_path)
|
|
||||||
# jsonpath will always store the result in json_dict[0]
|
|
||||||
# so the next line happens to work exactly as needed to
|
|
||||||
# find the result
|
|
||||||
if isinstance(json_dict, list):
|
|
||||||
json_dict = json_dict[0]
|
|
||||||
if isinstance(json_dict, dict):
|
|
||||||
attrs = {
|
|
||||||
k: json_dict[k] for k in self._json_attrs if k in json_dict
|
|
||||||
}
|
|
||||||
self._attr_extra_state_attributes = attrs
|
|
||||||
else:
|
|
||||||
_LOGGER.warning(
|
|
||||||
"JSON result was not a dictionary"
|
|
||||||
" or list with 0th element a dictionary"
|
|
||||||
)
|
)
|
||||||
except ValueError:
|
|
||||||
_LOGGER.warning("REST result could not be parsed as JSON")
|
|
||||||
_LOGGER.debug("Erroneous JSON: %s", value)
|
|
||||||
|
|
||||||
else:
|
|
||||||
_LOGGER.warning("Empty reply found when expecting JSON data")
|
|
||||||
|
|
||||||
raw_value = value
|
raw_value = value
|
||||||
|
|
||||||
|
40
homeassistant/components/rest/util.py
Normal file
40
homeassistant/components/rest/util.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
"""Helpers for RESTful API."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from jsonpath import jsonpath
|
||||||
|
|
||||||
|
from homeassistant.util.json import json_loads
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_json_attributes(
|
||||||
|
value: str | None, json_attrs: list[str], json_attrs_path: str | None
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Parse JSON attributes."""
|
||||||
|
if not value:
|
||||||
|
_LOGGER.warning("Empty reply found when expecting JSON data")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_dict = json_loads(value)
|
||||||
|
if json_attrs_path is not None:
|
||||||
|
json_dict = jsonpath(json_dict, json_attrs_path)
|
||||||
|
# jsonpath will always store the result in json_dict[0]
|
||||||
|
# so the next line happens to work exactly as needed to
|
||||||
|
# find the result
|
||||||
|
if isinstance(json_dict, list):
|
||||||
|
json_dict = json_dict[0]
|
||||||
|
if isinstance(json_dict, dict):
|
||||||
|
return {k: json_dict[k] for k in json_attrs if k in json_dict}
|
||||||
|
|
||||||
|
_LOGGER.warning(
|
||||||
|
"JSON result was not a dictionary or list with 0th element a dictionary"
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.warning("REST result could not be parsed as JSON")
|
||||||
|
_LOGGER.debug("Erroneous JSON: %s", value)
|
||||||
|
|
||||||
|
return {}
|
@@ -618,8 +618,13 @@ class EntityPlatform:
|
|||||||
**device_info,
|
**device_info,
|
||||||
)
|
)
|
||||||
except dev_reg.DeviceInfoError as exc:
|
except dev_reg.DeviceInfoError as exc:
|
||||||
self.logger.error("Ignoring invalid device info: %s", str(exc))
|
self.logger.error(
|
||||||
device = None
|
"%s: Not adding entity with invalid device info: %s",
|
||||||
|
self.platform_name,
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
entity.add_to_platform_abort()
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
device = None
|
device = None
|
||||||
|
|
||||||
|
2
mypy.ini
2
mypy.ini
@@ -17,7 +17,7 @@ warn_unused_configs = true
|
|||||||
warn_unused_ignores = true
|
warn_unused_ignores = true
|
||||||
enable_error_code = ignore-without-code, redundant-self, truthy-iterable
|
enable_error_code = ignore-without-code, redundant-self, truthy-iterable
|
||||||
disable_error_code = annotation-unchecked
|
disable_error_code = annotation-unchecked
|
||||||
strict_concatenate = false
|
extra_checks = false
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
disallow_incomplete_defs = true
|
disallow_incomplete_defs = true
|
||||||
disallow_subclassing_any = true
|
disallow_subclassing_any = true
|
||||||
|
@@ -11,7 +11,7 @@ astroid==2.15.4
|
|||||||
coverage==7.2.7
|
coverage==7.2.7
|
||||||
freezegun==1.2.2
|
freezegun==1.2.2
|
||||||
mock-open==1.4.0
|
mock-open==1.4.0
|
||||||
mypy==1.4.1
|
mypy==1.5.0
|
||||||
pre-commit==3.3.3
|
pre-commit==3.3.3
|
||||||
pydantic==1.10.12
|
pydantic==1.10.12
|
||||||
pylint==2.17.4
|
pylint==2.17.4
|
||||||
|
@@ -51,8 +51,9 @@ GENERAL_SETTINGS: Final[dict[str, str]] = {
|
|||||||
]
|
]
|
||||||
),
|
),
|
||||||
"disable_error_code": ", ".join(["annotation-unchecked"]),
|
"disable_error_code": ", ".join(["annotation-unchecked"]),
|
||||||
# Strict_concatenate breaks passthrough ParamSpec typing
|
# Impractical in real code
|
||||||
"strict_concatenate": "false",
|
# E.g. this breaks passthrough ParamSpec typing with Concatenate
|
||||||
|
"extra_checks": "false",
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is basically the list of checks which is enabled for "strict=true".
|
# This is basically the list of checks which is enabled for "strict=true".
|
||||||
|
@@ -35,7 +35,7 @@
|
|||||||
'entity_id': 'sensor.mock_title_valve_closing',
|
'entity_id': 'sensor.mock_title_valve_closing',
|
||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'unknown',
|
'state': 'unavailable',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_setup[98bd2a19-0b0e-421a-84e5-ddbf75dc6de4-raw0-sensor.mock_title_battery]
|
# name: test_setup[98bd2a19-0b0e-421a-84e5-ddbf75dc6de4-raw0-sensor.mock_title_battery]
|
||||||
|
@@ -758,6 +758,75 @@ async def test_thermostat_set_temperature_hvac_mode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("setpoint", "target_low", "target_high", "expected_params"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"heatCelsius": 19.0,
|
||||||
|
"coolCelsius": 25.0,
|
||||||
|
},
|
||||||
|
19.0,
|
||||||
|
20.0,
|
||||||
|
# Cool is accepted and lowers heat by the min range
|
||||||
|
{"heatCelsius": 18.33333, "coolCelsius": 20.0},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
{
|
||||||
|
"heatCelsius": 19.0,
|
||||||
|
"coolCelsius": 25.0,
|
||||||
|
},
|
||||||
|
24.0,
|
||||||
|
25.0,
|
||||||
|
# Cool is accepted and lowers heat by the min range
|
||||||
|
{"heatCelsius": 24.0, "coolCelsius": 25.66667},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_thermostat_set_temperature_range_too_close(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_platform: PlatformSetup,
|
||||||
|
auth: FakeAuth,
|
||||||
|
create_device: CreateDevice,
|
||||||
|
setpoint: dict[str, Any],
|
||||||
|
target_low: float,
|
||||||
|
target_high: float,
|
||||||
|
expected_params: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test setting an HVAC temperature range that is too small of a range."""
|
||||||
|
create_device.create(
|
||||||
|
{
|
||||||
|
"sdm.devices.traits.ThermostatHvac": {"status": "OFF"},
|
||||||
|
"sdm.devices.traits.ThermostatMode": {
|
||||||
|
"availableModes": ["HEAT", "COOL", "HEATCOOL", "OFF"],
|
||||||
|
"mode": "HEATCOOL",
|
||||||
|
},
|
||||||
|
"sdm.devices.traits.ThermostatTemperatureSetpoint": setpoint,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await setup_platform()
|
||||||
|
|
||||||
|
assert len(hass.states.async_all()) == 1
|
||||||
|
thermostat = hass.states.get("climate.my_thermostat")
|
||||||
|
assert thermostat is not None
|
||||||
|
assert thermostat.state == HVACMode.HEAT_COOL
|
||||||
|
|
||||||
|
# Move the target temp to be in too small of a range
|
||||||
|
await common.async_set_temperature(
|
||||||
|
hass,
|
||||||
|
target_temp_low=target_low,
|
||||||
|
target_temp_high=target_high,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert auth.method == "post"
|
||||||
|
assert auth.url == DEVICE_COMMAND
|
||||||
|
assert auth.json == {
|
||||||
|
"command": "sdm.devices.commands.ThermostatTemperatureSetpoint.SetRange",
|
||||||
|
"params": expected_params,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_thermostat_set_heat_cool(
|
async def test_thermostat_set_heat_cool(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
setup_platform: PlatformSetup,
|
setup_platform: PlatformSetup,
|
||||||
|
@@ -1853,23 +1853,27 @@ async def test_device_name_defaulting_config_entry(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("device_info"),
|
("device_info", "number_of_entities"),
|
||||||
[
|
[
|
||||||
# No identifiers
|
# No identifiers
|
||||||
{},
|
({}, 1), # Empty device info does not prevent the entity from being created
|
||||||
{"name": "bla"},
|
({"name": "bla"}, 0),
|
||||||
{"default_name": "bla"},
|
({"default_name": "bla"}, 0),
|
||||||
# Match multiple types
|
# Match multiple types
|
||||||
|
(
|
||||||
{
|
{
|
||||||
"identifiers": {("hue", "1234")},
|
"identifiers": {("hue", "1234")},
|
||||||
"name": "bla",
|
"name": "bla",
|
||||||
"default_name": "yo",
|
"default_name": "yo",
|
||||||
},
|
},
|
||||||
|
0,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_device_type_error_checking(
|
async def test_device_type_error_checking(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
device_info: dict,
|
device_info: dict,
|
||||||
|
number_of_entities: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test catching invalid device info."""
|
"""Test catching invalid device info."""
|
||||||
|
|
||||||
@@ -1895,6 +1899,6 @@ async def test_device_type_error_checking(
|
|||||||
|
|
||||||
dev_reg = dr.async_get(hass)
|
dev_reg = dr.async_get(hass)
|
||||||
assert len(dev_reg.devices) == 0
|
assert len(dev_reg.devices) == 0
|
||||||
# Entity should still be registered
|
|
||||||
ent_reg = er.async_get(hass)
|
ent_reg = er.async_get(hass)
|
||||||
assert ent_reg.async_get("test_domain.test_qwer") is not None
|
assert len(ent_reg.entities) == number_of_entities
|
||||||
|
assert len(hass.states.async_all()) == number_of_entities
|
||||||
|
Reference in New Issue
Block a user