Repository navigation
Conversation
GET /characteristics returned the stored value of every characteristic, including write-only ones such as Identify or a camera key. The HAP spec only allows reads of characteristics with the paired read (pr) permission and answers everything else with -70405. HAP-NodeJS does the same. The getter is not called for such characteristics. The /accessories database already omitted their value; this adds tests for it.
Any characteristic could be subscribed to, whether or not it has the notify (ev) permission. A controller could then receive the values that another controller wrote to a write-only characteristic. Per the HAP spec, (un)subscribing a characteristic without ev now answers -70406 and leaves the subscriptions unchanged, the same as HAP-NodeJS. A value in the same request entry is not written. Some tests subscribed to Version, which has no ev permission. They now use CurrentDoorState.
A value written to a characteristic without the pw permission still reached its setter. Answer it with -70404 like HAP-NodeJS instead, without calling the characteristic, service or accessory setters. A subscription in the same entry and the other characteristics of the request are still handled.
A write carrying a pid that was never prepared on this connection, was already used or has outlived its TTL was still applied, and its event subscriptions were changed. Only characteristics without a value got -70410. The HAP specification says such a request must be ignored and answered with -70410. Do that for every characteristic in the request, as HAP-NodeJS does. The TTL is now measured with the monotonic clock, so a wall clock jump, e.g. from NTP right after boot, can't expire a timed write.
A write with "r": true got the setter's return value back for any characteristic. The HAP spec ties write responses to the write response (wr) permission, and HAP-NodeJS ignores the request for other characteristics. Do the same. When a write response is sent, it becomes the characteristic's value instead of the written one, which HAP-NodeJS does as well. The sent value is the stored one, after the usual format checks. A response the characteristic can't hold is still sent and the written value is kept. The write response tests are now parametrized and also cover a wr characteristic without paired read: neither the written value nor the response can be read back.
The HAP specification requires a plain write to a characteristic with
the timed write ("tw") permission to be refused with -70410. HAP-NodeJS
does this.
A Home hub has been seen writing HomeKit Camera Active without a timed
write, and the HomeKit Secure Video guide gives Streaming Enabled and
Motion Enabled tw as well. Other controllers may not support timed
writes at all. So the check is opt-in: with enforce_timed_writes=True
the driver refuses such a write, otherwise it applies it and only logs
at debug level.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #536 +/- ##
==========================================
+ Coverage 91.53% 91.57% +0.03%
==========================================
Files 27 27
Lines 4219 4235 +16
Branches 532 537 +5
==========================================
+ Hits 3862 3878 +16
Misses 250 250
Partials 107 107
🚀 New features to boost your workflow:
|
A write response of a characteristic that also has pr was stored as its value, so any paired controller could read it with GET /characteristics or /accessories. Keep it per connection instead: the connection that wrote reads its last response until it closes. Other connections read the characteristic's own value, which the write leaves as it was, also when the setter raises or returns nothing. Any new write of a connection drops its former response. Characteristics without pr keep the response as the value.
RaHehl
marked this pull request as ready for review
November 10, 2026 13:13
client_update_value takes notify=False for a write response request on a readable characteristic, so ev subscribers don't get a request that only the writer may read, e.g. a WiFi configuration with a key. HAPValue moves to pyhap.characteristic next to the formats.
A pid of another type, e.g. a list, raised a TypeError instead of answering -70410.
It set a new default executor on the shared test loop and shut it down when the driver stopped, so later tests on the same xdist worker failed with "cannot schedule new futures after shutdown". The driver now makes its own loop and executor, as a sync start does.
# Conflicts: # pyhap/accessory_driver.py # tests/test_accessory_driver.py
The write only accepts an integer pid, so a prepare with another pid could never be used. A ttl too large for a float is refused too, and an entry expires at the same moment for prepare and write. Tests cover a rejected timed write with several characteristics and an explicit null value on a tw characteristic.
A bool pid, a float ttl and the last moment of a TTL are covered now.
ikalchev
reviewed
Nov 10, 2026
ikalchev
left a comment
Owner
There was a problem hiding this comment.
Just one type nit/question from my side, otherwise good.
| @@ -1060,6 +1075,23 @@ def set_characteristics( | |||
| """ | |||
| queries: list[dict[str, Any]] = chars_query[HAP_REPR_CHARS] | |||
Owner
There was a problem hiding this comment.
nit: Is the query mapping to Any really? Is it not a hap type here?
Contributor
Author
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A write whose
pidwas never prepared on the connection, was already used, or outlived its TTL is now ignored as a whole and every characteristic gets -70410, as the spec says and HAP-NodeJS does. Before, only characteristics without a value got -70410, and values were still written. The TTL now uses the monotonic clock, so a wall clock jump after boot can't expire a lock or garage door command.A plain write to a
twcharacteristic is refused with -70410 only withAccessoryDriver(enforce_timed_writes=True). By default it is applied and logged at debug level. A Home hub wrote HomeKit Camera Active without a timed write in my tests, and the HomeKit Secure Video guide gives Streaming Enabled and Motion Enabledtwtoo, so enforcing it by default would break HKSV. Other controllers, like aiohomekit over IP, don't do timed writes either.A prepare whose
ttlorpidis not an integer is refused with -70410, as the spec defines both as integers.test_start_from_syncnow runs on the driver's own loop and executor, so stopping the driver no longer shuts down the executor of the shared test loop that later tests use.AI use: written with the help of an AI coding assistant (Opus 5.5). I reviewed and tested the changes myself (pytest, ruff, ty).