Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Callback listeners on put for all controllers #237

Merged
merged 18 commits into from
Nov 15, 2022
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
8 changes: 8 additions & 0 deletions aiohomekit/controller/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ def broadcast_key(self) -> bytes | None:
async def _process_disconnected_events(self):
"""Process any disconnected events that are available."""

def _callback_listeners(self, event):
for listener in self.listeners:
try:
logger.debug("callback ev:%s", event)
listener(event)
except Exception:
logger.exception("Unhandled error when processing event")

def _async_description_update(
self, description: AbstractDescription | None
) -> None:
Expand Down
13 changes: 5 additions & 8 deletions aiohomekit/controller/ble/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,8 +671,7 @@ def _async_notification(self, data: HomeKitEncryptedNotification) -> None:
results = {(BLE_AID, iid): {"value": from_bytes(char, value)}}
logger.debug("%s: Received notification: results = %s", self.name, results)

for listener in self.listeners:
listener(results)
self._callback_listeners(results)
return

logger.warning(
Expand Down Expand Up @@ -1404,8 +1403,7 @@ async def _get_characteristics_while_connected(
# we want to notify the listeners as soon as we have the
# value for each characteristic.
single_results = {result_key: result_value}
for listener in self.listeners:
listener(single_results)
self._callback_listeners(single_results)

return results

Expand All @@ -1414,8 +1412,8 @@ async def _get_characteristics_while_connected(
@disconnect_on_missing_services
@restore_connection_and_resume
async def put_characteristics(
self, characteristics: list[tuple[int, int, Any]]
) -> dict[tuple[int, int], Any]:
self, characteristics: Iterable[tuple[int, int, Any]]
) -> dict[tuple[int, int], dict[str, Any]]:
results: dict[tuple[int, int], Any] = {}
logger.debug(
"%s: Writing characteristics: %s; rssi=%s",
Expand Down Expand Up @@ -1465,8 +1463,7 @@ async def put_characteristics(
# results only set on failure, no status is success
if not result:
if CharacteristicPermissions.paired_read in char.perms:
for listener in self.listeners:
listener({result_key: {"value": value}})
self._callback_listeners({result_key: {"value": value}})
else:
results[result_key] = result

Expand Down
35 changes: 24 additions & 11 deletions aiohomekit/controller/coap/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
import asyncio
from datetime import timedelta
import logging
from typing import Any
from typing import Any, Iterable

from aiohomekit.controller.abstract import AbstractController, AbstractPairingData
from aiohomekit.exceptions import AccessoryDisconnectedError
from aiohomekit.model import Accessories, AccessoriesState, Transport
from aiohomekit.model.characteristics import CharacteristicPermissions
from aiohomekit.protocol.statuscodes import HapStatusCode
from aiohomekit.utils import async_create_task
from aiohomekit.uuid import normalize_uuid
from aiohomekit.zeroconf import ZeroconfPairing
Expand Down Expand Up @@ -133,14 +135,6 @@ async def close(self) -> None:
def event_received(self, event):
self._callback_listeners(event)

def _callback_listeners(self, event):
for listener in self.listeners:
try:
logger.debug(f"callback ev:{event!r}")
listener(event)
except Exception:
logger.exception("Unhandled error when processing event")

async def identify(self):
await self._ensure_connected()
return await self.connection.do_identify()
Expand Down Expand Up @@ -198,9 +192,28 @@ async def get_characteristics(
await self._ensure_connected()
return await self.connection.read_characteristics(characteristics)

async def put_characteristics(self, characteristics):
async def put_characteristics(
self, characteristics: Iterable[tuple[int, int, Any]]
) -> dict[tuple[int, int], dict[str, Any]]:
await self._ensure_connected()
return await self.connection.write_characteristics(characteristics)
response_status = await self.connection.write_characteristics(characteristics)

listener_update: dict[tuple[int, int], dict[str, Any]] = {}
for characteristic in characteristics:
aid, iid, value = characteristic
accessory_chars = self.accessories.aid(aid).characteristics
char = accessory_chars.iid(iid)
if (
response_status.get((aid, iid), HapStatusCode.SUCCESS)
== HapStatusCode.SUCCESS
and CharacteristicPermissions.paired_read in char.perms
):
listener_update[(aid, iid)] = {"value": value}
bdraco marked this conversation as resolved.
Show resolved Hide resolved

if listener_update:
self._callback_listeners(listener_update)

return response_status

async def subscribe(self, characteristics):
await self._ensure_connected()
Expand Down
69 changes: 38 additions & 31 deletions aiohomekit/controller/ip/pairing.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from itertools import groupby
import logging
from operator import itemgetter
from typing import Any
from typing import Any, Iterable

from aiohomekit.controller.abstract import AbstractController, AbstractPairingData
from aiohomekit.exceptions import (
Expand All @@ -35,9 +35,12 @@
import aiohomekit.hkjson as hkjson
from aiohomekit.http import HttpContentTypes
from aiohomekit.model import Accessories, AccessoriesState, Transport
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.characteristics import (
CharacteristicPermissions,
CharacteristicsTypes,
)
from aiohomekit.protocol import error_handler
from aiohomekit.protocol.statuscodes import to_status_code
from aiohomekit.protocol.statuscodes import HapStatusCode, to_status_code
from aiohomekit.protocol.tlv import TLV
from aiohomekit.utils import asyncio_timeout
from aiohomekit.uuid import normalize_uuid
Expand Down Expand Up @@ -115,13 +118,6 @@ def name(self) -> str:
def event_received(self, event):
self._callback_listeners(format_characteristic_list(event))

def _callback_listeners(self, event):
for listener in self.listeners:
try:
listener(event)
except Exception:
logger.exception("Unhandled error when processing event")

async def connection_made(self, secure):
if not secure:
return
Expand Down Expand Up @@ -265,7 +261,9 @@ async def get_characteristics(

return format_characteristic_list(response)

async def put_characteristics(self, characteristics):
async def put_characteristics(
self, characteristics: Iterable[tuple[int, int, Any]]
) -> dict[tuple[int, int], dict[str, Any]]:
"""
Update the values of writable characteristics. The characteristics have to be identified by accessory id (aid),
instance id (iid). If do_conversion is False (the default), the value must be of proper format for the
Expand All @@ -282,28 +280,37 @@ async def put_characteristics(self, characteristics):
if not self.accessories:
await self.list_accessories_and_characteristics()

data = []
characteristics_set = set()
char_payload: list[dict[str, Any]] = []
listener_update: dict[tuple[int, int], dict[str, Any]] = {}
for characteristic in characteristics:
aid = characteristic[0]
iid = characteristic[1]
value = characteristic[2]
characteristics_set.add(f"{aid}.{iid}")
data.append({"aid": aid, "iid": iid, "value": value})
data = {"characteristics": data}

response = await self.connection.put_json("/characteristics", data)
aid, iid, value = characteristic
char_payload.append({"aid": aid, "iid": iid, "value": value})
accessory_chars = self.accessories.aid(aid).characteristics
bdraco marked this conversation as resolved.
Show resolved Hide resolved
char = accessory_chars.iid(iid)
if CharacteristicPermissions.paired_read in char.perms:
listener_update[(aid, iid)] = {"value": value}

response = await self.connection.put_json(
"/characteristics", {"characteristics": char_payload}
)
response_status: dict[tuple[int, int], dict[str, Any]] = {}
if response:
data = {
(d["aid"], d["iid"]): {
"status": d["status"],
"description": to_status_code(d["status"]).description,
}
for d in response["characteristics"]
}
return data

return {}
# If there is a response it means something failed so
# we need to remove the listener update for the failed
# characteristics.
for characteristic in response:
aid, iid = characteristic["aid"], characteristic["iid"]
key = (aid, iid)
status = characteristic["status"]
status_code = to_status_code(status).description
if status_code != HapStatusCode.SUCCESS:
listener_update.pop(key)
response_status[key] = {"status": status, "description": status_code}

if listener_update:
self._callback_listeners(listener_update)

return response_status

async def subscribe(self, characteristics):
await super().subscribe(set(characteristics))
Expand Down
Loading