<commit_before>"""Climate support for Shelly."""
from __future__ import annotations

import asyncio
from collections.abc import Mapping
from typing import Any, cast

from aioshelly.block_device import Block
import async_timeout

from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity
from homeassistant.components.climate.const import (
    PRESET_NONE,
    ClimateEntityFeature,
    HVACAction,
    HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers import device_registry, entity_registry, update_coordinator
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity

from . import BlockDeviceWrapper
from .const import (
    AIOSHELLY_DEVICE_TIMEOUT_SEC,
    BLOCK,
    DATA_CONFIG_ENTRY,
    DOMAIN,
    LOGGER,
    SHTRV_01_TEMPERATURE_SETTINGS,
)
from .utils import get_device_entry_gen


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up climate device."""

    if get_device_entry_gen(config_entry) == 2:
        return

    wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
        config_entry.entry_id
    ][BLOCK]

    if wrapper.device.initialized:
        async_setup_climate_entities(async_add_entities, wrapper)
    else:
        async_restore_climate_entities(hass, config_entry, async_add_entities, wrapper)


@callback
def async_setup_climate_entities(
    async_add_entities: AddEntitiesCallback,
    wrapper: BlockDeviceWrapper,
) -> None:
    """Set up online climate devices."""

    device_block: Block | None = None
    sensor_block: Block | None = None

    assert wrapper.device.blocks

    for block in wrapper.device.blocks:
        if block.type == "device":
            device_block = block
        if hasattr(block, "targetTemp"):
            sensor_block = block

    if sensor_block and device_block:
        LOGGER.debug("Setup online climate device %s", wrapper.name)
        async_add_entities([BlockSleepingClimate(wrapper, sensor_block, device_block)])


@callback
def async_restore_climate_entities(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
    wrapper: BlockDeviceWrapper,
) -> None:
    """Restore sleeping climate devices."""

    ent_reg = entity_registry.async_get(hass)
    entries = entity_registry.async_entries_for_config_entry(
        ent_reg, config_entry.entry_id
    )

    for entry in entries:

        if entry.domain != CLIMATE_DOMAIN:
            continue

        LOGGER.debug("Setup sleeping climate device %s", wrapper.name)
        LOGGER.debug("Found entry %s [%s]", entry.original_name, entry.domain)
        async_add_entities([BlockSleepingClimate(wrapper, None, None, entry)])
        break


class BlockSleepingClimate(
    update_coordinator.CoordinatorEntity,
    RestoreEntity,
    ClimateEntity,
):
    """Representation of a Shelly climate device."""

    _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT]
    _attr_icon = "mdi:thermostat"
    _attr_max_temp = SHTRV_01_TEMPERATURE_SETTINGS["max"]
    _attr_min_temp = SHTRV_01_TEMPERATURE_SETTINGS["min"]
    _attr_supported_features: int = (
        ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
    )
    _attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"]
    _attr_temperature_unit = TEMP_CELSIUS

    def __init__(
        self,
        wrapper: BlockDeviceWrapper,
        sensor_block: Block | None,
        device_block: Block | None,
        entry: entity_registry.RegistryEntry | None = None,
    ) -> None:
        """Initialize climate."""

        super().__init__(wrapper)

        self.wrapper = wrapper
        self.block: Block | None = sensor_block
        self.control_result: dict[str, Any] | None = None
        self.device_block: Block | None = device_block
        self.last_state: State | None = None
        self.last_state_attributes: Mapping[str, Any]
        self._preset_modes: list[str] = []

        if self.block is not None and self.device_block is not None:
            self._unique_id = f"{self.wrapper.mac}-{self.block.description}"
            assert self.block.channel
            self._preset_modes = [
                PRESET_NONE,
                *wrapper.device.settings["thermostats"][int(self.block.channel)][
                    "schedule_profile_names"
                ],
            ]
        elif entry is not None:
            self._unique_id = entry.unique_id

        self._channel = cast(int, self._unique_id.split("_")[1])

    @property
    def unique_id(self) -> str:
        """Set unique id of entity."""
        return self._unique_id

    @property
    def name(self) -> str:
        """Name of entity."""
        return self.wrapper.name

    @property
    def target_temperature(self) -> float | None:
        """Set target temperature."""
        if self.block is not None:
            return cast(float, self.block.targetTemp)
        return self.last_state_attributes.get("temperature")

    @property
    def current_temperature(self) -> float | None:
        """Return current temperature."""
        if self.block is not None:
            return cast(float, self.block.temp)
        return self.last_state_attributes.get("current_temperature")

    @property
    def available(self) -> bool:
        """Device availability."""
        if self.device_block is not None:
            return not cast(bool, self.device_block.valveError)
        return self.wrapper.last_update_success

    @property
    def hvac_mode(self) -> HVACMode:
        """HVAC current mode."""
        if self.device_block is None:
            return HVACMode(self.last_state.state) if self.last_state else HVACMode.OFF
        if self.device_block.mode is None or self._check_is_off():
            return HVACMode.OFF

        return HVACMode.HEAT

    @property
    def preset_mode(self) -> str | None:
        """Preset current mode."""
        if self.device_block is None:
            return self.last_state_attributes.get("preset_mode")<commit_msg>Protect against an exception in Shelly climate platform (#77102)<commit_after>"""Climate support for Shelly."""
from __future__ import annotations

import asyncio
from collections.abc import Mapping
from typing import Any, cast

from aioshelly.block_device import Block
import async_timeout

from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN, ClimateEntity
from homeassistant.components.climate.const import (
    PRESET_NONE,
    ClimateEntityFeature,
    HVACAction,
    HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers import device_registry, entity_registry, update_coordinator
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity

from . import BlockDeviceWrapper
from .const import (
    AIOSHELLY_DEVICE_TIMEOUT_SEC,
    BLOCK,
    DATA_CONFIG_ENTRY,
    DOMAIN,
    LOGGER,
    SHTRV_01_TEMPERATURE_SETTINGS,
)
from .utils import get_device_entry_gen


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up climate device."""

    if get_device_entry_gen(config_entry) == 2:
        return

    wrapper: BlockDeviceWrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][
        config_entry.entry_id
    ][BLOCK]

    if wrapper.device.initialized:
        async_setup_climate_entities(async_add_entities, wrapper)
    else:
        async_restore_climate_entities(hass, config_entry, async_add_entities, wrapper)


@callback
def async_setup_climate_entities(
    async_add_entities: AddEntitiesCallback,
    wrapper: BlockDeviceWrapper,
) -> None:
    """Set up online climate devices."""

    device_block: Block | None = None
    sensor_block: Block | None = None

    assert wrapper.device.blocks

    for block in wrapper.device.blocks:
        if block.type == "device":
            device_block = block
        if hasattr(block, "targetTemp"):
            sensor_block = block

    if sensor_block and device_block:
        LOGGER.debug("Setup online climate device %s", wrapper.name)
        async_add_entities([BlockSleepingClimate(wrapper, sensor_block, device_block)])


@callback
def async_restore_climate_entities(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddEntitiesCallback,
    wrapper: BlockDeviceWrapper,
) -> None:
    """Restore sleeping climate devices."""

    ent_reg = entity_registry.async_get(hass)
    entries = entity_registry.async_entries_for_config_entry(
        ent_reg, config_entry.entry_id
    )

    for entry in entries:

        if entry.domain != CLIMATE_DOMAIN:
            continue

        LOGGER.debug("Setup sleeping climate device %s", wrapper.name)
        LOGGER.debug("Found entry %s [%s]", entry.original_name, entry.domain)
        async_add_entities([BlockSleepingClimate(wrapper, None, None, entry)])
        break


class BlockSleepingClimate(
    update_coordinator.CoordinatorEntity,
    RestoreEntity,
    ClimateEntity,
):
    """Representation of a Shelly climate device."""

    _attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT]
    _attr_icon = "mdi:thermostat"
    _attr_max_temp = SHTRV_01_TEMPERATURE_SETTINGS["max"]
    _attr_min_temp = SHTRV_01_TEMPERATURE_SETTINGS["min"]
    _attr_supported_features: int = (
        ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
    )
    _attr_target_temperature_step = SHTRV_01_TEMPERATURE_SETTINGS["step"]
    _attr_temperature_unit = TEMP_CELSIUS

    def __init__(
        self,
        wrapper: BlockDeviceWrapper,
        sensor_block: Block | None,
        device_block: Block | None,
        entry: entity_registry.RegistryEntry | None = None,
    ) -> None:
        """Initialize climate."""

        super().__init__(wrapper)

        self.wrapper = wrapper
        self.block: Block | None = sensor_block
        self.control_result: dict[str, Any] | None = None
        self.device_block: Block | None = device_block
        self.last_state: State | None = None
        self.last_state_attributes: Mapping[str, Any]
        self._preset_modes: list[str] = []

        if self.block is not None and self.device_block is not None:
            self._unique_id = f"{self.wrapper.mac}-{self.block.description}"
            assert self.block.channel
            self._preset_modes = [
                PRESET_NONE,
                *wrapper.device.settings["thermostats"][int(self.block.channel)][
                    "schedule_profile_names"
                ],
            ]
        elif entry is not None:
            self._unique_id = entry.unique_id

        self._channel = cast(int, self._unique_id.split("_")[1])

    @property
    def unique_id(self) -> str:
        """Set unique id of entity."""
        return self._unique_id

    @property
    def name(self) -> str:
        """Name of entity."""
        return self.wrapper.name

    @property
    def target_temperature(self) -> float | None:
        """Set target temperature."""
        if self.block is not None:
            return cast(float, self.block.targetTemp)
        return self.last_state_attributes.get("temperature")

    @property
    def current_temperature(self) -> float | None:
        """Return current temperature."""
        if self.block is not None:
            return cast(float, self.block.temp)
        return self.last_state_attributes.get("current_temperature")

    @property
    def available(self) -> bool:
        """Device availability."""
        if self.device_block is not None:
            return not cast(bool, self.device_block.valveError)
        return self.wrapper.last_update_success

    @property
    def hvac_mode(self) -> HVACMode:
        """HVAC current mode."""
        if self.device_block is None:
            if self.last_state and self.last_state.state in list(HVACMode):
                return HVACMode(self.last_state.state)
            return HVACMode.OFF

        if self.device_block.mode is None or self._check_is_off():
            return HVACMode.OFF

        return HVACMode.HEAT

    @property
    def preset_mode(self) -> str | None:
        """Preset current mode."""
        if self.device_block is None:
            return self.last_state_attributes.get("preset_mode")
{'fraction_non_alphanumeric': 0.08582756744149887, 'fraction_numerical': 0.0019435590453237969, 'mean_word_length': 3.0554854981084487, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4812162', 'n_tokens_mistral': 3808, 'n_tokens_neox': 3731, 'n_words': 871}