mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 14:17:45 +00:00
Add preset mode to Comelit climate (#145195)
This commit is contained in:
parent
ce02a5544d
commit
526a8ee31f
@ -20,7 +20,12 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import (
|
||||||
|
DOMAIN,
|
||||||
|
PRESET_MODE_AUTO,
|
||||||
|
PRESET_MODE_AUTO_TARGET_TEMP,
|
||||||
|
PRESET_MODE_MANUAL,
|
||||||
|
)
|
||||||
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
|
from .coordinator import ComelitConfigEntry, ComelitSerialBridge
|
||||||
from .entity import ComelitBridgeBaseEntity
|
from .entity import ComelitBridgeBaseEntity
|
||||||
from .utils import bridge_api_call
|
from .utils import bridge_api_call
|
||||||
@ -41,11 +46,13 @@ class ClimaComelitMode(StrEnum):
|
|||||||
class ClimaComelitCommand(StrEnum):
|
class ClimaComelitCommand(StrEnum):
|
||||||
"""Serial Bridge clima commands."""
|
"""Serial Bridge clima commands."""
|
||||||
|
|
||||||
|
AUTO = "auto"
|
||||||
|
MANUAL = "man"
|
||||||
OFF = "off"
|
OFF = "off"
|
||||||
ON = "on"
|
ON = "on"
|
||||||
MANUAL = "man"
|
|
||||||
SET = "set"
|
SET = "set"
|
||||||
AUTO = "auto"
|
SNOW = "lower"
|
||||||
|
SUN = "upper"
|
||||||
|
|
||||||
|
|
||||||
class ClimaComelitApiStatus(TypedDict):
|
class ClimaComelitApiStatus(TypedDict):
|
||||||
@ -67,11 +74,15 @@ API_STATUS: dict[str, ClimaComelitApiStatus] = {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
MODE_TO_ACTION: dict[HVACMode, ClimaComelitCommand] = {
|
HVACMODE_TO_ACTION: dict[HVACMode, ClimaComelitCommand] = {
|
||||||
HVACMode.OFF: ClimaComelitCommand.OFF,
|
HVACMode.OFF: ClimaComelitCommand.OFF,
|
||||||
HVACMode.AUTO: ClimaComelitCommand.AUTO,
|
HVACMode.COOL: ClimaComelitCommand.SNOW,
|
||||||
HVACMode.COOL: ClimaComelitCommand.MANUAL,
|
HVACMode.HEAT: ClimaComelitCommand.SUN,
|
||||||
HVACMode.HEAT: ClimaComelitCommand.MANUAL,
|
}
|
||||||
|
|
||||||
|
PRESET_MODE_TO_ACTION: dict[str, ClimaComelitCommand] = {
|
||||||
|
PRESET_MODE_MANUAL: ClimaComelitCommand.MANUAL,
|
||||||
|
PRESET_MODE_AUTO: ClimaComelitCommand.AUTO,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -93,17 +104,20 @@ async def async_setup_entry(
|
|||||||
class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
|
class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
|
||||||
"""Climate device."""
|
"""Climate device."""
|
||||||
|
|
||||||
_attr_hvac_modes = [HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF]
|
_attr_hvac_modes = [HVACMode.COOL, HVACMode.HEAT, HVACMode.OFF]
|
||||||
|
_attr_preset_modes = [PRESET_MODE_AUTO, PRESET_MODE_MANUAL]
|
||||||
_attr_max_temp = 30
|
_attr_max_temp = 30
|
||||||
_attr_min_temp = 5
|
_attr_min_temp = 5
|
||||||
_attr_supported_features = (
|
_attr_supported_features = (
|
||||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
| ClimateEntityFeature.TURN_OFF
|
| ClimateEntityFeature.TURN_OFF
|
||||||
| ClimateEntityFeature.TURN_ON
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
| ClimateEntityFeature.PRESET_MODE
|
||||||
)
|
)
|
||||||
_attr_target_temperature_step = PRECISION_TENTHS
|
_attr_target_temperature_step = PRECISION_TENTHS
|
||||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||||
_attr_name = None
|
_attr_name = None
|
||||||
|
_attr_translation_key = "thermostat"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -132,6 +146,8 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
|
|||||||
_mode = values[2] # Values from API: "O", "L", "U"
|
_mode = values[2] # Values from API: "O", "L", "U"
|
||||||
_automatic = values[3] == ClimaComelitMode.AUTO
|
_automatic = values[3] == ClimaComelitMode.AUTO
|
||||||
|
|
||||||
|
self._attr_preset_mode = PRESET_MODE_AUTO if _automatic else PRESET_MODE_MANUAL
|
||||||
|
|
||||||
self._attr_current_temperature = values[0] / 10
|
self._attr_current_temperature = values[0] / 10
|
||||||
|
|
||||||
self._attr_hvac_action = None
|
self._attr_hvac_action = None
|
||||||
@ -141,10 +157,6 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
|
|||||||
self._attr_hvac_action = API_STATUS[_mode]["hvac_action"]
|
self._attr_hvac_action = API_STATUS[_mode]["hvac_action"]
|
||||||
|
|
||||||
self._attr_hvac_mode = None
|
self._attr_hvac_mode = None
|
||||||
if _mode == ClimaComelitMode.OFF:
|
|
||||||
self._attr_hvac_mode = HVACMode.OFF
|
|
||||||
if _automatic:
|
|
||||||
self._attr_hvac_mode = HVACMode.AUTO
|
|
||||||
if _mode in API_STATUS:
|
if _mode in API_STATUS:
|
||||||
self._attr_hvac_mode = API_STATUS[_mode]["hvac_mode"]
|
self._attr_hvac_mode = API_STATUS[_mode]["hvac_mode"]
|
||||||
|
|
||||||
@ -160,13 +172,12 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
|
|||||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||||
"""Set new target temperature."""
|
"""Set new target temperature."""
|
||||||
if (
|
if (
|
||||||
target_temp := kwargs.get(ATTR_TEMPERATURE)
|
(target_temp := kwargs.get(ATTR_TEMPERATURE)) is None
|
||||||
) is None or self.hvac_mode == HVACMode.OFF:
|
or self.hvac_mode == HVACMode.OFF
|
||||||
|
or self._attr_preset_mode == PRESET_MODE_AUTO
|
||||||
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
await self.coordinator.api.set_clima_status(
|
|
||||||
self._device.index, ClimaComelitCommand.MANUAL
|
|
||||||
)
|
|
||||||
await self.coordinator.api.set_clima_status(
|
await self.coordinator.api.set_clima_status(
|
||||||
self._device.index, ClimaComelitCommand.SET, target_temp
|
self._device.index, ClimaComelitCommand.SET, target_temp
|
||||||
)
|
)
|
||||||
@ -177,12 +188,28 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
|
|||||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||||
"""Set hvac mode."""
|
"""Set hvac mode."""
|
||||||
|
|
||||||
if hvac_mode != HVACMode.OFF:
|
if self._attr_hvac_mode == HVACMode.OFF:
|
||||||
await self.coordinator.api.set_clima_status(
|
await self.coordinator.api.set_clima_status(
|
||||||
self._device.index, ClimaComelitCommand.ON
|
self._device.index, ClimaComelitCommand.ON
|
||||||
)
|
)
|
||||||
await self.coordinator.api.set_clima_status(
|
await self.coordinator.api.set_clima_status(
|
||||||
self._device.index, MODE_TO_ACTION[hvac_mode]
|
self._device.index, HVACMODE_TO_ACTION[hvac_mode]
|
||||||
)
|
)
|
||||||
self._attr_hvac_mode = hvac_mode
|
self._attr_hvac_mode = hvac_mode
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||||
|
"""Set new target preset mode."""
|
||||||
|
|
||||||
|
if self._attr_hvac_mode == HVACMode.OFF:
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.coordinator.api.set_clima_status(
|
||||||
|
self._device.index, PRESET_MODE_TO_ACTION[preset_mode]
|
||||||
|
)
|
||||||
|
self._attr_preset_mode = preset_mode
|
||||||
|
|
||||||
|
if preset_mode == PRESET_MODE_AUTO:
|
||||||
|
self._attr_target_temperature = PRESET_MODE_AUTO_TARGET_TEMP
|
||||||
|
|
||||||
|
self.async_write_ha_state()
|
||||||
|
@ -11,3 +11,8 @@ DEFAULT_PORT = 80
|
|||||||
DEVICE_TYPE_LIST = [BRIDGE, VEDO]
|
DEVICE_TYPE_LIST = [BRIDGE, VEDO]
|
||||||
|
|
||||||
SCAN_INTERVAL = 5
|
SCAN_INTERVAL = 5
|
||||||
|
|
||||||
|
PRESET_MODE_AUTO = "automatic"
|
||||||
|
PRESET_MODE_MANUAL = "manual"
|
||||||
|
|
||||||
|
PRESET_MODE_AUTO_TARGET_TEMP = 20
|
||||||
|
@ -4,6 +4,18 @@
|
|||||||
"zone_status": {
|
"zone_status": {
|
||||||
"default": "mdi:shield-check"
|
"default": "mdi:shield-check"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"climate": {
|
||||||
|
"thermostat": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"automatic": "mdi:refresh-auto",
|
||||||
|
"manual": "mdi:alpha-m"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,18 @@
|
|||||||
"dehumidifier": {
|
"dehumidifier": {
|
||||||
"name": "Dehumidifier"
|
"name": "Dehumidifier"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"climate": {
|
||||||
|
"thermostat": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"automatic": "[%key:common::state::auto%]",
|
||||||
|
"manual": "[%key:common::state::manual%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exceptions": {
|
"exceptions": {
|
||||||
|
@ -6,13 +6,16 @@
|
|||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'hvac_modes': list([
|
'hvac_modes': list([
|
||||||
<HVACMode.AUTO: 'auto'>,
|
|
||||||
<HVACMode.COOL: 'cool'>,
|
<HVACMode.COOL: 'cool'>,
|
||||||
<HVACMode.HEAT: 'heat'>,
|
<HVACMode.HEAT: 'heat'>,
|
||||||
<HVACMode.OFF: 'off'>,
|
<HVACMode.OFF: 'off'>,
|
||||||
]),
|
]),
|
||||||
'max_temp': 30,
|
'max_temp': 30,
|
||||||
'min_temp': 5,
|
'min_temp': 5,
|
||||||
|
'preset_modes': list([
|
||||||
|
'automatic',
|
||||||
|
'manual',
|
||||||
|
]),
|
||||||
'target_temp_step': 0.1,
|
'target_temp_step': 0.1,
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@ -37,8 +40,8 @@
|
|||||||
'original_name': None,
|
'original_name': None,
|
||||||
'platform': 'comelit',
|
'platform': 'comelit',
|
||||||
'previous_unique_id': None,
|
'previous_unique_id': None,
|
||||||
'supported_features': <ClimateEntityFeature: 385>,
|
'supported_features': <ClimateEntityFeature: 401>,
|
||||||
'translation_key': None,
|
'translation_key': 'thermostat',
|
||||||
'unique_id': 'serial_bridge_config_entry_id-0',
|
'unique_id': 'serial_bridge_config_entry_id-0',
|
||||||
'unit_of_measurement': None,
|
'unit_of_measurement': None,
|
||||||
})
|
})
|
||||||
@ -50,14 +53,18 @@
|
|||||||
'friendly_name': 'Climate0',
|
'friendly_name': 'Climate0',
|
||||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||||
'hvac_modes': list([
|
'hvac_modes': list([
|
||||||
<HVACMode.AUTO: 'auto'>,
|
|
||||||
<HVACMode.COOL: 'cool'>,
|
<HVACMode.COOL: 'cool'>,
|
||||||
<HVACMode.HEAT: 'heat'>,
|
<HVACMode.HEAT: 'heat'>,
|
||||||
<HVACMode.OFF: 'off'>,
|
<HVACMode.OFF: 'off'>,
|
||||||
]),
|
]),
|
||||||
'max_temp': 30,
|
'max_temp': 30,
|
||||||
'min_temp': 5,
|
'min_temp': 5,
|
||||||
'supported_features': <ClimateEntityFeature: 385>,
|
'preset_mode': 'manual',
|
||||||
|
'preset_modes': list([
|
||||||
|
'automatic',
|
||||||
|
'manual',
|
||||||
|
]),
|
||||||
|
'supported_features': <ClimateEntityFeature: 401>,
|
||||||
'target_temp_step': 0.1,
|
'target_temp_step': 0.1,
|
||||||
'temperature': 5.0,
|
'temperature': 5.0,
|
||||||
}),
|
}),
|
||||||
|
@ -11,12 +11,19 @@ from syrupy.assertion import SnapshotAssertion
|
|||||||
|
|
||||||
from homeassistant.components.climate import (
|
from homeassistant.components.climate import (
|
||||||
ATTR_HVAC_MODE,
|
ATTR_HVAC_MODE,
|
||||||
|
ATTR_PRESET_MODE,
|
||||||
DOMAIN as CLIMATE_DOMAIN,
|
DOMAIN as CLIMATE_DOMAIN,
|
||||||
SERVICE_SET_HVAC_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
SERVICE_SET_TEMPERATURE,
|
SERVICE_SET_TEMPERATURE,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
HVACMode,
|
HVACMode,
|
||||||
)
|
)
|
||||||
from homeassistant.components.comelit.const import SCAN_INTERVAL
|
from homeassistant.components.comelit.const import (
|
||||||
|
PRESET_MODE_AUTO,
|
||||||
|
PRESET_MODE_MANUAL,
|
||||||
|
SCAN_INTERVAL,
|
||||||
|
)
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
@ -273,10 +280,75 @@ async def test_climate_hvac_mode_when_off(
|
|||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
CLIMATE_DOMAIN,
|
CLIMATE_DOMAIN,
|
||||||
SERVICE_SET_HVAC_MODE,
|
SERVICE_SET_HVAC_MODE,
|
||||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.AUTO},
|
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.COOL},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
mock_serial_bridge.set_clima_status.assert_called()
|
mock_serial_bridge.set_clima_status.assert_called()
|
||||||
|
|
||||||
assert (state := hass.states.get(ENTITY_ID))
|
assert (state := hass.states.get(ENTITY_ID))
|
||||||
assert state.state == HVACMode.AUTO
|
assert state.state == HVACMode.COOL
|
||||||
|
|
||||||
|
|
||||||
|
async def test_climate_preset_mode(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_serial_bridge: AsyncMock,
|
||||||
|
mock_serial_bridge_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test climate preset mode service."""
|
||||||
|
|
||||||
|
await setup_integration(hass, mock_serial_bridge_config_entry)
|
||||||
|
|
||||||
|
assert (state := hass.states.get(ENTITY_ID))
|
||||||
|
assert state.state == HVACMode.HEAT
|
||||||
|
assert state.attributes[ATTR_TEMPERATURE] == 5.0
|
||||||
|
assert state.attributes[ATTR_PRESET_MODE] == PRESET_MODE_MANUAL
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_MODE_AUTO},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_serial_bridge.set_clima_status.assert_called()
|
||||||
|
|
||||||
|
assert (state := hass.states.get(ENTITY_ID))
|
||||||
|
assert state.state == HVACMode.HEAT
|
||||||
|
assert state.attributes[ATTR_TEMPERATURE] == 20.0
|
||||||
|
assert state.attributes[ATTR_PRESET_MODE] == PRESET_MODE_AUTO
|
||||||
|
|
||||||
|
|
||||||
|
async def test_climate_preset_mode_when_off(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_serial_bridge: AsyncMock,
|
||||||
|
mock_serial_bridge_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test climate preset mode service when off."""
|
||||||
|
|
||||||
|
await setup_integration(hass, mock_serial_bridge_config_entry)
|
||||||
|
|
||||||
|
assert (state := hass.states.get(ENTITY_ID))
|
||||||
|
assert state.state == HVACMode.HEAT
|
||||||
|
assert state.attributes[ATTR_TEMPERATURE] == 5.0
|
||||||
|
assert state.attributes[ATTR_PRESET_MODE] == PRESET_MODE_MANUAL
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_serial_bridge.set_clima_status.assert_called()
|
||||||
|
|
||||||
|
assert (state := hass.states.get(ENTITY_ID))
|
||||||
|
assert state.state == HVACMode.OFF
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
CLIMATE_DOMAIN,
|
||||||
|
SERVICE_SET_PRESET_MODE,
|
||||||
|
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_MODE_AUTO},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_serial_bridge.set_clima_status.assert_called()
|
||||||
|
|
||||||
|
assert (state := hass.states.get(ENTITY_ID))
|
||||||
|
assert state.state == HVACMode.OFF
|
||||||
|
Loading…
x
Reference in New Issue
Block a user