HAP-python 5.1.0__tar.gz → 5.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. {hap_python-5.1.0 → hap_python-5.2.0}/HAP_python.egg-info/PKG-INFO +5 -2
  2. {hap_python-5.1.0 → hap_python-5.2.0}/HAP_python.egg-info/SOURCES.txt +5 -2
  3. {hap_python-5.1.0 → hap_python-5.2.0}/HAP_python.egg-info/requires.txt +4 -0
  4. {hap_python-5.1.0 → hap_python-5.2.0}/PKG-INFO +5 -2
  5. {hap_python-5.1.0 → hap_python-5.2.0}/README.md +1 -1
  6. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/accessory.py +103 -48
  7. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/accessory_driver.py +296 -140
  8. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/camera.py +392 -80
  9. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/camera_talkback.py +36 -6
  10. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/characteristic.py +38 -35
  11. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/const.py +1 -1
  12. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/encoder.py +18 -4
  13. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hap_crypto.py +38 -28
  14. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hap_event.py +4 -6
  15. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hap_handler.py +125 -75
  16. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hap_protocol.py +173 -58
  17. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hap_server.py +24 -16
  18. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hds.py +32 -6
  19. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hds_protocol.py +16 -10
  20. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hds_recording.py +151 -57
  21. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hds_server.py +150 -50
  22. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hksv_recording.py +20 -20
  23. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/hsrp.py +50 -23
  24. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/iid_manager.py +8 -8
  25. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/loader.py +17 -16
  26. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/params.py +8 -1
  27. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/service.py +27 -19
  28. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/state.py +14 -11
  29. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/tlv.py +40 -9
  30. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/util.py +49 -42
  31. hap_python-5.2.0/pyproject.toml +157 -0
  32. {hap_python-5.1.0 → hap_python-5.2.0}/setup.cfg +0 -3
  33. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_accessory.py +48 -34
  34. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_accessory_driver.py +253 -72
  35. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_camera.py +144 -29
  36. hap_python-5.2.0/tests/test_camera_talkback.py +26 -0
  37. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_characteristic.py +48 -37
  38. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_encoder.py +3 -3
  39. hap_python-5.2.0/tests/test_fmp4_packets.py +228 -0
  40. hap_python-5.2.0/tests/test_hap_crypto.py +111 -0
  41. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_hap_handler.py +192 -38
  42. hap_python-5.2.0/tests/test_hap_protocol.py +1272 -0
  43. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_hap_server.py +22 -12
  44. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_hds_recording.py +263 -44
  45. hap_python-5.2.0/tests/test_hksv_gating.py +357 -0
  46. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_hksv_recording.py +210 -27
  47. hap_python-5.2.0/tests/test_hksv_state.py +489 -0
  48. hap_python-5.2.0/tests/test_hksv_transport.py +446 -0
  49. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_hsrp.py +60 -22
  50. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_iid_manager.py +7 -7
  51. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_loader.py +5 -5
  52. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_service.py +15 -14
  53. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_state.py +15 -13
  54. hap_python-5.2.0/tests/test_tlv.py +46 -0
  55. {hap_python-5.1.0 → hap_python-5.2.0}/tests/test_util.py +7 -7
  56. hap_python-5.1.0/MANIFEST.in +0 -1
  57. hap_python-5.1.0/pyproject.toml +0 -169
  58. hap_python-5.1.0/tests/test_hap_crypto.py +0 -32
  59. hap_python-5.1.0/tests/test_hap_protocol.py +0 -798
  60. hap_python-5.1.0/tests/test_tlv.py +0 -29
  61. {hap_python-5.1.0 → hap_python-5.2.0}/AUTHORS +0 -0
  62. {hap_python-5.1.0 → hap_python-5.2.0}/HAP_python.egg-info/dependency_links.txt +0 -0
  63. {hap_python-5.1.0 → hap_python-5.2.0}/HAP_python.egg-info/top_level.txt +0 -0
  64. {hap_python-5.1.0 → hap_python-5.2.0}/LICENSE +0 -0
  65. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/__init__.py +0 -0
  66. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/resources/characteristics.json +0 -0
  67. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/resources/services.json +0 -0
  68. {hap_python-5.1.0 → hap_python-5.2.0}/pyhap/resources/snapshot.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: HAP-python
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: HomeKit Accessory Protocol implementation in python
5
5
  Author: Ivan Kalchev
6
6
  License-Expression: Apache-2.0
@@ -33,9 +33,12 @@ Requires-Dist: base36; extra == "qrcode"
33
33
  Requires-Dist: pyqrcode; extra == "qrcode"
34
34
  Provides-Extra: talkback
35
35
  Requires-Dist: pylibsrtp; extra == "talkback"
36
+ Provides-Extra: bench
37
+ Requires-Dist: psutil; extra == "bench"
38
+ Requires-Dist: py-spy; extra == "bench"
36
39
  Dynamic: license-file
37
40
 
38
- [![PyPI version](https://badge.fury.io/py/HAP-python.svg)](https://badge.fury.io/py/HAP-python) [![Build Status](https://github.com/ikalchev/HAP-python/workflows/CI/badge.svg)](https://github.com/ikalchev/HAP-python) [![codecov](https://codecov.io/gh/ikalchev/HAP-python/branch/master/graph/badge.svg)](https://codecov.io/gh/ikalchev/HAP-python) [![Python Versions](https://img.shields.io/pypi/pyversions/HAP-python.svg)](https://pypi.python.org/pypi/HAP-python/) [![Documentation Status](https://readthedocs.org/projects/hap-python/badge/?version=latest)](http://hap-python.readthedocs.io/en/latest/?badge=latest) [![Downloads](https://pepy.tech/badge/hap-python)](https://pepy.tech/project/hap-python)
41
+ [![PyPI version](https://badge.fury.io/py/HAP-python.svg)](https://badge.fury.io/py/HAP-python) [![CI](https://github.com/ikalchev/HAP-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/ikalchev/HAP-python/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/ikalchev/HAP-python/graph/badge.svg)](https://codecov.io/gh/ikalchev/HAP-python) [![Python Versions](https://img.shields.io/pypi/pyversions/HAP-python.svg)](https://pypi.python.org/pypi/HAP-python/) [![Documentation Status](https://readthedocs.org/projects/hap-python/badge/?version=latest)](http://hap-python.readthedocs.io/en/latest/?badge=latest) [![Downloads](https://pepy.tech/badge/hap-python)](https://pepy.tech/project/hap-python)
39
42
  # HAP-python
40
43
 
41
44
  HomeKit Accessory Protocol implementation in python 3.
@@ -1,9 +1,7 @@
1
1
  AUTHORS
2
2
  LICENSE
3
- MANIFEST.in
4
3
  README.md
5
4
  pyproject.toml
6
- setup.cfg
7
5
  HAP_python.egg-info/PKG-INFO
8
6
  HAP_python.egg-info/SOURCES.txt
9
7
  HAP_python.egg-info/dependency_links.txt
@@ -41,14 +39,19 @@ pyhap/resources/snapshot.jpg
41
39
  tests/test_accessory.py
42
40
  tests/test_accessory_driver.py
43
41
  tests/test_camera.py
42
+ tests/test_camera_talkback.py
44
43
  tests/test_characteristic.py
45
44
  tests/test_encoder.py
45
+ tests/test_fmp4_packets.py
46
46
  tests/test_hap_crypto.py
47
47
  tests/test_hap_handler.py
48
48
  tests/test_hap_protocol.py
49
49
  tests/test_hap_server.py
50
50
  tests/test_hds_recording.py
51
+ tests/test_hksv_gating.py
51
52
  tests/test_hksv_recording.py
53
+ tests/test_hksv_state.py
54
+ tests/test_hksv_transport.py
52
55
  tests/test_hsrp.py
53
56
  tests/test_iid_manager.py
54
57
  tests/test_loader.py
@@ -13,3 +13,7 @@ pyqrcode
13
13
 
14
14
  [Talkback]
15
15
  pylibsrtp
16
+
17
+ [bench]
18
+ psutil
19
+ py-spy
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: HAP-python
3
- Version: 5.1.0
3
+ Version: 5.2.0
4
4
  Summary: HomeKit Accessory Protocol implementation in python
5
5
  Author: Ivan Kalchev
6
6
  License-Expression: Apache-2.0
@@ -33,9 +33,12 @@ Requires-Dist: base36; extra == "qrcode"
33
33
  Requires-Dist: pyqrcode; extra == "qrcode"
34
34
  Provides-Extra: talkback
35
35
  Requires-Dist: pylibsrtp; extra == "talkback"
36
+ Provides-Extra: bench
37
+ Requires-Dist: psutil; extra == "bench"
38
+ Requires-Dist: py-spy; extra == "bench"
36
39
  Dynamic: license-file
37
40
 
38
- [![PyPI version](https://badge.fury.io/py/HAP-python.svg)](https://badge.fury.io/py/HAP-python) [![Build Status](https://github.com/ikalchev/HAP-python/workflows/CI/badge.svg)](https://github.com/ikalchev/HAP-python) [![codecov](https://codecov.io/gh/ikalchev/HAP-python/branch/master/graph/badge.svg)](https://codecov.io/gh/ikalchev/HAP-python) [![Python Versions](https://img.shields.io/pypi/pyversions/HAP-python.svg)](https://pypi.python.org/pypi/HAP-python/) [![Documentation Status](https://readthedocs.org/projects/hap-python/badge/?version=latest)](http://hap-python.readthedocs.io/en/latest/?badge=latest) [![Downloads](https://pepy.tech/badge/hap-python)](https://pepy.tech/project/hap-python)
41
+ [![PyPI version](https://badge.fury.io/py/HAP-python.svg)](https://badge.fury.io/py/HAP-python) [![CI](https://github.com/ikalchev/HAP-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/ikalchev/HAP-python/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/ikalchev/HAP-python/graph/badge.svg)](https://codecov.io/gh/ikalchev/HAP-python) [![Python Versions](https://img.shields.io/pypi/pyversions/HAP-python.svg)](https://pypi.python.org/pypi/HAP-python/) [![Documentation Status](https://readthedocs.org/projects/hap-python/badge/?version=latest)](http://hap-python.readthedocs.io/en/latest/?badge=latest) [![Downloads](https://pepy.tech/badge/hap-python)](https://pepy.tech/project/hap-python)
39
42
  # HAP-python
40
43
 
41
44
  HomeKit Accessory Protocol implementation in python 3.
@@ -1,4 +1,4 @@
1
- [![PyPI version](https://badge.fury.io/py/HAP-python.svg)](https://badge.fury.io/py/HAP-python) [![Build Status](https://github.com/ikalchev/HAP-python/workflows/CI/badge.svg)](https://github.com/ikalchev/HAP-python) [![codecov](https://codecov.io/gh/ikalchev/HAP-python/branch/master/graph/badge.svg)](https://codecov.io/gh/ikalchev/HAP-python) [![Python Versions](https://img.shields.io/pypi/pyversions/HAP-python.svg)](https://pypi.python.org/pypi/HAP-python/) [![Documentation Status](https://readthedocs.org/projects/hap-python/badge/?version=latest)](http://hap-python.readthedocs.io/en/latest/?badge=latest) [![Downloads](https://pepy.tech/badge/hap-python)](https://pepy.tech/project/hap-python)
1
+ [![PyPI version](https://badge.fury.io/py/HAP-python.svg)](https://badge.fury.io/py/HAP-python) [![CI](https://github.com/ikalchev/HAP-python/actions/workflows/ci.yaml/badge.svg)](https://github.com/ikalchev/HAP-python/actions/workflows/ci.yaml) [![codecov](https://codecov.io/gh/ikalchev/HAP-python/graph/badge.svg)](https://codecov.io/gh/ikalchev/HAP-python) [![Python Versions](https://img.shields.io/pypi/pyversions/HAP-python.svg)](https://pypi.python.org/pypi/HAP-python/) [![Documentation Status](https://readthedocs.org/projects/hap-python/badge/?version=latest)](http://hap-python.readthedocs.io/en/latest/?badge=latest) [![Downloads](https://pepy.tech/badge/hap-python)](https://pepy.tech/project/hap-python)
2
2
  # HAP-python
3
3
 
4
4
  HomeKit Accessory Protocol implementation in python 3.
@@ -1,11 +1,13 @@
1
1
  """Module for the Accessory classes."""
2
2
 
3
+ from collections.abc import Callable, Iterable
3
4
  import itertools
4
5
  import logging
5
- from typing import TYPE_CHECKING, Any, Callable, Dict, Iterable, List, Optional
6
+ from typing import TYPE_CHECKING, Any
6
7
  from uuid import UUID
7
8
 
8
9
  from . import SUPPORT_QR_CODE, util
10
+ from .characteristic import Characteristic
9
11
  from .const import (
10
12
  CATEGORY_BRIDGE,
11
13
  CATEGORY_OTHER,
@@ -26,7 +28,6 @@ if SUPPORT_QR_CODE:
26
28
 
27
29
  if TYPE_CHECKING:
28
30
  from .accessory_driver import AccessoryDriver
29
- from .characteristic import Characteristic
30
31
 
31
32
 
32
33
  HAP_PROTOCOL_INFORMATION_SERVICE_UUID = UUID("000000A2-0000-1000-8000-0026BB765291")
@@ -45,9 +46,9 @@ class Accessory:
45
46
  def __init__(
46
47
  self,
47
48
  driver: "AccessoryDriver",
48
- display_name: Optional[str],
49
- aid: Optional[int] = None,
50
- iid_manager: Optional[IIDManager] = None,
49
+ display_name: str | None,
50
+ aid: int | None = None,
51
+ iid_manager: IIDManager | None = None,
51
52
  ) -> None:
52
53
  """Initialise with the given properties.
53
54
 
@@ -60,12 +61,12 @@ class Accessory:
60
61
  will assign the standalone AID to this `Accessory`.
61
62
  :type aid: int
62
63
  """
63
- self.aid: Optional[int] = aid
64
- self.display_name: Optional[str] = display_name
64
+ self.aid: int | None = aid
65
+ self.display_name: str | None = display_name
65
66
  self.driver = driver
66
- self.services: List[Service] = []
67
+ self.services: list[Service] = []
67
68
  self.iid_manager = iid_manager or IIDManager()
68
- self.setter_callback: Optional[Callable[[Any], None]] = None
69
+ self.setter_callback: Callable[[Any], None] | None = None
69
70
 
70
71
  self.add_info_service()
71
72
  if aid == STANDALONE_AID:
@@ -99,7 +100,7 @@ class Accessory:
99
100
  serv_info.configure_char("SerialNumber", value="default")
100
101
  self.add_service(serv_info)
101
102
 
102
- def add_protocol_version_service(self):
103
+ def add_protocol_version_service(self) -> None:
103
104
  """Helper method to add the required HAP Protocol Information service"""
104
105
  serv_hap_proto_info = Service(
105
106
  HAP_PROTOCOL_INFORMATION_SERVICE_UUID, "HAPProtocolInformation"
@@ -109,10 +110,15 @@ class Accessory:
109
110
  self.add_service(serv_hap_proto_info)
110
111
 
111
112
  def set_info_service(
112
- self, firmware_revision=None, manufacturer=None, model=None, serial_number=None
113
- ):
113
+ self,
114
+ firmware_revision: str | None = None,
115
+ manufacturer: str | None = None,
116
+ model: str | None = None,
117
+ serial_number: str | None = None,
118
+ ) -> None:
114
119
  """Quick assign basic accessory information."""
115
120
  serv_info = self.get_service("AccessoryInformation")
121
+ assert serv_info is not None # noqa: S101
116
122
  if firmware_revision:
117
123
  serv_info.configure_char("FirmwareRevision", value=firmware_revision)
118
124
  if manufacturer:
@@ -131,21 +137,21 @@ class Accessory:
131
137
 
132
138
  def add_preload_service(
133
139
  self,
134
- service: Service,
135
- chars: Optional[Iterable["Characteristic"]] = None,
136
- unique_id: Optional[str] = None,
140
+ service: str,
141
+ chars: str | Iterable[str] | None = None,
142
+ unique_id: str | int | None = None,
137
143
  ) -> Service:
138
144
  """Create a service with the given name and add it to this acc."""
139
- service = self.driver.loader.get_service(service)
145
+ serv: Service = self.driver.loader.get_service(service)
140
146
  if unique_id is not None:
141
- service.unique_id = unique_id
147
+ serv.unique_id = unique_id
142
148
  if chars:
143
- chars = chars if isinstance(chars, list) else [chars]
144
- for char_name in chars:
149
+ char_names = [chars] if isinstance(chars, str) else chars
150
+ for char_name in char_names:
145
151
  char = self.driver.loader.get_char(char_name)
146
- service.add_characteristic(char)
147
- self.add_service(service)
148
- return service
152
+ serv.add_characteristic(char)
153
+ self.add_service(serv)
154
+ return serv
149
155
 
150
156
  def set_primary_service(self, primary_service: Service) -> None:
151
157
  """Set the primary service of the acc."""
@@ -157,8 +163,8 @@ class Accessory:
157
163
 
158
164
  This also assigns unique IIDS to the services and their Characteristics.
159
165
 
160
- .. note:: Do not add or remove characteristics from services that have been added
161
- to an Accessory, as this will lead to inconsistent IIDs.
166
+ .. note:: Do not add or remove characteristics from services that have
167
+ been added to an Accessory, as this will lead to inconsistent IIDs.
162
168
 
163
169
  :param servs: Variable number of services to add to this Accessory.
164
170
  :type: Service
@@ -171,7 +177,7 @@ class Accessory:
171
177
  c.broker = self
172
178
  self.iid_manager.assign(c)
173
179
 
174
- def get_service(self, name: str) -> Optional[Service]:
180
+ def get_service(self, name: str) -> Service | None:
175
181
  """Return a Service with the given name.
176
182
 
177
183
  A single Service is returned even if more than one Service with the same name
@@ -208,13 +214,12 @@ class Accessory:
208
214
  int(self.driver.state.pincode.replace(b"-", b""), 10) & 0x7FFFFFFF
209
215
  ) # pincode
210
216
 
211
- # pylint: disable-next=possibly-used-before-assignment
212
- encoded_payload = base36.dumps(payload).upper()
217
+ encoded_payload: str = base36.dumps(payload).upper()
213
218
  encoded_payload = encoded_payload.rjust(9, "0")
214
219
 
215
220
  return "X-HM://" + encoded_payload + self.driver.state.setup_id
216
221
 
217
- def get_characteristic(self, aid: int, iid: int) -> Optional["Characteristic"]:
222
+ def get_characteristic(self, aid: int, iid: int) -> "Characteristic | None":
218
223
  """Get the characteristic for the given IID.
219
224
 
220
225
  The AID is used to verify if the search is in the correct accessory.
@@ -222,9 +227,10 @@ class Accessory:
222
227
  if aid != self.aid:
223
228
  return None
224
229
 
225
- return self.iid_manager.get_obj(iid)
230
+ obj = self.iid_manager.get_obj(iid)
231
+ return obj if isinstance(obj, Characteristic) else None
226
232
 
227
- def to_HAP(self, include_value: bool = True) -> Dict[str, Any]:
233
+ def to_HAP(self, include_value: bool = True) -> dict[str, Any]:
228
234
  """A HAP representation of this Accessory.
229
235
 
230
236
  :return: A HAP representation of this accessory. For example:
@@ -248,7 +254,7 @@ class Accessory:
248
254
  ],
249
255
  }
250
256
 
251
- def setup_message(self):
257
+ def setup_message(self) -> None:
252
258
  """Print setup message to console.
253
259
 
254
260
  For QRCode `base36`, `pyqrcode` are required.
@@ -277,7 +283,9 @@ class Accessory:
277
283
  )
278
284
 
279
285
  @staticmethod
280
- def run_at_interval(seconds):
286
+ def run_at_interval(
287
+ seconds: float,
288
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
281
289
  """Decorator that runs decorated method every x seconds, until stopped.
282
290
 
283
291
  Can be used with normal and async methods.
@@ -293,8 +301,8 @@ class Accessory:
293
301
  :type seconds: float
294
302
  """
295
303
 
296
- def _repeat(func):
297
- async def _wrapper(self, *args):
304
+ def _repeat(func: Callable[..., Any]) -> Callable[..., Any]:
305
+ async def _wrapper(self: Any, *args: Any) -> None:
298
306
  while True:
299
307
  await self.driver.async_add_job(func, self, *args)
300
308
  if await util.event_wait(self.driver.aio_stop_event, seconds):
@@ -304,22 +312,43 @@ class Accessory:
304
312
 
305
313
  return _repeat
306
314
 
307
- async def run(self):
315
+ async def run(self) -> None:
308
316
  """Called when the Accessory should start doing its thing.
309
317
 
310
318
  Called when HAP server is running, advertising is set, etc.
311
319
  Can be overridden with a normal or async method.
312
320
  """
313
321
 
314
- async def stop(self):
315
- """Called when the Accessory should stop what is doing and clean up any resources.
322
+ async def stop(self) -> None:
323
+ """Called when the Accessory should stop what it is doing and clean up
324
+ any resources.
316
325
 
317
326
  Can be overridden with a normal or async method.
318
327
  """
319
328
 
329
+ def restore_accessory_state(self) -> None:
330
+ """Apply this accessory's entry of ``driver.state.accessory_states``.
331
+
332
+ The driver calls this after it loads its state file, for accessories
333
+ built before that. Does nothing by default.
334
+ """
335
+
336
+ def reset_accessory_state(self) -> None:
337
+ """Forget settings made by the previous home.
338
+
339
+ The driver calls this when the last pairing is removed. Does nothing by
340
+ default.
341
+ """
342
+
320
343
  # Driver
321
344
 
322
- def publish(self, value, sender, sender_client_addr=None, immediate=False):
345
+ def publish(
346
+ self,
347
+ value: Any,
348
+ sender: Service | Characteristic,
349
+ sender_client_addr: Any = None,
350
+ immediate: bool = False,
351
+ ) -> None:
323
352
  """Append AID and IID of the sender and forward it to the driver.
324
353
 
325
354
  Characteristics call this method to send updates.
@@ -349,13 +378,13 @@ class Bridge(Accessory):
349
378
  def __init__(
350
379
  self,
351
380
  driver: "AccessoryDriver",
352
- display_name: Optional[str],
353
- iid_manager: Optional[IIDManager] = None,
381
+ display_name: str | None,
382
+ iid_manager: IIDManager | None = None,
354
383
  ) -> None:
355
384
  super().__init__(
356
385
  driver, display_name, aid=STANDALONE_AID, iid_manager=iid_manager
357
386
  )
358
- self.accessories = {} # aid: acc
387
+ self.accessories: dict[int, Accessory] = {} # aid: acc
359
388
 
360
389
  def add_accessory(self, acc: "Accessory") -> None:
361
390
  """Add the given ``Accessory`` to this ``Bridge``.
@@ -364,16 +393,17 @@ class Bridge(Accessory):
364
393
  unique among all the ``Accessories`` in the same `Bridge`. If the given
365
394
  ``Accessory``'s AID is None, a unique AID will be assigned to it. Otherwise,
366
395
  it will be verified that the AID is not the standalone aid (``STANDALONE_AID``)
367
- and that there is no other ``Accessory`` already in this ``Bridge`` with that AID.
396
+ and that there is no other ``Accessory`` already in this ``Bridge``
397
+ with that AID.
368
398
 
369
399
  .. note:: A ``Bridge`` cannot be added to another ``Bridge``.
370
400
 
371
401
  :param acc: The ``Accessory`` to be bridged.
372
402
  :type acc: Accessory
373
403
 
374
- :raise ValueError: When the given ``Accessory`` is of category ``CATEGORY_BRIDGE``
375
- or if the AID of the ``Accessory`` clashes with another ``Accessory`` already in this
376
- ``Bridge``.
404
+ :raise ValueError: When the given ``Accessory`` is of category
405
+ ``CATEGORY_BRIDGE`` or if the AID of the ``Accessory`` clashes with
406
+ another ``Accessory`` already in this ``Bridge``.
377
407
  """
378
408
  if acc.category == CATEGORY_BRIDGE:
379
409
  raise ValueError("Bridges cannot be bridged")
@@ -390,7 +420,11 @@ class Bridge(Accessory):
390
420
 
391
421
  self.accessories[acc.aid] = acc
392
422
 
393
- def to_HAP(self, include_value: bool = True) -> List[Dict[str, Any]]:
423
+ # A Bridge serializes one HAP dict per bridged accessory, which
424
+ # legitimately widens the parent's single-dict return type.
425
+ def to_HAP( # ty: ignore[invalid-method-override]
426
+ self, include_value: bool = True
427
+ ) -> list[dict[str, Any]]:
394
428
  """Returns a HAP representation of itself and all contained accessories.
395
429
 
396
430
  .. seealso:: Accessory.to_HAP
@@ -400,10 +434,11 @@ class Bridge(Accessory):
400
434
  for acc in (super(), *self.accessories.values())
401
435
  ]
402
436
 
403
- def get_characteristic(self, aid: int, iid: int) -> Optional["Characteristic"]:
437
+ def get_characteristic(self, aid: int, iid: int) -> "Characteristic | None":
404
438
  """.. seealso:: Accessory.to_HAP"""
405
439
  if self.aid == aid:
406
- return self.iid_manager.get_obj(iid)
440
+ obj = self.iid_manager.get_obj(iid)
441
+ return obj if isinstance(obj, Characteristic) else None
407
442
 
408
443
  acc = self.accessories.get(aid)
409
444
  if acc is None:
@@ -411,6 +446,26 @@ class Bridge(Accessory):
411
446
 
412
447
  return acc.get_characteristic(aid, iid)
413
448
 
449
+ def restore_accessory_state(self) -> None:
450
+ """Restore the state of each bridged accessory."""
451
+ for acc in self.accessories.values():
452
+ try:
453
+ acc.restore_accessory_state()
454
+ except Exception:
455
+ logger.warning(
456
+ "Failed to restore the state of %s", acc.display_name, exc_info=True
457
+ )
458
+
459
+ def reset_accessory_state(self) -> None:
460
+ """Reset the state of each bridged accessory."""
461
+ for acc in self.accessories.values():
462
+ try:
463
+ acc.reset_accessory_state()
464
+ except Exception:
465
+ logger.warning(
466
+ "Failed to reset the state of %s", acc.display_name, exc_info=True
467
+ )
468
+
414
469
  async def run(self) -> None:
415
470
  """Schedule tasks for each of the accessories' run method."""
416
471
  for acc in self.accessories.values():