Skip to content

Commit 54d174d

Browse files
authored
V4.7.0
V4.7.0
2 parents 8b62c19 + e346dab commit 54d174d

19 files changed

+282
-154
lines changed

.github/workflows/ci.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010
strategy:
1111
matrix:
12-
python-version: [3.7, 3.8, 3.9, "3.10"]
12+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11"]
1313

1414
steps:
1515
- uses: actions/checkout@v1
@@ -35,7 +35,7 @@ jobs:
3535

3636
strategy:
3737
matrix:
38-
python-version: [3.9]
38+
python-version: ["3.10"]
3939

4040
steps:
4141
- uses: actions/checkout@v1
@@ -66,7 +66,7 @@ jobs:
6666

6767
strategy:
6868
matrix:
69-
python-version: [3.9]
69+
python-version: ["3.10"]
7070

7171
steps:
7272
- uses: actions/checkout@v1

CHANGELOG.md

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

19+
## [4.7.0] - 2023-06-18
20+
21+
- Allow passing multiple ip to advertise on to AccessoryDriver. [#442](https://github.com/ikalchev/HAP-python/pull/442)
22+
- Fix for the new home architecture - retain the original format of the UUID. [#441](https://github.com/ikalchev/HAP-python/pull/441)
23+
- Add python 3.11 to the CI. [#440](https://github.com/ikalchev/HAP-python/pull/440)
24+
- Use orjson.loads in loader to speed up startup. [#436](https://github.com/ikalchev/HAP-python/pull/436)
25+
1926
## [4.6.0] - 2022-12-10
2027

2128
- Patch for [WinError 5] Access Denied. [#421](https://github.com/ikalchev/HAP-python/pull/421)

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The project was developed for a Raspberry Pi, but it should work on other platfo
1818
you can open `main.py` or `busy_home.py`, where you will find some fake accessories.
1919
Just run one of them, for example `python3 busy_home.py`, and you can add it in
2020
the Home app (be sure to be in the same network).
21-
Stop it by hitting Ctrl+C.
21+
Stop it by hitting <kbd>Ctrl</kbd>+<kbd>C</kbd>.
2222

2323
There are example accessories as well as integrations with real products
2424
in [the accessories folder](accessories). See how to configure your camera in
@@ -90,7 +90,7 @@ class TemperatureSensor(Accessory):
9090
"""
9191
print('Temperature changed to: ', value)
9292

93-
@Acessory.run_at_interval(3) # Run this method every 3 seconds
93+
@Accessory.run_at_interval(3) # Run this method every 3 seconds
9494
# The `run` method can be `async` as well
9595
def run(self):
9696
"""We override this method to implement what the accessory will do when it is
@@ -151,7 +151,7 @@ class Light(Accessory):
151151
if "Brightness" in char_values:
152152
print('Brightness changed to: ', char_values["Brightness"])
153153

154-
@Acessory.run_at_interval(3) # Run this method every 3 seconds
154+
@Accessory.run_at_interval(3) # Run this method every 3 seconds
155155
# The `run` method can be `async` as well
156156
def run(self):
157157
"""We override this method to implement what the accessory will do when it is

docs/source/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
4141
# ones.
4242
extensions = [
43-
'sphinx.ext.autodoc',
43+
'sphinx.ext.autodoc', 'sphinx.ext.viewcode'
4444
]
4545

4646
# Add any paths that contain templates here, relative to this directory.

pyhap/accessory_driver.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,17 @@
2222
import logging
2323
import os
2424
import re
25-
import socket
2625
import sys
2726
import tempfile
28-
import time
2927
import threading
28+
import time
29+
from typing import Optional
3030

3131
from zeroconf import ServiceInfo
3232
from zeroconf.asyncio import AsyncZeroconf
3333

3434
from pyhap import util
35-
from pyhap.accessory import get_topic
35+
from pyhap.accessory import Accessory, get_topic
3636
from pyhap.characteristic import CharacteristicError
3737
from pyhap.const import (
3838
HAP_PERMISSION_NOTIFY,
@@ -41,9 +41,9 @@
4141
HAP_REPR_AID,
4242
HAP_REPR_CHARS,
4343
HAP_REPR_IID,
44-
HAP_REPR_TTL,
4544
HAP_REPR_PID,
4645
HAP_REPR_STATUS,
46+
HAP_REPR_TTL,
4747
HAP_REPR_VALUE,
4848
STANDALONE_AID,
4949
)
@@ -122,7 +122,7 @@ class AccessoryMDNSServiceInfo(ServiceInfo):
122122

123123
def __init__(self, accessory, state, zeroconf_server=None):
124124
self.accessory = accessory
125-
self.state = state
125+
self.state: State = state
126126

127127
adv_data = self._get_advert_data()
128128
valid_name = self._valid_name()
@@ -139,7 +139,7 @@ def __init__(self, accessory, state, zeroconf_server=None):
139139
weight=0,
140140
priority=0,
141141
properties=adv_data,
142-
addresses=[socket.inet_aton(self.state.address)],
142+
parsed_addresses=self.state.addresses,
143143
)
144144

145145
def _valid_name(self):
@@ -244,10 +244,10 @@ def __init__(
244244
If not given, the value of the address parameter will be used.
245245
:type listen_address: str
246246
247-
:param advertised_address: The address of the HAPServer announced via mDNS.
247+
:param advertised_address: The addresses of the HAPServer announced via mDNS.
248248
This can be used to announce an external address from behind a NAT.
249249
If not given, the value of the address parameter will be used.
250-
:type advertised_address: str
250+
:type advertised_address: str | list[str]
251251
252252
:param interface_choice: The zeroconf interfaces to listen on.
253253
:type InterfacesType: [InterfaceChoice.Default, InterfaceChoice.All]
@@ -279,7 +279,7 @@ def __init__(
279279

280280
self.loop = loop
281281

282-
self.accessory = None
282+
self.accessory: Optional[Accessory] = None
283283
self.advertiser = async_zeroconf_instance
284284
self.zeroconf_server = zeroconf_server
285285
self.interface_choice = interface_choice
@@ -366,9 +366,9 @@ async def async_start(self):
366366
self.aio_stop_event = asyncio.Event()
367367

368368
logger.info(
369-
"Starting accessory %s on address %s, port %s.",
369+
"Starting accessory %s on addresses %s, port %s.",
370370
self.accessory.display_name,
371-
self.state.address,
371+
self.state.addresses,
372372
self.state.port,
373373
)
374374

@@ -428,7 +428,7 @@ async def async_stop(self):
428428
logger.info(
429429
"Stopping accessory %s on address %s, port %s.",
430430
self.accessory.display_name,
431-
self.state.address,
431+
self.state.addresses,
432432
self.state.port,
433433
)
434434

@@ -643,7 +643,9 @@ def persist(self):
643643
) as file_handle:
644644
tmp_filename = file_handle.name
645645
self.encoder.persist(file_handle, self.state)
646-
if os.name == 'nt': # Or `[WinError 5] Access Denied` will be raised on Windows
646+
if (
647+
os.name == "nt"
648+
): # Or `[WinError 5] Access Denied` will be raised on Windows
647649
os.chmod(tmp_filename, 0o644)
648650
os.chmod(self.persist_file, 0o644)
649651
os.replace(tmp_filename, self.persist_file)
@@ -663,13 +665,18 @@ def load(self):
663665
self.encoder.load_into(file_handle, self.state)
664666

665667
@callback
666-
def pair(self, client_uuid, client_public, client_permissions):
668+
def pair(
669+
self,
670+
client_username_bytes: bytes,
671+
client_public: bytes,
672+
client_permissions: bytes,
673+
) -> bool:
667674
"""Called when a client has paired with the accessory.
668675
669676
Persist the new accessory state.
670677
671-
:param client_uuid: The client uuid.
672-
:type client_uuid: uuid.UUID
678+
:param client_username_bytes: The client username bytes.
679+
:type client_username_bytes: bytes
673680
674681
:param client_public: The client's public key.
675682
:type client_public: bytes
@@ -681,9 +688,13 @@ def pair(self, client_uuid, client_public, client_permissions):
681688
:rtype: bool
682689
"""
683690
logger.info(
684-
"Paired with %s with permissions %s.", client_uuid, client_permissions
691+
"Paired with %s with permissions %s.",
692+
client_username_bytes,
693+
client_permissions,
694+
)
695+
self.state.add_paired_client(
696+
client_username_bytes, client_public, client_permissions
685697
)
686-
self.state.add_paired_client(client_uuid, client_public, client_permissions)
687698
self.async_persist()
688699
return True
689700

pyhap/const.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""This module contains constants used by other modules."""
22
MAJOR_VERSION = 4
3-
MINOR_VERSION = 6
3+
MINOR_VERSION = 7
44
PATCH_VERSION = 0
55
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
66
__version__ = f"{__short_version__}.{PATCH_VERSION}"

pyhap/encoder.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from cryptography.hazmat.primitives.asymmetric import ed25519
1111

1212
from .const import CLIENT_PROP_PERMS
13+
from .state import State
1314

1415

1516
class AccessoryEncoder:
@@ -45,7 +46,7 @@ class AccessoryEncoder:
4546
"""
4647

4748
@staticmethod
48-
def persist(fp, state):
49+
def persist(fp, state: State):
4950
"""Persist the state of the given Accessory to the given file object.
5051
5152
Persists:
@@ -61,12 +62,16 @@ def persist(fp, state):
6162
client_properties = {
6263
str(client): props for client, props in state.client_properties.items()
6364
}
65+
client_uuid_to_bytes = {
66+
str(client): bytes.hex(key) for client, key in state.uuid_to_bytes.items()
67+
}
6468
config_state = {
6569
"mac": state.mac,
6670
"config_version": state.config_version,
6771
"paired_clients": paired_clients,
6872
"client_properties": client_properties,
6973
"accessories_hash": state.accessories_hash,
74+
"client_uuid_to_bytes": client_uuid_to_bytes,
7075
"private_key": bytes.hex(
7176
state.private_key.private_bytes(
7277
encoding=serialization.Encoding.Raw,
@@ -84,7 +89,7 @@ def persist(fp, state):
8489
json.dump(config_state, fp)
8590

8691
@staticmethod
87-
def load_into(fp, state):
92+
def load_into(fp, state: State) -> None:
8893
"""Load the accessory state from the given file object into the given Accessory.
8994
9095
@see: AccessoryEncoder.persist
@@ -115,3 +120,7 @@ def load_into(fp, state):
115120
state.public_key = ed25519.Ed25519PublicKey.from_public_bytes(
116121
bytes.fromhex(loaded["public_key"])
117122
)
123+
state.uuid_to_bytes = {
124+
uuid.UUID(client): bytes.fromhex(key)
125+
for client, key in loaded.get("client_uuid_to_bytes", {}).items()
126+
}

0 commit comments

Comments
 (0)