Skip to content

Commit e0f6204

Browse files
committed
Merge branch 'dev' into v3.4.1
2 parents a0de0c0 + 9e45334 commit e0f6204

File tree

10 files changed

+232
-75
lines changed

10 files changed

+232
-75
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Sections
1616
### Developers
1717
-->
1818

19+
## [3.4.1] - 2021-03-28
20+
21+
# Fixed
22+
- Fix `run_at_interval` with multiple accessories. [#335](https://github.com/ikalchev/HAP-python/pull/335)
23+
- Ensure HTTP 200 status is sent when there are no failures for `get_characteristics`. Improves battery life. [#337](https://github.com/ikalchev/HAP-python/pull/337)
24+
1925
## [3.4.0] - 2021-03-06
2026

2127
### Added

pyhap/accessory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ def get_characteristic(self, aid, iid):
368368
async def run(self):
369369
"""Schedule tasks for each of the accessories' run method."""
370370
for acc in self.accessories.values():
371-
await self.driver.async_add_job(acc.run)
371+
self.driver.async_add_job(acc.run)
372372

373373
async def stop(self):
374374
"""Calls stop() on all contained accessories."""

pyhap/accessory_driver.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,11 @@
5151
from pyhap.params import get_srp_context
5252
from pyhap.state import State
5353

54+
from .const import HAP_SERVER_STATUS
5455
from .util import callback
5556

5657
logger = logging.getLogger(__name__)
5758

58-
CHAR_STAT_OK = 0
59-
SERVICE_COMMUNICATION_FAILURE = -70402
6059
SERVICE_CALLBACK = "callback"
6160
SERVICE_CHARS = "chars"
6261
SERVICE_IIDS = "iids"
@@ -682,7 +681,7 @@ def get_characteristics(self, char_ids):
682681
rep = {
683682
HAP_REPR_AID: aid,
684683
HAP_REPR_IID: iid,
685-
HAP_REPR_STATUS: SERVICE_COMMUNICATION_FAILURE,
684+
HAP_REPR_STATUS: HAP_SERVER_STATUS.SERVICE_COMMUNICATION_FAILURE,
686685
}
687686

688687
try:
@@ -698,7 +697,7 @@ def get_characteristics(self, char_ids):
698697

699698
if available:
700699
rep[HAP_REPR_VALUE] = char.get_value()
701-
rep[HAP_REPR_STATUS] = CHAR_STAT_OK
700+
rep[HAP_REPR_STATUS] = HAP_SERVER_STATUS.SUCCESS
702701
except CharacteristicError:
703702
logger.error("Error getting value for characteristic %s.", id)
704703
except Exception: # pylint: disable=broad-except
@@ -761,10 +760,12 @@ def set_characteristics(self, chars_query, client_addr):
761760
char.display_name,
762761
value,
763762
)
764-
setter_results[aid][iid] = SERVICE_COMMUNICATION_FAILURE
763+
setter_results[aid][
764+
iid
765+
] = HAP_SERVER_STATUS.SERVICE_COMMUNICATION_FAILURE
765766
had_error = True
766767
else:
767-
setter_results[aid][iid] = CHAR_STAT_OK
768+
setter_results[aid][iid] = HAP_SERVER_STATUS.SUCCESS
768769

769770
# For some services we want to send all the char value
770771
# changes at once. This resolves an issue where we send
@@ -798,10 +799,10 @@ def set_characteristics(self, chars_query, client_addr):
798799
client_addr,
799800
service_name,
800801
)
801-
set_result = SERVICE_COMMUNICATION_FAILURE
802+
set_result = HAP_SERVER_STATUS.SERVICE_COMMUNICATION_FAILURE
802803
had_error = True
803804
else:
804-
set_result = CHAR_STAT_OK
805+
set_result = HAP_SERVER_STATUS.SUCCESS
805806

806807
for iid in service_data[SERVICE_IIDS]:
807808
setter_results[aid][iid] = set_result

pyhap/const.py

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
"""This module contains constants used by other modules."""
22
MAJOR_VERSION = 3
33
MINOR_VERSION = 4
4-
PATCH_VERSION = 0
5-
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
6-
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
4+
PATCH_VERSION = 1
5+
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
6+
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
77
REQUIRED_PYTHON_VER = (3, 5)
88

99
# ### Misc ###
@@ -51,26 +51,42 @@
5151

5252

5353
# ### HAP Permissions ###
54-
HAP_PERMISSION_HIDDEN = 'hd'
55-
HAP_PERMISSION_NOTIFY = 'ev'
56-
HAP_PERMISSION_READ = 'pr'
57-
HAP_PERMISSION_WRITE = 'pw'
58-
HAP_PERMISSION_WRITE_RESPONSE = 'wr'
54+
HAP_PERMISSION_HIDDEN = "hd"
55+
HAP_PERMISSION_NOTIFY = "ev"
56+
HAP_PERMISSION_READ = "pr"
57+
HAP_PERMISSION_WRITE = "pw"
58+
HAP_PERMISSION_WRITE_RESPONSE = "wr"
5959

6060

6161
# ### HAP representation ###
62-
HAP_REPR_ACCS = 'accessories'
63-
HAP_REPR_AID = 'aid'
64-
HAP_REPR_CHARS = 'characteristics'
65-
HAP_REPR_DESC = 'description'
66-
HAP_REPR_FORMAT = 'format'
67-
HAP_REPR_IID = 'iid'
68-
HAP_REPR_MAX_LEN = 'maxLen'
69-
HAP_REPR_PERM = 'perms'
70-
HAP_REPR_PRIMARY = 'primary'
71-
HAP_REPR_SERVICES = 'services'
72-
HAP_REPR_LINKED = 'linked'
73-
HAP_REPR_STATUS = 'status'
74-
HAP_REPR_TYPE = 'type'
75-
HAP_REPR_VALUE = 'value'
76-
HAP_REPR_VALID_VALUES = 'valid-values'
62+
HAP_REPR_ACCS = "accessories"
63+
HAP_REPR_AID = "aid"
64+
HAP_REPR_CHARS = "characteristics"
65+
HAP_REPR_DESC = "description"
66+
HAP_REPR_FORMAT = "format"
67+
HAP_REPR_IID = "iid"
68+
HAP_REPR_MAX_LEN = "maxLen"
69+
HAP_REPR_PERM = "perms"
70+
HAP_REPR_PRIMARY = "primary"
71+
HAP_REPR_SERVICES = "services"
72+
HAP_REPR_LINKED = "linked"
73+
HAP_REPR_STATUS = "status"
74+
HAP_REPR_TYPE = "type"
75+
HAP_REPR_VALUE = "value"
76+
HAP_REPR_VALID_VALUES = "valid-values"
77+
78+
79+
# Status codes for underlying HAP calls
80+
class HAP_SERVER_STATUS:
81+
SUCCESS = 0
82+
INSUFFICIENT_PRIVILEGES = -70401
83+
SERVICE_COMMUNICATION_FAILURE = -70402
84+
RESOURCE_BUSY = -70403
85+
READ_ONLY_CHARACTERISTIC = -70404
86+
WRITE_ONLY_CHARACTERISTIC = -70405
87+
NOTIFICATION_NOT_SUPPORTED = -70406
88+
OUT_OF_RESOURCE = -70407
89+
OPERATION_TIMED_OUT = -70408
90+
RESOURCE_DOES_NOT_EXIST = -70409
91+
INVALID_VALUE_IN_REQUEST = -70410
92+
INSUFFICIENT_AUTHORIZATION = -70411

pyhap/hap_handler.py

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@
1414
import curve25519
1515
import ed25519
1616

17-
from pyhap.const import CATEGORY_BRIDGE
17+
from pyhap.const import (
18+
CATEGORY_BRIDGE,
19+
HAP_REPR_CHARS,
20+
HAP_REPR_STATUS,
21+
HAP_SERVER_STATUS,
22+
)
1823
import pyhap.tlv as tlv
1924
from pyhap.util import long_to_bytes
2025

@@ -74,22 +79,6 @@ class HAP_TLV_TAGS:
7479
PERMISSIONS = b"\x0B"
7580

7681

77-
# Status codes for underlying HAP calls
78-
class HAP_SERVER_STATUS:
79-
SUCCESS = 0
80-
INSUFFICIENT_PRIVILEGES = -70401
81-
SERVICE_COMMUNICATION_FAILURE = -70402
82-
RESOURCE_BUSY = -70403
83-
READ_ONLY_CHARACTERISTIC = -70404
84-
WRITE_ONLY_CHARACTERISTIC = -70405
85-
NOTIFICATION_NOT_SUPPORTED = -70406
86-
OUT_OF_RESOURCE = -70407
87-
OPERATION_TIMED_OUT = -70408
88-
RESOURCE_DOES_NOT_EXIST = -70409
89-
INVALID_VALUE_IN_REQUEST = -70410
90-
INSUFFICIENT_AUTHORIZATION = -70411
91-
92-
9382
class HAP_PERMISSIONS:
9483
USER = b"\x00"
9584
ADMIN = b"\x01"
@@ -576,10 +565,22 @@ def handle_get_characteristics(self):
576565

577566
# Check that char exists and ...
578567
params = parse_qs(urlparse(self.path).query)
579-
chars = self.accessory_handler.get_characteristics(params["id"][0].split(","))
568+
response = self.accessory_handler.get_characteristics(
569+
params["id"][0].split(",")
570+
)
571+
chars = response[HAP_REPR_CHARS]
580572

581-
data = json.dumps(chars).encode("utf-8")
582-
self.send_response(HTTPStatus.MULTI_STATUS)
573+
had_failure = any(
574+
result[HAP_REPR_STATUS] != HAP_SERVER_STATUS.SUCCESS for result in chars
575+
)
576+
if had_failure:
577+
self.send_response(HTTPStatus.MULTI_STATUS)
578+
else:
579+
self.send_response(HTTPStatus.OK)
580+
for result in chars:
581+
del result[HAP_REPR_STATUS]
582+
583+
data = json.dumps(response).encode("utf-8")
583584
self.send_header("Content-Type", self.JSON_RESPONSE_TYPE)
584585
self.end_response(data)
585586

tests/test_accessory.py

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""Tests for pyhap.accessory."""
2+
import asyncio
23
from io import StringIO
34
from unittest.mock import patch
45

56
import pytest
67

78
from pyhap import accessory
89
from pyhap.accessory import Accessory, Bridge
10+
from pyhap.accessory_driver import AccessoryDriver
911
from pyhap.const import (
1012
CATEGORY_CAMERA,
1113
CATEGORY_TARGET_CONTROLLER,
@@ -22,6 +24,21 @@
2224
# #####################
2325

2426

27+
class TestAccessory(Accessory):
28+
"""An accessory that keeps track of if its stopped."""
29+
30+
def __init__(self, *args, **kwargs):
31+
super().__init__(*args, **kwargs)
32+
self._stopped = False
33+
34+
async def stop(self):
35+
self._stopped = True
36+
37+
@property
38+
def stopped(self):
39+
return self._stopped
40+
41+
2542
def test_acc_init(mock_driver):
2643
Accessory(mock_driver, "Test Accessory")
2744

@@ -383,15 +400,27 @@ def test_to_hap(mock_driver):
383400

384401

385402
@pytest.mark.asyncio
386-
async def test_bridge_run_stop(mock_driver):
387-
mock_driver.async_add_job = AsyncMock()
388-
bridge = Bridge(mock_driver, "Test Bridge")
389-
acc = Accessory(mock_driver, "Test Accessory", aid=2)
390-
assert acc.available is True
391-
bridge.add_accessory(acc)
392-
acc2 = Accessory(mock_driver, "Test Accessory 2")
393-
bridge.add_accessory(acc2)
403+
async def test_bridge_run_stop():
404+
with patch(
405+
"pyhap.accessory_driver.HAPServer.async_stop", new_callable=AsyncMock
406+
), patch(
407+
"pyhap.accessory_driver.HAPServer.async_start", new_callable=AsyncMock
408+
), patch(
409+
"pyhap.accessory_driver.Zeroconf"
410+
), patch(
411+
"pyhap.accessory_driver.AccessoryDriver.persist"
412+
), patch(
413+
"pyhap.accessory_driver.AccessoryDriver.load"
414+
):
415+
driver = AccessoryDriver(loop=asyncio.get_event_loop())
416+
bridge = Bridge(driver, "Test Bridge")
417+
acc = TestAccessory(driver, "Test Accessory", aid=2)
418+
assert acc.available is True
419+
bridge.add_accessory(acc)
420+
acc2 = TestAccessory(driver, "Test Accessory 2")
421+
bridge.add_accessory(acc2)
394422

395-
await bridge.run()
396-
assert mock_driver.async_add_job.called
397-
await bridge.stop()
423+
await bridge.run()
424+
await bridge.stop()
425+
assert acc.stopped is True
426+
assert acc2.stopped is True

0 commit comments

Comments
 (0)