mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Add more typing to HomeKit (#101896)
This commit is contained in:
parent
c4ce900567
commit
cc3d1a11bd
@ -36,7 +36,7 @@ class IIDStorage(Store):
|
|||||||
old_major_version: int,
|
old_major_version: int,
|
||||||
old_minor_version: int,
|
old_minor_version: int,
|
||||||
old_data: dict,
|
old_data: dict,
|
||||||
):
|
) -> dict:
|
||||||
"""Migrate to the new version."""
|
"""Migrate to the new version."""
|
||||||
if old_major_version == 1:
|
if old_major_version == 1:
|
||||||
# Convert v1 to v2 format which uses a unique iid set per accessory
|
# Convert v1 to v2 format which uses a unique iid set per accessory
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from haffmpeg.core import FFMPEG_STDERR, HAFFmpeg
|
from haffmpeg.core import FFMPEG_STDERR, HAFFmpeg
|
||||||
from pyhap.camera import (
|
from pyhap.camera import (
|
||||||
@ -14,7 +15,7 @@ from pyhap.const import CATEGORY_CAMERA
|
|||||||
from homeassistant.components import camera
|
from homeassistant.components import camera
|
||||||
from homeassistant.components.ffmpeg import get_ffmpeg_manager
|
from homeassistant.components.ffmpeg import get_ffmpeg_manager
|
||||||
from homeassistant.const import STATE_ON
|
from homeassistant.const import STATE_ON
|
||||||
from homeassistant.core import State, callback
|
from homeassistant.core import HomeAssistant, State, callback
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
EventStateChangedData,
|
EventStateChangedData,
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
@ -22,7 +23,7 @@ from homeassistant.helpers.event import (
|
|||||||
)
|
)
|
||||||
from homeassistant.helpers.typing import EventType
|
from homeassistant.helpers.typing import EventType
|
||||||
|
|
||||||
from .accessories import TYPES, HomeAccessory
|
from .accessories import TYPES, HomeAccessory, HomeDriver
|
||||||
from .const import (
|
from .const import (
|
||||||
CHAR_MOTION_DETECTED,
|
CHAR_MOTION_DETECTED,
|
||||||
CHAR_MUTE,
|
CHAR_MUTE,
|
||||||
@ -141,7 +142,15 @@ CONFIG_DEFAULTS = {
|
|||||||
class Camera(HomeAccessory, PyhapCamera):
|
class Camera(HomeAccessory, PyhapCamera):
|
||||||
"""Generate a Camera accessory."""
|
"""Generate a Camera accessory."""
|
||||||
|
|
||||||
def __init__(self, hass, driver, name, entity_id, aid, config):
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
driver: HomeDriver,
|
||||||
|
name: str,
|
||||||
|
entity_id: str,
|
||||||
|
aid: int,
|
||||||
|
config: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
"""Initialize a Camera accessory object."""
|
"""Initialize a Camera accessory object."""
|
||||||
self._ffmpeg = get_ffmpeg_manager(hass)
|
self._ffmpeg = get_ffmpeg_manager(hass)
|
||||||
for config_key, conf in CONFIG_DEFAULTS.items():
|
for config_key, conf in CONFIG_DEFAULTS.items():
|
||||||
@ -242,12 +251,13 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
|
|
||||||
self._async_update_doorbell_state(state)
|
self._async_update_doorbell_state(state)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self) -> None:
|
||||||
"""Handle accessory driver started event.
|
"""Handle accessory driver started event.
|
||||||
|
|
||||||
Run inside the Home Assistant event loop.
|
Run inside the Home Assistant event loop.
|
||||||
"""
|
"""
|
||||||
if self._char_motion_detected:
|
if self._char_motion_detected:
|
||||||
|
assert self.linked_motion_sensor
|
||||||
self._subscriptions.append(
|
self._subscriptions.append(
|
||||||
async_track_state_change_event(
|
async_track_state_change_event(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -257,6 +267,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self._char_doorbell_detected:
|
if self._char_doorbell_detected:
|
||||||
|
assert self.linked_doorbell_sensor
|
||||||
self._subscriptions.append(
|
self._subscriptions.append(
|
||||||
async_track_state_change_event(
|
async_track_state_change_event(
|
||||||
self.hass,
|
self.hass,
|
||||||
@ -282,6 +293,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
return
|
return
|
||||||
|
|
||||||
detected = new_state.state == STATE_ON
|
detected = new_state.state == STATE_ON
|
||||||
|
assert self._char_motion_detected
|
||||||
if self._char_motion_detected.value == detected:
|
if self._char_motion_detected.value == detected:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -307,6 +319,8 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
if not new_state:
|
if not new_state:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
assert self._char_doorbell_detected
|
||||||
|
assert self._char_doorbell_detected_switch
|
||||||
if new_state.state == STATE_ON:
|
if new_state.state == STATE_ON:
|
||||||
self._char_doorbell_detected.set_value(DOORBELL_SINGLE_PRESS)
|
self._char_doorbell_detected.set_value(DOORBELL_SINGLE_PRESS)
|
||||||
self._char_doorbell_detected_switch.set_value(DOORBELL_SINGLE_PRESS)
|
self._char_doorbell_detected_switch.set_value(DOORBELL_SINGLE_PRESS)
|
||||||
@ -318,11 +332,10 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State | None) -> None:
|
||||||
"""Handle state change to update HomeKit value."""
|
"""Handle state change to update HomeKit value."""
|
||||||
pass # pylint: disable=unnecessary-pass
|
|
||||||
|
|
||||||
async def _async_get_stream_source(self):
|
async def _async_get_stream_source(self) -> str | None:
|
||||||
"""Find the camera stream source url."""
|
"""Find the camera stream source url."""
|
||||||
if stream_source := self.config.get(CONF_STREAM_SOURCE):
|
if stream_source := self.config.get(CONF_STREAM_SOURCE):
|
||||||
return stream_source
|
return stream_source
|
||||||
@ -337,7 +350,9 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
)
|
)
|
||||||
return stream_source
|
return stream_source
|
||||||
|
|
||||||
async def start_stream(self, session_info, stream_config):
|
async def start_stream(
|
||||||
|
self, session_info: dict[str, Any], stream_config: dict[str, Any]
|
||||||
|
) -> bool:
|
||||||
"""Start a new stream with the given configuration."""
|
"""Start a new stream with the given configuration."""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"[%s] Starting stream with the following parameters: %s",
|
"[%s] Starting stream with the following parameters: %s",
|
||||||
@ -418,7 +433,9 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
|
|
||||||
return await self._async_ffmpeg_watch(session_info["id"])
|
return await self._async_ffmpeg_watch(session_info["id"])
|
||||||
|
|
||||||
async def _async_log_stderr_stream(self, stderr_reader):
|
async def _async_log_stderr_stream(
|
||||||
|
self, stderr_reader: asyncio.StreamReader
|
||||||
|
) -> None:
|
||||||
"""Log output from ffmpeg."""
|
"""Log output from ffmpeg."""
|
||||||
_LOGGER.debug("%s: ffmpeg: started", self.display_name)
|
_LOGGER.debug("%s: ffmpeg: started", self.display_name)
|
||||||
while True:
|
while True:
|
||||||
@ -428,7 +445,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
|
|
||||||
_LOGGER.debug("%s: ffmpeg: %s", self.display_name, line.rstrip())
|
_LOGGER.debug("%s: ffmpeg: %s", self.display_name, line.rstrip())
|
||||||
|
|
||||||
async def _async_ffmpeg_watch(self, session_id):
|
async def _async_ffmpeg_watch(self, session_id: str) -> bool:
|
||||||
"""Check to make sure ffmpeg is still running and cleanup if not."""
|
"""Check to make sure ffmpeg is still running and cleanup if not."""
|
||||||
ffmpeg_pid = self.sessions[session_id][FFMPEG_PID]
|
ffmpeg_pid = self.sessions[session_id][FFMPEG_PID]
|
||||||
if pid_is_alive(ffmpeg_pid):
|
if pid_is_alive(ffmpeg_pid):
|
||||||
@ -440,7 +457,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_stop_ffmpeg_watch(self, session_id):
|
def _async_stop_ffmpeg_watch(self, session_id: str) -> None:
|
||||||
"""Cleanup a streaming session after stopping."""
|
"""Cleanup a streaming session after stopping."""
|
||||||
if FFMPEG_WATCHER not in self.sessions[session_id]:
|
if FFMPEG_WATCHER not in self.sessions[session_id]:
|
||||||
return
|
return
|
||||||
@ -448,7 +465,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
self.sessions[session_id].pop(FFMPEG_LOGGER).cancel()
|
self.sessions[session_id].pop(FFMPEG_LOGGER).cancel()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_stop(self):
|
def async_stop(self) -> None:
|
||||||
"""Stop any streams when the accessory is stopped."""
|
"""Stop any streams when the accessory is stopped."""
|
||||||
for session_info in self.sessions.values():
|
for session_info in self.sessions.values():
|
||||||
self.hass.async_create_background_task(
|
self.hass.async_create_background_task(
|
||||||
@ -456,7 +473,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
)
|
)
|
||||||
super().async_stop()
|
super().async_stop()
|
||||||
|
|
||||||
async def stop_stream(self, session_info):
|
async def stop_stream(self, session_info: dict[str, Any]) -> None:
|
||||||
"""Stop the stream for the given ``session_id``."""
|
"""Stop the stream for the given ``session_id``."""
|
||||||
session_id = session_info["id"]
|
session_id = session_info["id"]
|
||||||
if not (stream := session_info.get("stream")):
|
if not (stream := session_info.get("stream")):
|
||||||
@ -467,7 +484,7 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
|
|
||||||
if not pid_is_alive(stream.process.pid):
|
if not pid_is_alive(stream.process.pid):
|
||||||
_LOGGER.info("[%s] Stream already stopped", session_id)
|
_LOGGER.info("[%s] Stream already stopped", session_id)
|
||||||
return True
|
return
|
||||||
|
|
||||||
for shutdown_method in ("close", "kill"):
|
for shutdown_method in ("close", "kill"):
|
||||||
_LOGGER.info("[%s] %s stream", session_id, shutdown_method)
|
_LOGGER.info("[%s] %s stream", session_id, shutdown_method)
|
||||||
@ -479,11 +496,13 @@ class Camera(HomeAccessory, PyhapCamera):
|
|||||||
"[%s] Failed to %s stream", session_id, shutdown_method
|
"[%s] Failed to %s stream", session_id, shutdown_method
|
||||||
)
|
)
|
||||||
|
|
||||||
async def reconfigure_stream(self, session_info, stream_config):
|
async def reconfigure_stream(
|
||||||
|
self, session_info: dict[str, Any], stream_config: dict[str, Any]
|
||||||
|
) -> bool:
|
||||||
"""Reconfigure the stream so that it uses the given ``stream_config``."""
|
"""Reconfigure the stream so that it uses the given ``stream_config``."""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def async_get_snapshot(self, image_size):
|
async def async_get_snapshot(self, image_size: dict[str, int]) -> bytes:
|
||||||
"""Return a jpeg of a snapshot from the camera."""
|
"""Return a jpeg of a snapshot from the camera."""
|
||||||
image = await camera.async_get_image(
|
image = await camera.async_get_image(
|
||||||
self.hass,
|
self.hass,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Class to hold all thermostat accessories."""
|
"""Class to hold all thermostat accessories."""
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pyhap.const import CATEGORY_HUMIDIFIER
|
from pyhap.const import CATEGORY_HUMIDIFIER
|
||||||
|
|
||||||
@ -73,7 +74,7 @@ HC_STATE_DEHUMIDIFYING = 3
|
|||||||
class HumidifierDehumidifier(HomeAccessory):
|
class HumidifierDehumidifier(HomeAccessory):
|
||||||
"""Generate a HumidifierDehumidifier accessory for a humidifier."""
|
"""Generate a HumidifierDehumidifier accessory for a humidifier."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize a HumidifierDehumidifier accessory object."""
|
"""Initialize a HumidifierDehumidifier accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_HUMIDIFIER)
|
super().__init__(*args, category=CATEGORY_HUMIDIFIER)
|
||||||
self._reload_on_change_attrs.extend(
|
self._reload_on_change_attrs.extend(
|
||||||
@ -83,8 +84,9 @@ class HumidifierDehumidifier(HomeAccessory):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.chars = []
|
self.chars: list[str] = []
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
device_class = state.attributes.get(
|
device_class = state.attributes.get(
|
||||||
ATTR_DEVICE_CLASS, HumidifierDeviceClass.HUMIDIFIER
|
ATTR_DEVICE_CLASS, HumidifierDeviceClass.HUMIDIFIER
|
||||||
)
|
)
|
||||||
@ -151,7 +153,7 @@ class HumidifierDehumidifier(HomeAccessory):
|
|||||||
if humidity_state:
|
if humidity_state:
|
||||||
self._async_update_current_humidity(humidity_state)
|
self._async_update_current_humidity(humidity_state)
|
||||||
|
|
||||||
async def run(self):
|
async def run(self) -> None:
|
||||||
"""Handle accessory driver started event.
|
"""Handle accessory driver started event.
|
||||||
|
|
||||||
Run inside the Home Assistant event loop.
|
Run inside the Home Assistant event loop.
|
||||||
@ -205,7 +207,8 @@ class HumidifierDehumidifier(HomeAccessory):
|
|||||||
ex,
|
ex,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _set_chars(self, char_values):
|
def _set_chars(self, char_values: dict[str, Any]) -> None:
|
||||||
|
"""Set characteristics based on the data coming from HomeKit."""
|
||||||
_LOGGER.debug("HumidifierDehumidifier _set_chars: %s", char_values)
|
_LOGGER.debug("HumidifierDehumidifier _set_chars: %s", char_values)
|
||||||
|
|
||||||
if CHAR_TARGET_HUMIDIFIER_DEHUMIDIFIER in char_values:
|
if CHAR_TARGET_HUMIDIFIER_DEHUMIDIFIER in char_values:
|
||||||
@ -225,6 +228,7 @@ class HumidifierDehumidifier(HomeAccessory):
|
|||||||
|
|
||||||
if self._target_humidity_char_name in char_values:
|
if self._target_humidity_char_name in char_values:
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
max_humidity = state.attributes.get(ATTR_MAX_HUMIDITY, DEFAULT_MAX_HUMIDITY)
|
max_humidity = state.attributes.get(ATTR_MAX_HUMIDITY, DEFAULT_MAX_HUMIDITY)
|
||||||
max_humidity = round(max_humidity)
|
max_humidity = round(max_humidity)
|
||||||
max_humidity = min(max_humidity, 100)
|
max_humidity = min(max_humidity, 100)
|
||||||
@ -232,6 +236,11 @@ class HumidifierDehumidifier(HomeAccessory):
|
|||||||
min_humidity = state.attributes.get(ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY)
|
min_humidity = state.attributes.get(ATTR_MIN_HUMIDITY, DEFAULT_MIN_HUMIDITY)
|
||||||
min_humidity = round(min_humidity)
|
min_humidity = round(min_humidity)
|
||||||
min_humidity = max(min_humidity, 0)
|
min_humidity = max(min_humidity, 0)
|
||||||
|
# The min/max humidity values here should be clamped to the HomeKit
|
||||||
|
# min/max that was set when the accessory was added to HomeKit so
|
||||||
|
# that the user cannot set a value outside of the range that was
|
||||||
|
# originally set as it could cause HomeKit to report the accessory
|
||||||
|
# as not responding.
|
||||||
|
|
||||||
humidity = round(char_values[self._target_humidity_char_name])
|
humidity = round(char_values[self._target_humidity_char_name])
|
||||||
|
|
||||||
@ -252,7 +261,7 @@ class HumidifierDehumidifier(HomeAccessory):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update state without rechecking the device features."""
|
"""Update state without rechecking the device features."""
|
||||||
is_active = new_state.state == STATE_ON
|
is_active = new_state.state == STATE_ON
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
"""Class to hold all light accessories."""
|
"""Class to hold all light accessories."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pyhap.const import CATEGORY_LIGHTBULB
|
from pyhap.const import CATEGORY_LIGHTBULB
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ from homeassistant.const import (
|
|||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import CALLBACK_TYPE, State, callback
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
from homeassistant.util.color import (
|
from homeassistant.util.color import (
|
||||||
color_temperature_kelvin_to_mired,
|
color_temperature_kelvin_to_mired,
|
||||||
@ -68,7 +70,7 @@ class Light(HomeAccessory):
|
|||||||
Currently supports: state, brightness, color temperature, rgb_color.
|
Currently supports: state, brightness, color temperature, rgb_color.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize a new Light accessory object."""
|
"""Initialize a new Light accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_LIGHTBULB)
|
super().__init__(*args, category=CATEGORY_LIGHTBULB)
|
||||||
self._reload_on_change_attrs.extend(
|
self._reload_on_change_attrs.extend(
|
||||||
@ -79,10 +81,11 @@ class Light(HomeAccessory):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.chars = []
|
self.chars = []
|
||||||
self._event_timer = None
|
self._event_timer: CALLBACK_TYPE | None = None
|
||||||
self._pending_events = {}
|
self._pending_events: dict[str, Any] = {}
|
||||||
|
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
attributes = state.attributes
|
attributes = state.attributes
|
||||||
self.color_modes = color_modes = (
|
self.color_modes = color_modes = (
|
||||||
attributes.get(ATTR_SUPPORTED_COLOR_MODES) or []
|
attributes.get(ATTR_SUPPORTED_COLOR_MODES) or []
|
||||||
@ -140,7 +143,7 @@ class Light(HomeAccessory):
|
|||||||
self.async_update_state(state)
|
self.async_update_state(state)
|
||||||
serv_light.setter_callback = self._set_chars
|
serv_light.setter_callback = self._set_chars
|
||||||
|
|
||||||
def _set_chars(self, char_values):
|
def _set_chars(self, char_values: dict[str, Any]) -> None:
|
||||||
_LOGGER.debug("Light _set_chars: %s", char_values)
|
_LOGGER.debug("Light _set_chars: %s", char_values)
|
||||||
# Newest change always wins
|
# Newest change always wins
|
||||||
if CHAR_COLOR_TEMPERATURE in self._pending_events and (
|
if CHAR_COLOR_TEMPERATURE in self._pending_events and (
|
||||||
@ -159,14 +162,14 @@ class Light(HomeAccessory):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_send_events(self, *_):
|
def _async_send_events(self, _now: datetime) -> None:
|
||||||
"""Process all changes at once."""
|
"""Process all changes at once."""
|
||||||
_LOGGER.debug("Coalesced _set_chars: %s", self._pending_events)
|
_LOGGER.debug("Coalesced _set_chars: %s", self._pending_events)
|
||||||
char_values = self._pending_events
|
char_values = self._pending_events
|
||||||
self._pending_events = {}
|
self._pending_events = {}
|
||||||
events = []
|
events = []
|
||||||
service = SERVICE_TURN_ON
|
service = SERVICE_TURN_ON
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
params: dict[str, Any] = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
|
|
||||||
if CHAR_ON in char_values:
|
if CHAR_ON in char_values:
|
||||||
if not char_values[CHAR_ON]:
|
if not char_values[CHAR_ON]:
|
||||||
@ -231,7 +234,7 @@ class Light(HomeAccessory):
|
|||||||
self.async_call_service(DOMAIN, service, params, ", ".join(events))
|
self.async_call_service(DOMAIN, service, params, ", ".join(events))
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update light after state change."""
|
"""Update light after state change."""
|
||||||
# Handle State
|
# Handle State
|
||||||
state = new_state.state
|
state = new_state.state
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import NamedTuple
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
|
from pyhap.characteristic import Characteristic
|
||||||
from pyhap.const import (
|
from pyhap.const import (
|
||||||
CATEGORY_FAUCET,
|
CATEGORY_FAUCET,
|
||||||
CATEGORY_OUTLET,
|
CATEGORY_OUTLET,
|
||||||
@ -30,7 +31,7 @@ from homeassistant.const import (
|
|||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import callback, split_entity_id
|
from homeassistant.core import State, callback, split_entity_id
|
||||||
from homeassistant.helpers.event import async_call_later
|
from homeassistant.helpers.event import async_call_later
|
||||||
|
|
||||||
from .accessories import TYPES, HomeAccessory
|
from .accessories import TYPES, HomeAccessory
|
||||||
@ -78,10 +79,11 @@ ACTIVATE_ONLY_RESET_SECONDS = 10
|
|||||||
class Outlet(HomeAccessory):
|
class Outlet(HomeAccessory):
|
||||||
"""Generate an Outlet accessory."""
|
"""Generate an Outlet accessory."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize an Outlet accessory object."""
|
"""Initialize an Outlet accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_OUTLET)
|
super().__init__(*args, category=CATEGORY_OUTLET)
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
|
|
||||||
serv_outlet = self.add_preload_service(SERV_OUTLET)
|
serv_outlet = self.add_preload_service(SERV_OUTLET)
|
||||||
self.char_on = serv_outlet.configure_char(
|
self.char_on = serv_outlet.configure_char(
|
||||||
@ -94,7 +96,7 @@ class Outlet(HomeAccessory):
|
|||||||
# GET to avoid an event storm after homekit startup
|
# GET to avoid an event storm after homekit startup
|
||||||
self.async_update_state(state)
|
self.async_update_state(state)
|
||||||
|
|
||||||
def set_state(self, value):
|
def set_state(self, value: bool) -> None:
|
||||||
"""Move switch state to value if call came from HomeKit."""
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
@ -102,7 +104,7 @@ class Outlet(HomeAccessory):
|
|||||||
self.async_call_service(DOMAIN, service, params)
|
self.async_call_service(DOMAIN, service, params)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
current_state = new_state.state == STATE_ON
|
current_state = new_state.state == STATE_ON
|
||||||
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
||||||
@ -113,13 +115,14 @@ class Outlet(HomeAccessory):
|
|||||||
class Switch(HomeAccessory):
|
class Switch(HomeAccessory):
|
||||||
"""Generate a Switch accessory."""
|
"""Generate a Switch accessory."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize a Switch accessory object."""
|
"""Initialize a Switch accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SWITCH)
|
super().__init__(*args, category=CATEGORY_SWITCH)
|
||||||
self._domain, self._object_id = split_entity_id(self.entity_id)
|
self._domain, self._object_id = split_entity_id(self.entity_id)
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
|
|
||||||
self.activate_only = self.is_activate(self.hass.states.get(self.entity_id))
|
self.activate_only = self.is_activate(state)
|
||||||
|
|
||||||
serv_switch = self.add_preload_service(SERV_SWITCH)
|
serv_switch = self.add_preload_service(SERV_SWITCH)
|
||||||
self.char_on = serv_switch.configure_char(
|
self.char_on = serv_switch.configure_char(
|
||||||
@ -129,16 +132,16 @@ class Switch(HomeAccessory):
|
|||||||
# GET to avoid an event storm after homekit startup
|
# GET to avoid an event storm after homekit startup
|
||||||
self.async_update_state(state)
|
self.async_update_state(state)
|
||||||
|
|
||||||
def is_activate(self, state):
|
def is_activate(self, state: State) -> bool:
|
||||||
"""Check if entity is activate only."""
|
"""Check if entity is activate only."""
|
||||||
return self._domain in ACTIVATE_ONLY_SWITCH_DOMAINS
|
return self._domain in ACTIVATE_ONLY_SWITCH_DOMAINS
|
||||||
|
|
||||||
def reset_switch(self, *args):
|
def reset_switch(self, *args: Any) -> None:
|
||||||
"""Reset switch to emulate activate click."""
|
"""Reset switch to emulate activate click."""
|
||||||
_LOGGER.debug("%s: Reset switch to off", self.entity_id)
|
_LOGGER.debug("%s: Reset switch to off", self.entity_id)
|
||||||
self.char_on.set_value(False)
|
self.char_on.set_value(False)
|
||||||
|
|
||||||
def set_state(self, value):
|
def set_state(self, value: bool) -> None:
|
||||||
"""Move switch state to value if call came from HomeKit."""
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
||||||
if self.activate_only and not value:
|
if self.activate_only and not value:
|
||||||
@ -162,7 +165,7 @@ class Switch(HomeAccessory):
|
|||||||
async_call_later(self.hass, ACTIVATE_ONLY_RESET_SECONDS, self.reset_switch)
|
async_call_later(self.hass, ACTIVATE_ONLY_RESET_SECONDS, self.reset_switch)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
self.activate_only = self.is_activate(new_state)
|
self.activate_only = self.is_activate(new_state)
|
||||||
if self.activate_only:
|
if self.activate_only:
|
||||||
@ -180,10 +183,12 @@ class Switch(HomeAccessory):
|
|||||||
class Vacuum(Switch):
|
class Vacuum(Switch):
|
||||||
"""Generate a Switch accessory."""
|
"""Generate a Switch accessory."""
|
||||||
|
|
||||||
def set_state(self, value):
|
def set_state(self, value: bool) -> None:
|
||||||
"""Move switch state to value if call came from HomeKit."""
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
|
|
||||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
@ -198,7 +203,7 @@ class Vacuum(Switch):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
current_state = new_state.state in (STATE_CLEANING, STATE_ON)
|
current_state = new_state.state in (STATE_CLEANING, STATE_ON)
|
||||||
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
||||||
@ -209,10 +214,12 @@ class Vacuum(Switch):
|
|||||||
class Valve(HomeAccessory):
|
class Valve(HomeAccessory):
|
||||||
"""Generate a Valve accessory."""
|
"""Generate a Valve accessory."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize a Valve accessory object."""
|
"""Initialize a Valve accessory object."""
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
|
assert state
|
||||||
|
|
||||||
valve_type = self.config[CONF_TYPE]
|
valve_type = self.config[CONF_TYPE]
|
||||||
self.category = VALVE_TYPE[valve_type].category
|
self.category = VALVE_TYPE[valve_type].category
|
||||||
|
|
||||||
@ -228,7 +235,7 @@ class Valve(HomeAccessory):
|
|||||||
# GET to avoid an event storm after homekit startup
|
# GET to avoid an event storm after homekit startup
|
||||||
self.async_update_state(state)
|
self.async_update_state(state)
|
||||||
|
|
||||||
def set_state(self, value):
|
def set_state(self, value: bool) -> None:
|
||||||
"""Move value state to value if call came from HomeKit."""
|
"""Move value state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
||||||
self.char_in_use.set_value(value)
|
self.char_in_use.set_value(value)
|
||||||
@ -237,7 +244,7 @@ class Valve(HomeAccessory):
|
|||||||
self.async_call_service(DOMAIN, service, params)
|
self.async_call_service(DOMAIN, service, params)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
current_state = 1 if new_state.state == STATE_ON else 0
|
current_state = 1 if new_state.state == STATE_ON else 0
|
||||||
_LOGGER.debug("%s: Set active state to %s", self.entity_id, current_state)
|
_LOGGER.debug("%s: Set active state to %s", self.entity_id, current_state)
|
||||||
@ -250,12 +257,14 @@ class Valve(HomeAccessory):
|
|||||||
class SelectSwitch(HomeAccessory):
|
class SelectSwitch(HomeAccessory):
|
||||||
"""Generate a Switch accessory that contains multiple switches."""
|
"""Generate a Switch accessory that contains multiple switches."""
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args: Any) -> None:
|
||||||
"""Initialize a Switch accessory object."""
|
"""Initialize a Switch accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SWITCH)
|
super().__init__(*args, category=CATEGORY_SWITCH)
|
||||||
self.domain = split_entity_id(self.entity_id)[0]
|
self.domain = split_entity_id(self.entity_id)[0]
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
self.select_chars = {}
|
assert state
|
||||||
|
|
||||||
|
self.select_chars: dict[str, Characteristic] = {}
|
||||||
options = state.attributes[ATTR_OPTIONS]
|
options = state.attributes[ATTR_OPTIONS]
|
||||||
for option in options:
|
for option in options:
|
||||||
serv_option = self.add_preload_service(
|
serv_option = self.add_preload_service(
|
||||||
@ -275,14 +284,14 @@ class SelectSwitch(HomeAccessory):
|
|||||||
# GET to avoid an event storm after homekit startup
|
# GET to avoid an event storm after homekit startup
|
||||||
self.async_update_state(state)
|
self.async_update_state(state)
|
||||||
|
|
||||||
def select_option(self, option):
|
def select_option(self, option: str) -> None:
|
||||||
"""Set option from HomeKit."""
|
"""Set option from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set option to %s", self.entity_id, option)
|
_LOGGER.debug("%s: Set option to %s", self.entity_id, option)
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id, "option": option}
|
params = {ATTR_ENTITY_ID: self.entity_id, "option": option}
|
||||||
self.async_call_service(self.domain, SERVICE_SELECT_OPTION, params)
|
self.async_call_service(self.domain, SERVICE_SELECT_OPTION, params)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_state(self, new_state):
|
def async_update_state(self, new_state: State) -> None:
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
current_option = cleanup_name_for_homekit(new_state.state)
|
current_option = cleanup_name_for_homekit(new_state.state)
|
||||||
for option, char in self.select_chars.items():
|
for option, char in self.select_chars.items():
|
||||||
|
Loading…
x
Reference in New Issue
Block a user