Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions homeassistant/components/aws_s3/diagnostics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Diagnostics support for AWS S3."""

from __future__ import annotations

import dataclasses
from typing import Any

from homeassistant.components.backup import (
DATA_MANAGER as BACKUP_DATA_MANAGER,
BackupManager,
)
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant

from .const import (
CONF_ACCESS_KEY_ID,
CONF_BUCKET,
CONF_PREFIX,
CONF_SECRET_ACCESS_KEY,
DOMAIN,
)
from .coordinator import S3ConfigEntry
from .helpers import async_list_backups_from_s3

TO_REDACT = (CONF_ACCESS_KEY_ID, CONF_SECRET_ACCESS_KEY)


async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: S3ConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
backup_manager: BackupManager = hass.data[BACKUP_DATA_MANAGER]
backups = await async_list_backups_from_s3(
coordinator.client,
bucket=entry.data[CONF_BUCKET],
prefix=entry.data.get(CONF_PREFIX, ""),
)

data = {
"coordinator_data": dataclasses.asdict(coordinator.data),
"config": {
**entry.data,
**entry.options,
},
"backup_agents": [
{"name": agent.name}
for agent in backup_manager.backup_agents.values()
if agent.domain == DOMAIN
],
"backup": [backup.as_dict() for backup in backups],
}

return async_redact_data(data, TO_REDACT)
2 changes: 1 addition & 1 deletion homeassistant/components/aws_s3/quality_scale.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ rules:

# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: S3 is a cloud service that is not discovered on the network.
Expand Down
42 changes: 30 additions & 12 deletions homeassistant/components/evohome/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util

from .const import ATTR_DURATION, ATTR_PERIOD, ATTR_SETPOINT, EVOHOME_DATA, EvoService
from .const import ATTR_DURATION, ATTR_PERIOD, DOMAIN, EVOHOME_DATA, EvoService
from .coordinator import EvoDataUpdateCoordinator
from .entity import EvoChild, EvoEntity

Expand Down Expand Up @@ -132,6 +132,24 @@ class EvoClimateEntity(EvoEntity, ClimateEntity):
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT]
_attr_temperature_unit = UnitOfTemperature.CELSIUS

async def async_clear_zone_override(self) -> None:
"""Clear the zone override; only supported by zones."""
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="zone_only_service",
translation_placeholders={"service": EvoService.CLEAR_ZONE_OVERRIDE},
)

async def async_set_zone_override(
self, setpoint: float, duration: timedelta | None = None
) -> None:
"""Set the zone override; only supported by zones."""
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="zone_only_service",
translation_placeholders={"service": EvoService.SET_ZONE_OVERRIDE},
)


class EvoZone(EvoChild, EvoClimateEntity):
"""Base for any evohome-compatible heating zone."""
Expand Down Expand Up @@ -170,22 +188,22 @@ def __init__(
| ClimateEntityFeature.TURN_ON
)

async def async_zone_svc_request(self, service: str, data: dict[str, Any]) -> None:
"""Process a service request (setpoint override) for a zone."""
if service == EvoService.CLEAR_ZONE_OVERRIDE:
await self.coordinator.call_client_api(self._evo_device.reset())
return
async def async_clear_zone_override(self) -> None:
"""Clear the zone's override, if any."""
await self.coordinator.call_client_api(self._evo_device.reset())

# otherwise it is EvoService.SET_ZONE_OVERRIDE
temperature = max(min(data[ATTR_SETPOINT], self.max_temp), self.min_temp)
async def async_set_zone_override(
self, setpoint: float, duration: timedelta | None = None
) -> None:
"""Set the zone's override (mode/setpoint)."""
temperature = max(min(setpoint, self.max_temp), self.min_temp)

if ATTR_DURATION in data:
duration: timedelta = data[ATTR_DURATION]
if duration is not None:
if duration.total_seconds() == 0:
await self._update_schedule()
until = self.setpoints.get("next_sp_from")
else:
until = dt_util.now() + data[ATTR_DURATION]
until = dt_util.now() + duration
else:
until = None # indefinitely

Expand Down
12 changes: 1 addition & 11 deletions homeassistant/components/evohome/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN, EvoService
from .const import DOMAIN
from .coordinator import EvoDataUpdateCoordinator

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -47,22 +47,12 @@ async def process_signal(self, payload: dict | None = None) -> None:
raise NotImplementedError
if payload["unique_id"] != self._attr_unique_id:
return
if payload["service"] in (
EvoService.SET_ZONE_OVERRIDE,
EvoService.CLEAR_ZONE_OVERRIDE,
):
await self.async_zone_svc_request(payload["service"], payload["data"])
return
await self.async_tcs_svc_request(payload["service"], payload["data"])

async def async_tcs_svc_request(self, service: str, data: dict[str, Any]) -> None:
"""Process a service request (system mode) for a controller."""
raise NotImplementedError

async def async_zone_svc_request(self, service: str, data: dict[str, Any]) -> None:
"""Process a service request (setpoint override) for a zone."""
raise NotImplementedError

@property
def extra_state_attributes(self) -> Mapping[str, Any]:
"""Return the evohome-specific state attributes."""
Expand Down
92 changes: 37 additions & 55 deletions homeassistant/components/evohome/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

from datetime import timedelta
from typing import Final
from typing import Any, Final

from evohomeasync2.const import SZ_CAN_BE_TEMPORARY, SZ_SYSTEM_MODE, SZ_TIMING_MODE
from evohomeasync2.schemas.const import (
Expand All @@ -13,9 +13,10 @@
)
import voluptuous as vol

from homeassistant.const import ATTR_ENTITY_ID, ATTR_MODE
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.const import ATTR_MODE
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers import config_validation as cv, service
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.service import verify_domain_control

Expand All @@ -25,21 +26,38 @@
# system mode schemas are built dynamically when the services are registered
# because supported modes can vary for edge-case systems

CLEAR_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
{vol.Required(ATTR_ENTITY_ID): cv.entity_id}
)
SET_ZONE_OVERRIDE_SCHEMA: Final = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_SETPOINT): vol.All(
vol.Coerce(float), vol.Range(min=4.0, max=35.0)
),
vol.Optional(ATTR_DURATION): vol.All(
cv.time_period,
vol.Range(min=timedelta(days=0), max=timedelta(days=1)),
),
}
)
# Zone service schemas (registered as entity services)
CLEAR_ZONE_OVERRIDE_SCHEMA: Final[dict[str | vol.Marker, Any]] = {}
SET_ZONE_OVERRIDE_SCHEMA: Final[dict[str | vol.Marker, Any]] = {
vol.Required(ATTR_SETPOINT): vol.All(
vol.Coerce(float), vol.Range(min=4.0, max=35.0)
),
vol.Optional(ATTR_DURATION): vol.All(
cv.time_period,
vol.Range(min=timedelta(days=0), max=timedelta(days=1)),
),
}


def _register_zone_entity_services(hass: HomeAssistant) -> None:
"""Register entity-level services for zones."""

service.async_register_platform_entity_service(
hass,
DOMAIN,
EvoService.CLEAR_ZONE_OVERRIDE,
entity_domain=CLIMATE_DOMAIN,
schema=CLEAR_ZONE_OVERRIDE_SCHEMA,
func="async_clear_zone_override",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
EvoService.SET_ZONE_OVERRIDE,
entity_domain=CLIMATE_DOMAIN,
schema=SET_ZONE_OVERRIDE_SCHEMA,
func="async_set_zone_override",
)


@callback
Expand All @@ -51,8 +69,6 @@ def setup_service_functions(
Not all Honeywell TCC-compatible systems support all operating modes. In addition,
each mode will require any of four distinct service schemas. This has to be
enumerated before registering the appropriate handlers.

It appears that all TCC-compatible systems support the same three zones modes.
"""

@verify_domain_control(DOMAIN)
Expand All @@ -72,28 +88,6 @@ async def set_system_mode(call: ServiceCall) -> None:
}
async_dispatcher_send(hass, DOMAIN, payload)

@verify_domain_control(DOMAIN)
async def set_zone_override(call: ServiceCall) -> None:
"""Set the zone override (setpoint)."""
entity_id = call.data[ATTR_ENTITY_ID]

registry = er.async_get(hass)
registry_entry = registry.async_get(entity_id)

if registry_entry is None or registry_entry.platform != DOMAIN:
raise ValueError(f"'{entity_id}' is not a known {DOMAIN} entity")

if registry_entry.domain != "climate":
raise ValueError(f"'{entity_id}' is not an {DOMAIN} controller/zone")

payload = {
"unique_id": registry_entry.unique_id,
"service": call.service,
"data": call.data,
}

async_dispatcher_send(hass, DOMAIN, payload)

assert coordinator.tcs is not None # mypy

hass.services.async_register(DOMAIN, EvoService.REFRESH_SYSTEM, force_refresh)
Expand Down Expand Up @@ -156,16 +150,4 @@ async def set_zone_override(call: ServiceCall) -> None:
schema=vol.Schema(vol.Any(*system_mode_schemas)),
)

# The zone modes are consistent across all systems and use the same schema
hass.services.async_register(
DOMAIN,
EvoService.CLEAR_ZONE_OVERRIDE,
set_zone_override,
schema=CLEAR_ZONE_OVERRIDE_SCHEMA,
)
hass.services.async_register(
DOMAIN,
EvoService.SET_ZONE_OVERRIDE,
set_zone_override,
schema=SET_ZONE_OVERRIDE_SCHEMA,
)
_register_zone_entity_services(hass)
22 changes: 8 additions & 14 deletions homeassistant/components/evohome/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,11 @@ reset_system:
refresh_system:

set_zone_override:
target:
entity:
integration: evohome
domain: climate
fields:
entity_id:
required: true
example: climate.bathroom
selector:
entity:
integration: evohome
domain: climate
setpoint:
required: true
selector:
Expand All @@ -49,10 +46,7 @@ set_zone_override:
object:

clear_zone_override:
fields:
entity_id:
required: true
selector:
entity:
integration: evohome
domain: climate
target:
entity:
integration: evohome
domain: climate
15 changes: 5 additions & 10 deletions homeassistant/components/evohome/strings.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
{
"exceptions": {
"zone_only_service": {
"message": "Only zones support the `{service}` service"
}
},
"services": {
"clear_zone_override": {
"description": "Sets a zone to follow its schedule.",
"fields": {
"entity_id": {
"description": "[%key:component::evohome::services::set_zone_override::fields::entity_id::description%]",
"name": "[%key:component::evohome::services::set_zone_override::fields::entity_id::name%]"
}
},
"name": "Clear zone override"
},
"refresh_system": {
Expand Down Expand Up @@ -43,10 +42,6 @@
"description": "The zone will revert to its schedule after this time. If 0 the change is until the next scheduled setpoint.",
"name": "Duration"
},
"entity_id": {
"description": "The entity ID of the Evohome zone.",
"name": "Entity"
},
"setpoint": {
"description": "The temperature to be used instead of the scheduled setpoint.",
"name": "Setpoint"
Expand Down
16 changes: 16 additions & 0 deletions homeassistant/components/proxmoxve/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
"username": "[%key:common::config_flow::data::username%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"host": "[%key:component::proxmoxve::config::step::user::data_description::host%]",
"password": "[%key:component::proxmoxve::config::step::user::data_description::password%]",
"port": "[%key:component::proxmoxve::config::step::user::data_description::port%]",
"realm": "[%key:component::proxmoxve::config::step::user::data_description::realm%]",
"username": "[%key:component::proxmoxve::config::step::user::data_description::username%]",
"verify_ssl": "[%key:component::proxmoxve::config::step::user::data_description::verify_ssl%]"
},
"description": "Use the following form to reconfigure your Proxmox VE server connection.",
"title": "Reconfigure Proxmox VE integration"
},
Expand All @@ -44,6 +52,14 @@
"username": "[%key:common::config_flow::data::username%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"host": "The hostname or IP address of your Proxmox VE server",
"password": "The password for the Proxmox VE server",
"port": "The port of your Proxmox VE server (default: 8006)",
"realm": "The authentication realm for the Proxmox VE server (default: 'pam')",
"username": "The username for the Proxmox VE server",
"verify_ssl": "Whether to verify SSL certificates. Disable only if you have a self-signed certificate"
},
"description": "Enter your Proxmox VE server details to set up the integration.",
"title": "Connect to Proxmox VE"
}
Expand Down
Loading
Loading