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
1 change: 1 addition & 0 deletions .strict-typing
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ homeassistant.components.russound_rio.*
homeassistant.components.ruuvi_gateway.*
homeassistant.components.ruuvitag_ble.*
homeassistant.components.samsungtv.*
homeassistant.components.saunum.*
homeassistant.components.scene.*
homeassistant.components.schedule.*
homeassistant.components.schlage.*
Expand Down
7 changes: 4 additions & 3 deletions CODEOWNERS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions homeassistant/components/airobot/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Platform.CLIMATE,
Platform.NUMBER,
Platform.SENSOR,
Platform.SWITCH,
]


Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/airobot/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ class AirobotClimate(AirobotEntity, ClimateEntity):
_attr_min_temp = SETPOINT_TEMP_MIN
_attr_max_temp = SETPOINT_TEMP_MAX

def __init__(self, coordinator) -> None:
"""Initialize the climate entity."""
super().__init__(coordinator)
self._attr_unique_id = coordinator.data.status.device_id

@property
def _status(self) -> ThermostatStatus:
"""Get status from coordinator data."""
Expand Down
2 changes: 0 additions & 2 deletions homeassistant/components/airobot/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ def __init__(
status = coordinator.data.status
settings = coordinator.data.settings

self._attr_unique_id = status.device_id

connections = set()
if (mac := coordinator.config_entry.data.get(CONF_MAC)) is not None:
connections.add((CONNECTION_NETWORK_MAC, mac))
Expand Down
8 changes: 8 additions & 0 deletions homeassistant/components/airobot/icons.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
"hysteresis_band": {
"default": "mdi:delta"
}
},
"switch": {
"actuator_exercise_disabled": {
"default": "mdi:valve"
},
"child_lock": {
"default": "mdi:lock"
}
}
}
}
14 changes: 14 additions & 0 deletions homeassistant/components/airobot/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@
"heating_uptime": {
"name": "Heating uptime"
}
},
"switch": {
"actuator_exercise_disabled": {
"name": "Actuator exercise disabled"
},
"child_lock": {
"name": "Child lock"
}
}
},
"exceptions": {
Expand All @@ -105,6 +113,12 @@
},
"set_value_failed": {
"message": "Failed to set value: {error}"
},
"switch_turn_off_failed": {
"message": "Failed to turn off {switch}."
},
"switch_turn_on_failed": {
"message": "Failed to turn on {switch}."
}
}
}
118 changes: 118 additions & 0 deletions homeassistant/components/airobot/switch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Switch platform for Airobot thermostat."""

from __future__ import annotations

from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any

from pyairobotrest.exceptions import AirobotError

from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import AirobotConfigEntry
from .const import DOMAIN
from .coordinator import AirobotDataUpdateCoordinator
from .entity import AirobotEntity

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class AirobotSwitchEntityDescription(SwitchEntityDescription):
"""Describes Airobot switch entity."""

is_on_fn: Callable[[AirobotDataUpdateCoordinator], bool]
turn_on_fn: Callable[[AirobotDataUpdateCoordinator], Coroutine[Any, Any, None]]
turn_off_fn: Callable[[AirobotDataUpdateCoordinator], Coroutine[Any, Any, None]]


SWITCH_TYPES: tuple[AirobotSwitchEntityDescription, ...] = (
AirobotSwitchEntityDescription(
key="child_lock",
translation_key="child_lock",
entity_category=EntityCategory.CONFIG,
is_on_fn=lambda coordinator: (
coordinator.data.settings.setting_flags.childlock_enabled
),
turn_on_fn=lambda coordinator: coordinator.client.set_child_lock(True),
turn_off_fn=lambda coordinator: coordinator.client.set_child_lock(False),
),
AirobotSwitchEntityDescription(
key="actuator_exercise_disabled",
translation_key="actuator_exercise_disabled",
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
is_on_fn=lambda coordinator: (
coordinator.data.settings.setting_flags.actuator_exercise_disabled
),
turn_on_fn=lambda coordinator: coordinator.client.toggle_actuator_exercise(
True
),
turn_off_fn=lambda coordinator: coordinator.client.toggle_actuator_exercise(
False
),
),
)


async def async_setup_entry(
hass: HomeAssistant,
entry: AirobotConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Airobot switch entities."""
coordinator = entry.runtime_data

async_add_entities(
AirobotSwitch(coordinator, description) for description in SWITCH_TYPES
)


class AirobotSwitch(AirobotEntity, SwitchEntity):
"""Representation of an Airobot switch."""

entity_description: AirobotSwitchEntityDescription

def __init__(
self,
coordinator: AirobotDataUpdateCoordinator,
description: AirobotSwitchEntityDescription,
) -> None:
"""Initialize the switch."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator.data.status.device_id}_{description.key}"

@property
def is_on(self) -> bool:
"""Return true if the switch is on."""
return self.entity_description.is_on_fn(self.coordinator)

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""
try:
await self.entity_description.turn_on_fn(self.coordinator)
except AirobotError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="switch_turn_on_failed",
translation_placeholders={"switch": self.entity_description.key},
) from err
await self.coordinator.async_request_refresh()

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the switch off."""
try:
await self.entity_description.turn_off_fn(self.coordinator)
except AirobotError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="switch_turn_off_failed",
translation_placeholders={"switch": self.entity_description.key},
) from err
await self.coordinator.async_request_refresh()
29 changes: 25 additions & 4 deletions homeassistant/components/jvc_projector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorTimeoutError

from homeassistant.const import (
CONF_HOST,
Expand All @@ -11,8 +11,9 @@
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.entity_registry import RegistryEntry, async_migrate_entries

from .coordinator import JVCConfigEntry, JvcProjectorDataUpdateCoordinator

Expand All @@ -28,8 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool:
)

try:
await device.connect(True)
except JvcProjectorConnectError as err:
await device.connect()
except JvcProjectorTimeoutError as err:
await device.disconnect()
raise ConfigEntryNotReady(
f"Unable to connect to {entry.data[CONF_HOST]}"
Expand All @@ -50,6 +51,8 @@ async def disconnect(event: Event) -> None:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, disconnect)
)

await async_migrate_entities(hass, entry, coordinator)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

return True
Expand All @@ -60,3 +63,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: JVCConfigEntry) -> bool
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await entry.runtime_data.device.disconnect()
return unload_ok


async def async_migrate_entities(
hass: HomeAssistant,
config_entry: JVCConfigEntry,
coordinator: JvcProjectorDataUpdateCoordinator,
) -> None:
"""Migrate old entities as needed."""

@callback
def _update_entry(entry: RegistryEntry) -> dict[str, str] | None:
"""Fix unique_id of power binary_sensor entry."""
if entry.domain == Platform.BINARY_SENSOR and ":" not in entry.unique_id:
if "_power" in entry.unique_id:
return {"new_unique_id": f"{coordinator.unique_id}_power"}
return None

await async_migrate_entries(hass, config_entry.entry_id, _update_entry)
14 changes: 7 additions & 7 deletions homeassistant/components/jvc_projector/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

from __future__ import annotations

from jvcprojector import const
from jvcprojector import command as cmd

from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import POWER
from .coordinator import JVCConfigEntry, JvcProjectorDataUpdateCoordinator
from .entity import JvcProjectorEntity

ON_STATUS = (const.ON, const.WARMING)
ON_STATUS = (cmd.Power.ON, cmd.Power.WARMING)


async def async_setup_entry(
Expand All @@ -21,24 +22,23 @@ async def async_setup_entry(
) -> None:
"""Set up the JVC Projector platform from a config entry."""
coordinator = entry.runtime_data

async_add_entities([JvcBinarySensor(coordinator)])


class JvcBinarySensor(JvcProjectorEntity, BinarySensorEntity):
"""The entity class for JVC Projector Binary Sensor."""

_attr_translation_key = "jvc_power"
_attr_translation_key = "power"

def __init__(
self,
coordinator: JvcProjectorDataUpdateCoordinator,
) -> None:
"""Initialize the JVC Projector sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.device.mac}_power"
self._attr_unique_id = f"{coordinator.unique_id}_power"

@property
def is_on(self) -> bool:
"""Return true if the JVC is on."""
return self.coordinator.data["power"] in ON_STATUS
"""Return true if the JVC Projector is on."""
return self.coordinator.data[POWER] in ON_STATUS
15 changes: 10 additions & 5 deletions homeassistant/components/jvc_projector/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from collections.abc import Mapping
from typing import Any

from jvcprojector import JvcProjector, JvcProjectorAuthError, JvcProjectorConnectError
from jvcprojector import (
JvcProjector,
JvcProjectorAuthError,
JvcProjectorTimeoutError,
command as cmd,
)
from jvcprojector.projector import DEFAULT_PORT
import voluptuous as vol

Expand Down Expand Up @@ -40,7 +45,7 @@ async def async_step_user(
mac = await get_mac_address(host, port, password)
except InvalidHost:
errors["base"] = "invalid_host"
except JvcProjectorConnectError:
except JvcProjectorTimeoutError:
errors["base"] = "cannot_connect"
except JvcProjectorAuthError:
errors["base"] = "invalid_auth"
Expand Down Expand Up @@ -91,7 +96,7 @@ async def async_step_reauth_confirm(

try:
await get_mac_address(host, port, password)
except JvcProjectorConnectError:
except JvcProjectorTimeoutError:
errors["base"] = "cannot_connect"
except JvcProjectorAuthError:
errors["base"] = "invalid_auth"
Expand All @@ -115,7 +120,7 @@ async def get_mac_address(host: str, port: int, password: str | None) -> str:
"""Get device mac address for config flow."""
device = JvcProjector(host, port=port, password=password)
try:
await device.connect(True)
await device.connect()
return await device.get(cmd.MacAddress)
finally:
await device.disconnect()
return device.mac
4 changes: 4 additions & 0 deletions homeassistant/components/jvc_projector/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
NAME = "JVC Projector"
DOMAIN = "jvc_projector"
MANUFACTURER = "JVC"

POWER = "power"
INPUT = "input"
SOURCE = "source"
Loading
Loading