Add homematicip_cloud service set cooling home (#121943)

* [homematicip_cloud] Add service to set cooling mode

* Create seperate test for cooling

* Rename service to set_home_cooling_mode

* Raise exception when accesspoint not found
This commit is contained in:
Barry vd. Heuvel 2024-08-12 14:01:12 +02:00 committed by GitHub
parent ecf22e4c4f
commit 81faf1b582
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 133 additions and 17 deletions

View file

@ -7,6 +7,7 @@
"deactivate_vacation": "mdi:compass-off",
"set_active_climate_profile": "mdi:home-thermometer",
"dump_hap_config": "mdi:database-export",
"reset_energy_counter": "mdi:reload"
"reset_energy_counter": "mdi:reload",
"set_home_cooling_mode": "mdi:snowflake"
}
}

View file

@ -7,5 +7,5 @@
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"quality_scale": "silver",
"requirements": ["homematicip==1.1.1"]
"requirements": ["homematicip==1.1.2"]
}

View file

@ -13,6 +13,7 @@ import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import comp_entity_ids
from homeassistant.helpers.service import (
@ -31,6 +32,7 @@ ATTR_CONFIG_OUTPUT_FILE_PREFIX = "config_output_file_prefix"
ATTR_CONFIG_OUTPUT_PATH = "config_output_path"
ATTR_DURATION = "duration"
ATTR_ENDTIME = "endtime"
ATTR_COOLING = "cooling"
DEFAULT_CONFIG_FILE_PREFIX = "hmip-config"
@ -42,6 +44,7 @@ SERVICE_DEACTIVATE_VACATION = "deactivate_vacation"
SERVICE_DUMP_HAP_CONFIG = "dump_hap_config"
SERVICE_RESET_ENERGY_COUNTER = "reset_energy_counter"
SERVICE_SET_ACTIVE_CLIMATE_PROFILE = "set_active_climate_profile"
SERVICE_SET_HOME_COOLING_MODE = "set_home_cooling_mode"
HMIPC_SERVICES = [
SERVICE_ACTIVATE_ECO_MODE_WITH_DURATION,
@ -52,6 +55,7 @@ HMIPC_SERVICES = [
SERVICE_DUMP_HAP_CONFIG,
SERVICE_RESET_ENERGY_COUNTER,
SERVICE_SET_ACTIVE_CLIMATE_PROFILE,
SERVICE_SET_HOME_COOLING_MODE,
]
SCHEMA_ACTIVATE_ECO_MODE_WITH_DURATION = vol.Schema(
@ -107,6 +111,13 @@ SCHEMA_RESET_ENERGY_COUNTER = vol.Schema(
{vol.Required(ATTR_ENTITY_ID): comp_entity_ids}
)
SCHEMA_SET_HOME_COOLING_MODE = vol.Schema(
{
vol.Optional(ATTR_COOLING, default=True): cv.boolean,
vol.Optional(ATTR_ACCESSPOINT_ID): vol.All(str, vol.Length(min=24, max=24)),
}
)
async def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the HomematicIP Cloud services."""
@ -135,6 +146,8 @@ async def async_setup_services(hass: HomeAssistant) -> None:
await _async_reset_energy_counter(hass, service)
elif service_name == SERVICE_SET_ACTIVE_CLIMATE_PROFILE:
await _set_active_climate_profile(hass, service)
elif service_name == SERVICE_SET_HOME_COOLING_MODE:
await _async_set_home_cooling_mode(hass, service)
hass.services.async_register(
domain=HMIPC_DOMAIN,
@ -194,6 +207,14 @@ async def async_setup_services(hass: HomeAssistant) -> None:
schema=SCHEMA_RESET_ENERGY_COUNTER,
)
async_register_admin_service(
hass=hass,
domain=HMIPC_DOMAIN,
service=SERVICE_SET_HOME_COOLING_MODE,
service_func=async_call_hmipc_service,
schema=SCHEMA_SET_HOME_COOLING_MODE,
)
async def async_unload_services(hass: HomeAssistant):
"""Unload HomematicIP Cloud services."""
@ -324,10 +345,25 @@ async def _async_reset_energy_counter(hass: HomeAssistant, service: ServiceCall)
await device.reset_energy_counter()
async def _async_set_home_cooling_mode(hass: HomeAssistant, service: ServiceCall):
"""Service to set the cooling mode."""
cooling = service.data[ATTR_COOLING]
if hapid := service.data.get(ATTR_ACCESSPOINT_ID):
if home := _get_home(hass, hapid):
await home.set_cooling(cooling)
else:
for hap in hass.data[HMIPC_DOMAIN].values():
await hap.home.set_cooling(cooling)
def _get_home(hass: HomeAssistant, hapid: str) -> AsyncHome | None:
"""Return a HmIP home."""
if hap := hass.data[HMIPC_DOMAIN].get(hapid):
return hap.home
_LOGGER.info("No matching access point found for access point id %s", hapid)
return None
raise ServiceValidationError(
translation_domain=HMIPC_DOMAIN,
translation_key="access_point_not_found",
translation_placeholders={"id": hapid},
)

View file

@ -98,3 +98,14 @@ reset_energy_counter:
example: switch.livingroom
selector:
text:
set_home_cooling_mode:
fields:
cooling:
default: true
selector:
boolean:
accesspoint_id:
example: 3014xxxxxxxxxxxxxxxxxxxx
selector:
text:

View file

@ -26,6 +26,11 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"exceptions": {
"access_point_not_found": {
"message": "No matching access point found for access point id {id}"
}
},
"services": {
"activate_eco_mode_with_duration": {
"name": "Activate eco mode with duration",
@ -134,6 +139,20 @@
"description": "The ID of the measuring entity. Use 'all' keyword to reset all energy counters."
}
}
},
"set_home_cooling_mode": {
"name": "Set home cooling mode",
"description": "Set the heating/cooling mode for the entire home",
"fields": {
"accesspoint_id": {
"name": "[%key:component::homematicip_cloud::services::activate_eco_mode_with_duration::fields::accesspoint_id::name%]",
"description": "[%key:component::homematicip_cloud::services::activate_eco_mode_with_duration::fields::accesspoint_id::description%]"
},
"cooling": {
"name": "Cooling",
"description": "Enable for cooling mode, disable for heating mode"
}
}
}
}
}

View file

@ -1105,7 +1105,7 @@ home-assistant-intents==2024.8.7
homeconnect==0.8.0
# homeassistant.components.homematicip_cloud
homematicip==1.1.1
homematicip==1.1.2
# homeassistant.components.horizon
horimote==0.4.1

View file

@ -931,7 +931,7 @@ home-assistant-intents==2024.8.7
homeconnect==0.8.0
# homeassistant.components.homematicip_cloud
homematicip==1.1.1
homematicip==1.1.2
# homeassistant.components.remember_the_milk
httplib2==0.20.4

View file

@ -622,18 +622,67 @@ async def test_hmip_climate_services(
assert len(home._connection.mock_calls) == 10
not_existing_hap_id = "5555F7110000000000000001"
with pytest.raises(ServiceValidationError) as excinfo:
await hass.services.async_call(
"homematicip_cloud",
"deactivate_vacation",
{"accesspoint_id": not_existing_hap_id},
blocking=True,
)
assert home.mock_calls[-1][0] == "deactivate_vacation"
assert home.mock_calls[-1][1] == ()
assert excinfo.value.translation_domain == HMIPC_DOMAIN
assert excinfo.value.translation_key == "access_point_not_found"
# There is no further call on connection.
assert len(home._connection.mock_calls) == 10
async def test_hmip_set_home_cooling_mode(
hass: HomeAssistant, mock_hap_with_service
) -> None:
"""Test HomematicipSetHomeCoolingMode."""
home = mock_hap_with_service.home
await hass.services.async_call(
"homematicip_cloud",
"set_home_cooling_mode",
{"accesspoint_id": HAPID, "cooling": False},
blocking=True,
)
assert home.mock_calls[-1][0] == "set_cooling"
assert home.mock_calls[-1][1] == (False,)
assert len(home._connection.mock_calls) == 1
await hass.services.async_call(
"homematicip_cloud",
"set_home_cooling_mode",
{"accesspoint_id": HAPID, "cooling": True},
blocking=True,
)
assert home.mock_calls[-1][0] == "set_cooling"
assert home.mock_calls[-1][1]
assert len(home._connection.mock_calls) == 2
await hass.services.async_call(
"homematicip_cloud", "set_home_cooling_mode", blocking=True
)
assert home.mock_calls[-1][0] == "set_cooling"
assert home.mock_calls[-1][1]
assert len(home._connection.mock_calls) == 3
not_existing_hap_id = "5555F7110000000000000001"
with pytest.raises(ServiceValidationError) as excinfo:
await hass.services.async_call(
"homematicip_cloud",
"set_home_cooling_mode",
{"accesspoint_id": not_existing_hap_id, "cooling": True},
blocking=True,
)
assert excinfo.value.translation_domain == HMIPC_DOMAIN
assert excinfo.value.translation_key == "access_point_not_found"
# There is no further call on connection.
assert len(home._connection.mock_calls) == 3
async def test_hmip_heating_group_services(
hass: HomeAssistant, default_mock_hap_factory
) -> None:

View file

@ -199,7 +199,7 @@ async def test_setup_services_and_unload_services(hass: HomeAssistant) -> None:
# Check services are created
hmipc_services = hass.services.async_services()[HMIPC_DOMAIN]
assert len(hmipc_services) == 8
assert len(hmipc_services) == 9
config_entries = hass.config_entries.async_entries(HMIPC_DOMAIN)
assert len(config_entries) == 1
@ -232,7 +232,7 @@ async def test_setup_two_haps_unload_one_by_one(hass: HomeAssistant) -> None:
assert await async_setup_component(hass, HMIPC_DOMAIN, {})
hmipc_services = hass.services.async_services()[HMIPC_DOMAIN]
assert len(hmipc_services) == 8
assert len(hmipc_services) == 9
config_entries = hass.config_entries.async_entries(HMIPC_DOMAIN)
assert len(config_entries) == 2
@ -241,7 +241,7 @@ async def test_setup_two_haps_unload_one_by_one(hass: HomeAssistant) -> None:
# services still exists
hmipc_services = hass.services.async_services()[HMIPC_DOMAIN]
assert len(hmipc_services) == 8
assert len(hmipc_services) == 9
# unload the second AP
await hass.config_entries.async_unload(config_entries[1].entry_id)