Skip to content

Commit

Permalink
2022.11.5 (#82980)
Browse files Browse the repository at this point in the history
Co-authored-by: mvn23 <schopdiedwaas@gmail.com>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: Daniel Hjelseth Høyer <github@dahoiv.net>
Co-authored-by: Aaron Bach <bachya1208@gmail.com>
  • Loading branch information
8 people committed Nov 30, 2022
2 parents 5fe426a + 20b73d8 commit 13a4541
Show file tree
Hide file tree
Showing 20 changed files with 372 additions and 72 deletions.
3 changes: 3 additions & 0 deletions homeassistant/components/esphome/bluetooth/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import asyncio
from collections.abc import Callable, Coroutine
import contextlib
import logging
from typing import Any, TypeVar, cast
import uuid
Expand Down Expand Up @@ -65,6 +66,8 @@ async def _async_wrap_bluetooth_connected_operation(
)
if disconnected_event.is_set():
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
raise BleakError(
f"{self._source}: {self._ble_device.name} - {self._ble_device.address}: " # pylint: disable=protected-access
"Disconnected during operation"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/google/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"config_flow": true,
"dependencies": ["application_credentials"],
"documentation": "https://www.home-assistant.io/integrations/calendar.google/",
"requirements": ["gcal-sync==4.0.2", "oauth2client==4.1.3"],
"requirements": ["gcal-sync==4.0.3", "oauth2client==4.1.3"],
"codeowners": ["@allenporter"],
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"]
Expand Down
86 changes: 54 additions & 32 deletions homeassistant/components/homekit_controller/device_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.characteristics.const import InputEventValues
from aiohomekit.model.services import ServicesTypes
from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.utils import clamp_enum_to_char
import voluptuous as vol

Expand Down Expand Up @@ -57,57 +57,67 @@
class TriggerSource:
"""Represents a stateless source of event data from HomeKit."""

def __init__(
self, connection: HKDevice, aid: int, triggers: list[dict[str, Any]]
) -> None:
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize a set of triggers for a device."""
self._hass = connection.hass
self._connection = connection
self._aid = aid
self._hass = hass
self._triggers: dict[tuple[str, str], dict[str, Any]] = {}
for trigger in triggers:
self._triggers[(trigger["type"], trigger["subtype"])] = trigger
self._callbacks: dict[int, list[Callable[[Any], None]]] = {}
self._callbacks: dict[tuple[str, str], list[Callable[[Any], None]]] = {}
self._iid_trigger_keys: dict[int, set[tuple[str, str]]] = {}

def fire(self, iid, value):
async def async_setup(
self, connection: HKDevice, aid: int, triggers: list[dict[str, Any]]
) -> None:
"""Set up a set of triggers for a device.
This function must be re-entrant since
it is called when the device is first added and
when the config entry is reloaded.
"""
for trigger_data in triggers:
trigger_key = (trigger_data[CONF_TYPE], trigger_data[CONF_SUBTYPE])
self._triggers[trigger_key] = trigger_data
iid = trigger_data["characteristic"]
self._iid_trigger_keys.setdefault(iid, set()).add(trigger_key)
await connection.add_watchable_characteristics([(aid, iid)])

def fire(self, iid: int, value: dict[str, Any]) -> None:
"""Process events that have been received from a HomeKit accessory."""
for event_handler in self._callbacks.get(iid, []):
event_handler(value)
for trigger_key in self._iid_trigger_keys.get(iid, set()):
for event_handler in self._callbacks.get(trigger_key, []):
event_handler(value)

def async_get_triggers(self) -> Generator[tuple[str, str], None, None]:
"""List device triggers for homekit devices."""
"""List device triggers for HomeKit devices."""
yield from self._triggers

async def async_attach_trigger(
@callback
def async_attach_trigger(
self,
config: ConfigType,
action: TriggerActionType,
trigger_info: TriggerInfo,
) -> CALLBACK_TYPE:
"""Attach a trigger."""
trigger_data = trigger_info["trigger_data"]
trigger_key = (config[CONF_TYPE], config[CONF_SUBTYPE])
job = HassJob(action)

@callback
def event_handler(char):
def event_handler(char: dict[str, Any]) -> None:
if config[CONF_SUBTYPE] != HK_TO_HA_INPUT_EVENT_VALUES[char["value"]]:
return
self._hass.async_run_hass_job(job, {"trigger": {**trigger_data, **config}})

trigger = self._triggers[config[CONF_TYPE], config[CONF_SUBTYPE]]
iid = trigger["characteristic"]

await self._connection.add_watchable_characteristics([(self._aid, iid)])
self._callbacks.setdefault(iid, []).append(event_handler)
self._callbacks.setdefault(trigger_key, []).append(event_handler)

def async_remove_handler():
if iid in self._callbacks:
self._callbacks[iid].remove(event_handler)
if trigger_key in self._callbacks:
self._callbacks[trigger_key].remove(event_handler)

return async_remove_handler


def enumerate_stateless_switch(service):
def enumerate_stateless_switch(service: Service) -> list[dict[str, Any]]:
"""Enumerate a stateless switch, like a single button."""

# A stateless switch that has a SERVICE_LABEL_INDEX is part of a group
Expand Down Expand Up @@ -135,7 +145,7 @@ def enumerate_stateless_switch(service):
]


def enumerate_stateless_switch_group(service):
def enumerate_stateless_switch_group(service: Service) -> list[dict[str, Any]]:
"""Enumerate a group of stateless switches, like a remote control."""
switches = list(
service.accessory.services.filter(
Expand Down Expand Up @@ -165,7 +175,7 @@ def enumerate_stateless_switch_group(service):
return results


def enumerate_doorbell(service):
def enumerate_doorbell(service: Service) -> list[dict[str, Any]]:
"""Enumerate doorbell buttons."""
input_event = service[CharacteristicsTypes.INPUT_EVENT]

Expand Down Expand Up @@ -217,21 +227,32 @@ def async_add_service(service):
if device_id in hass.data[TRIGGERS]:
return False

# Just because we recognise the service type doesn't mean we can actually
# Just because we recognize the service type doesn't mean we can actually
# extract any triggers - so only proceed if we can
triggers = TRIGGER_FINDERS[service_type](service)
if len(triggers) == 0:
return False

trigger = TriggerSource(conn, aid, triggers)
hass.data[TRIGGERS][device_id] = trigger
trigger = async_get_or_create_trigger_source(conn.hass, device_id)
hass.async_create_task(trigger.async_setup(conn, aid, triggers))

return True

conn.add_listener(async_add_service)


def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], Any]):
@callback
def async_get_or_create_trigger_source(
hass: HomeAssistant, device_id: str
) -> TriggerSource:
"""Get or create a trigger source for a device id."""
if not (source := hass.data[TRIGGERS].get(device_id)):
source = TriggerSource(hass)
hass.data[TRIGGERS][device_id] = source
return source


def async_fire_triggers(conn: HKDevice, events: dict[tuple[int, int], dict[str, Any]]):
"""Process events generated by a HomeKit accessory into automation triggers."""
trigger_sources: dict[str, TriggerSource] = conn.hass.data[TRIGGERS]
for (aid, iid), ev in events.items():
Expand Down Expand Up @@ -271,5 +292,6 @@ async def async_attach_trigger(
) -> CALLBACK_TYPE:
"""Attach a trigger."""
device_id = config[CONF_DEVICE_ID]
device = hass.data[TRIGGERS][device_id]
return await device.async_attach_trigger(config, action, trigger_info)
return async_get_or_create_trigger_source(hass, device_id).async_attach_trigger(
config, action, trigger_info
)
20 changes: 19 additions & 1 deletion homeassistant/components/ibeacon/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,25 @@ def _async_check_unavailable_groups_with_random_macs(self) -> None:
for group_id in self._group_ids_random_macs
if group_id not in self._unavailable_group_ids
and (service_info := self._last_seen_by_group_id.get(group_id))
and now - service_info.time > UNAVAILABLE_TIMEOUT
and (
# We will not be callbacks for iBeacons with random macs
# that rotate infrequently since their advertisement data is
# does not change as the bluetooth.async_register_callback API
# suppresses callbacks for duplicate advertisements to avoid
# exposing integrations to the firehose of bluetooth advertisements.
#
# To solve this we need to ask for the latest service info for
# the address we last saw to get the latest timestamp.
#
# If there is no last service info for the address we know that
# the device is no longer advertising.
not (
latest_service_info := bluetooth.async_last_service_info(
self.hass, service_info.address, connectable=False
)
)
or now - latest_service_info.time > UNAVAILABLE_TIMEOUT
)
]
for group_id in gone_unavailable:
self._unavailable_group_ids.add(group_id)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/opentherm_gw/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "opentherm_gw",
"name": "OpenTherm Gateway",
"documentation": "https://www.home-assistant.io/integrations/opentherm_gw",
"requirements": ["pyotgw==2.1.1"],
"requirements": ["pyotgw==2.1.3"],
"codeowners": ["@mvn23"],
"config_flow": true,
"iot_class": "local_push",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/sensibo/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "sensibo",
"name": "Sensibo",
"documentation": "https://www.home-assistant.io/integrations/sensibo",
"requirements": ["pysensibo==1.0.20"],
"requirements": ["pysensibo==1.0.22"],
"config_flow": true,
"codeowners": ["@andrey-git", "@gjohansson-ST"],
"iot_class": "cloud_polling",
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/simplisafe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ def _async_register_base_station(
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, system.system_id)},
identifiers={(DOMAIN, str(system.system_id))},
manufacturer="SimpliSafe",
model=system.version,
name=system.address,
Expand Down Expand Up @@ -757,7 +757,7 @@ def __init__(
manufacturer="SimpliSafe",
model=model,
name=device_name,
via_device=(DOMAIN, system.system_id),
via_device=(DOMAIN, str(system.system_id)),
)

self._attr_unique_id = serial
Expand Down
25 changes: 19 additions & 6 deletions homeassistant/components/simplisafe/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
EVENT_AWAY_EXIT_DELAY_BY_REMOTE,
EVENT_DISARMED_BY_MASTER_PIN,
EVENT_DISARMED_BY_REMOTE,
EVENT_ENTRY_DELAY,
EVENT_HOME_EXIT_DELAY,
EVENT_SECRET_ALERT_TRIGGERED,
EVENT_USER_INITIATED_TEST,
WebsocketEvent,
)

Expand Down Expand Up @@ -66,9 +69,12 @@
SystemStates.ALARM_COUNT: STATE_ALARM_PENDING,
SystemStates.AWAY: STATE_ALARM_ARMED_AWAY,
SystemStates.AWAY_COUNT: STATE_ALARM_ARMING,
SystemStates.ENTRY_DELAY: STATE_ALARM_PENDING,
SystemStates.EXIT_DELAY: STATE_ALARM_ARMING,
SystemStates.HOME: STATE_ALARM_ARMED_HOME,
SystemStates.HOME_COUNT: STATE_ALARM_ARMING,
SystemStates.OFF: STATE_ALARM_DISARMED,
SystemStates.TEST: STATE_ALARM_DISARMED,
}

STATE_MAP_FROM_WEBSOCKET_EVENT = {
Expand All @@ -82,7 +88,10 @@
EVENT_AWAY_EXIT_DELAY_BY_REMOTE: STATE_ALARM_ARMING,
EVENT_DISARMED_BY_MASTER_PIN: STATE_ALARM_DISARMED,
EVENT_DISARMED_BY_REMOTE: STATE_ALARM_DISARMED,
EVENT_ENTRY_DELAY: STATE_ALARM_PENDING,
EVENT_HOME_EXIT_DELAY: STATE_ALARM_ARMING,
EVENT_SECRET_ALERT_TRIGGERED: STATE_ALARM_TRIGGERED,
EVENT_USER_INITIATED_TEST: STATE_ALARM_DISARMED,
}

WEBSOCKET_EVENTS_TO_LISTEN_FOR = (
Expand Down Expand Up @@ -156,13 +165,11 @@ def _set_state_from_system_data(self) -> None:
"""Set the state based on the latest REST API data."""
if self._system.alarm_going_off:
self._attr_state = STATE_ALARM_TRIGGERED
elif self._system.state == SystemStates.ERROR:
self.async_increment_error_count()
elif state := STATE_MAP_FROM_REST_API.get(self._system.state):
self._attr_state = state
self.async_reset_error_count()
else:
LOGGER.error("Unknown system state (REST API): %s", self._system.state)
LOGGER.warning("Unexpected system state (REST API): %s", self._system.state)
self.async_increment_error_count()

async def async_alarm_disarm(self, code: str | None = None) -> None:
Expand Down Expand Up @@ -217,22 +224,28 @@ def async_update_from_rest_api(self) -> None:
self._attr_extra_state_attributes.update(
{
ATTR_ALARM_DURATION: self._system.alarm_duration,
ATTR_ALARM_VOLUME: self._system.alarm_volume.name.lower(),
ATTR_BATTERY_BACKUP_POWER_LEVEL: self._system.battery_backup_power_level,
ATTR_CHIME_VOLUME: self._system.chime_volume.name.lower(),
ATTR_ENTRY_DELAY_AWAY: self._system.entry_delay_away,
ATTR_ENTRY_DELAY_HOME: self._system.entry_delay_home,
ATTR_EXIT_DELAY_AWAY: self._system.exit_delay_away,
ATTR_EXIT_DELAY_HOME: self._system.exit_delay_home,
ATTR_GSM_STRENGTH: self._system.gsm_strength,
ATTR_LIGHT: self._system.light,
ATTR_RF_JAMMING: self._system.rf_jamming,
ATTR_VOICE_PROMPT_VOLUME: self._system.voice_prompt_volume.name.lower(),
ATTR_WALL_POWER_LEVEL: self._system.wall_power_level,
ATTR_WIFI_STRENGTH: self._system.wifi_strength,
}
)

for key, volume_prop in (
(ATTR_ALARM_VOLUME, self._system.alarm_volume),
(ATTR_CHIME_VOLUME, self._system.chime_volume),
(ATTR_VOICE_PROMPT_VOLUME, self._system.voice_prompt_volume),
):
if not volume_prop:
continue
self._attr_extra_state_attributes[key] = volume_prop.name.lower()

self._set_state_from_system_data()

@callback
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/simplisafe/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
DeviceTypes.CARBON_MONOXIDE,
DeviceTypes.ENTRY,
DeviceTypes.GLASS_BREAK,
DeviceTypes.KEYPAD,
DeviceTypes.LEAK,
DeviceTypes.LOCK_KEYPAD,
DeviceTypes.MOTION,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/simplisafe/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "SimpliSafe",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/simplisafe",
"requirements": ["simplisafe-python==2022.07.1"],
"requirements": ["simplisafe-python==2022.11.2"],
"codeowners": ["@bachya"],
"iot_class": "cloud_polling",
"dhcp": [
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/tibber/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"domain": "tibber",
"name": "Tibber",
"documentation": "https://www.home-assistant.io/integrations/tibber",
"requirements": ["pyTibber==0.25.6"],
"requirements": ["pyTibber==0.26.1"],
"codeowners": ["@danielhiversen"],
"quality_scale": "silver",
"config_flow": true,
Expand Down
8 changes: 4 additions & 4 deletions homeassistant/components/zha/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/zha",
"requirements": [
"bellows==0.34.2",
"bellows==0.34.4",
"pyserial==3.5",
"pyserial-asyncio==0.6",
"zha-quirks==0.0.86",
"zigpy-deconz==0.19.0",
"zigpy==0.51.5",
"zha-quirks==0.0.87",
"zigpy-deconz==0.19.1",
"zigpy==0.51.6",
"zigpy-xbee==0.16.2",
"zigpy-zigate==0.10.3",
"zigpy-znp==0.9.1"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
APPLICATION_NAME: Final = "HomeAssistant"
MAJOR_VERSION: Final = 2022
MINOR_VERSION: Final = 11
PATCH_VERSION: Final = "4"
PATCH_VERSION: Final = "5"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "homeassistant"
version = "2022.11.4"
version = "2022.11.5"
license = {text = "Apache-2.0"}
description = "Open-source home automation platform running on Python 3."
readme = "README.rst"
Expand Down

0 comments on commit 13a4541

Please sign in to comment.