Add preset mode to Comelit climate (#145195)

This commit is contained in:
Simone Chemelli 2025-05-25 00:37:21 +03:00 committed by GitHub
parent ce02a5544d
commit 526a8ee31f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 162 additions and 27 deletions

View File

@ -20,7 +20,12 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
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 .entity import ComelitBridgeBaseEntity
from .utils import bridge_api_call
@ -41,11 +46,13 @@ class ClimaComelitMode(StrEnum):
class ClimaComelitCommand(StrEnum):
"""Serial Bridge clima commands."""
AUTO = "auto"
MANUAL = "man"
OFF = "off"
ON = "on"
MANUAL = "man"
SET = "set"
AUTO = "auto"
SNOW = "lower"
SUN = "upper"
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.AUTO: ClimaComelitCommand.AUTO,
HVACMode.COOL: ClimaComelitCommand.MANUAL,
HVACMode.HEAT: ClimaComelitCommand.MANUAL,
HVACMode.COOL: ClimaComelitCommand.SNOW,
HVACMode.HEAT: ClimaComelitCommand.SUN,
}
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):
"""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_min_temp = 5
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.PRESET_MODE
)
_attr_target_temperature_step = PRECISION_TENTHS
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_name = None
_attr_translation_key = "thermostat"
def __init__(
self,
@ -132,6 +146,8 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
_mode = values[2] # Values from API: "O", "L", "U"
_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_hvac_action = None
@ -141,10 +157,6 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
self._attr_hvac_action = API_STATUS[_mode]["hvac_action"]
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:
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:
"""Set new target temperature."""
if (
target_temp := kwargs.get(ATTR_TEMPERATURE)
) is None or self.hvac_mode == HVACMode.OFF:
(target_temp := kwargs.get(ATTR_TEMPERATURE)) is None
or self.hvac_mode == HVACMode.OFF
or self._attr_preset_mode == PRESET_MODE_AUTO
):
return
await self.coordinator.api.set_clima_status(
self._device.index, ClimaComelitCommand.MANUAL
)
await self.coordinator.api.set_clima_status(
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:
"""Set hvac mode."""
if hvac_mode != HVACMode.OFF:
if self._attr_hvac_mode == HVACMode.OFF:
await self.coordinator.api.set_clima_status(
self._device.index, ClimaComelitCommand.ON
)
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.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()

View File

@ -11,3 +11,8 @@ DEFAULT_PORT = 80
DEVICE_TYPE_LIST = [BRIDGE, VEDO]
SCAN_INTERVAL = 5
PRESET_MODE_AUTO = "automatic"
PRESET_MODE_MANUAL = "manual"
PRESET_MODE_AUTO_TARGET_TEMP = 20

View File

@ -4,6 +4,18 @@
"zone_status": {
"default": "mdi:shield-check"
}
},
"climate": {
"thermostat": {
"state_attributes": {
"preset_mode": {
"state": {
"automatic": "mdi:refresh-auto",
"manual": "mdi:alpha-m"
}
}
}
}
}
}
}

View File

@ -74,6 +74,18 @@
"dehumidifier": {
"name": "Dehumidifier"
}
},
"climate": {
"thermostat": {
"state_attributes": {
"preset_mode": {
"state": {
"automatic": "[%key:common::state::auto%]",
"manual": "[%key:common::state::manual%]"
}
}
}
}
}
},
"exceptions": {

View File

@ -6,13 +6,16 @@
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.HEAT: 'heat'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 30,
'min_temp': 5,
'preset_modes': list([
'automatic',
'manual',
]),
'target_temp_step': 0.1,
}),
'config_entry_id': <ANY>,
@ -37,8 +40,8 @@
'original_name': None,
'platform': 'comelit',
'previous_unique_id': None,
'supported_features': <ClimateEntityFeature: 385>,
'translation_key': None,
'supported_features': <ClimateEntityFeature: 401>,
'translation_key': 'thermostat',
'unique_id': 'serial_bridge_config_entry_id-0',
'unit_of_measurement': None,
})
@ -50,14 +53,18 @@
'friendly_name': 'Climate0',
'hvac_action': <HVACAction.IDLE: 'idle'>,
'hvac_modes': list([
<HVACMode.AUTO: 'auto'>,
<HVACMode.COOL: 'cool'>,
<HVACMode.HEAT: 'heat'>,
<HVACMode.OFF: 'off'>,
]),
'max_temp': 30,
'min_temp': 5,
'supported_features': <ClimateEntityFeature: 385>,
'preset_mode': 'manual',
'preset_modes': list([
'automatic',
'manual',
]),
'supported_features': <ClimateEntityFeature: 401>,
'target_temp_step': 0.1,
'temperature': 5.0,
}),

View File

@ -11,12 +11,19 @@ from syrupy.assertion import SnapshotAssertion
from homeassistant.components.climate import (
ATTR_HVAC_MODE,
ATTR_PRESET_MODE,
DOMAIN as CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
SERVICE_TURN_OFF,
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.core import HomeAssistant
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(
CLIMATE_DOMAIN,
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,
)
mock_serial_bridge.set_clima_status.assert_called()
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