|
57 | 57 |
|
58 | 58 | RETRY_INTERVAL = 60 # seconds |
59 | 59 | MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE = 3 |
60 | | - |
| 60 | +# HomeKit accessories have varying limits on how many characteristics |
| 61 | +# they can handle per request. Since we don't know each device's specific limit, |
| 62 | +# we batch requests to a conservative size to avoid overwhelming any device. |
| 63 | +MAX_CHARACTERISTICS_PER_REQUEST = 49 |
61 | 64 |
|
62 | 65 | BLE_AVAILABILITY_CHECK_INTERVAL = 1800 # seconds |
63 | 66 |
|
@@ -326,16 +329,20 @@ async def async_setup(self) -> None: |
326 | 329 | ) |
327 | 330 | entry.async_on_unload(self._async_cancel_subscription_timer) |
328 | 331 |
|
| 332 | + if transport != Transport.BLE: |
| 333 | + # Although async_populate_accessories_state fetched the accessory database, |
| 334 | + # the /accessories endpoint may return cached values from the accessory's |
| 335 | + # perspective. For example, Ecobee thermostats may report stale temperature |
| 336 | + # values (like 100°C) in their /accessories response after restarting. |
| 337 | + # We need to explicitly poll characteristics to get fresh sensor readings |
| 338 | + # before processing the entity map and creating devices. |
| 339 | + # Use poll_all=True since entities haven't registered their characteristics yet. |
| 340 | + await self.async_update(poll_all=True) |
| 341 | + |
329 | 342 | await self.async_process_entity_map() |
330 | 343 |
|
331 | 344 | if transport != Transport.BLE: |
332 | | - # When Home Assistant starts, we restore the accessory map from storage |
333 | | - # which contains characteristic values from when HA was last running. |
334 | | - # These values are stale and may be incorrect (e.g., Ecobee thermostats |
335 | | - # report 100°C when restarting). We need to poll for fresh values before |
336 | | - # creating entities. Use poll_all=True since entities haven't registered |
337 | | - # their characteristics yet. |
338 | | - await self.async_update(poll_all=True) |
| 345 | + # Start regular polling after entity map is processed |
339 | 346 | self._async_start_polling() |
340 | 347 |
|
341 | 348 | # If everything is up to date, we can create the entities |
@@ -938,20 +945,26 @@ async def async_update( |
938 | 945 | async with self._polling_lock: |
939 | 946 | _LOGGER.debug("Starting HomeKit device update: %s", self.unique_id) |
940 | 947 |
|
941 | | - try: |
942 | | - new_values_dict = await self.get_characteristics(to_poll) |
943 | | - except AccessoryNotFoundError: |
944 | | - # Not only did the connection fail, but also the accessory is not |
945 | | - # visible on the network. |
946 | | - self.async_set_available_state(False) |
947 | | - return |
948 | | - except (AccessoryDisconnectedError, EncryptionError): |
949 | | - # Temporary connection failure. Device may still available but our |
950 | | - # connection was dropped or we are reconnecting |
951 | | - self._poll_failures += 1 |
952 | | - if self._poll_failures >= MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE: |
| 948 | + new_values_dict: dict[tuple[int, int], dict[str, Any]] = {} |
| 949 | + to_poll_list = list(to_poll) |
| 950 | + |
| 951 | + for i in range(0, len(to_poll_list), MAX_CHARACTERISTICS_PER_REQUEST): |
| 952 | + batch = to_poll_list[i : i + MAX_CHARACTERISTICS_PER_REQUEST] |
| 953 | + try: |
| 954 | + batch_values = await self.get_characteristics(batch) |
| 955 | + new_values_dict.update(batch_values) |
| 956 | + except AccessoryNotFoundError: |
| 957 | + # Not only did the connection fail, but also the accessory is not |
| 958 | + # visible on the network. |
953 | 959 | self.async_set_available_state(False) |
954 | | - return |
| 960 | + return |
| 961 | + except (AccessoryDisconnectedError, EncryptionError): |
| 962 | + # Temporary connection failure. Device may still available but our |
| 963 | + # connection was dropped or we are reconnecting |
| 964 | + self._poll_failures += 1 |
| 965 | + if self._poll_failures >= MAX_POLL_FAILURES_TO_DECLARE_UNAVAILABLE: |
| 966 | + self.async_set_available_state(False) |
| 967 | + return |
955 | 968 |
|
956 | 969 | self._poll_failures = 0 |
957 | 970 | self.process_new_events(new_values_dict) |
|
0 commit comments