# Aiohomekit > aiohomekit is an asyncio HomeKit client library for Python. It provides APIs for: --- # aiohomekit API Documentation ## Overview aiohomekit is an asyncio HomeKit client library for Python. It provides APIs for: - Pairing with HomeKit accessories - Discovering HomeKit devices over mDNS/Bonjour - Controlling HomeKit accessories over IP, BLE, and CoAP protocols - Handling HomeKit events and notifications This library is primarily used by Home Assistant for HomeKit device integration. ## Installation ```bash pip install aiohomekit ``` ## Core Components ### Controller Classes The main entry point is the `Controller` class which manages connections to HomeKit accessories. ```python from aiohomekit import Controller # Create controller instance controller = Controller() ``` ### Key Exceptions aiohomekit defines several exception classes for error handling: - `HomeKitException` - Base exception class - `AccessoryDisconnectedError` - Accessory went offline - `AccessoryNotFoundError` - Accessory not found - `AlreadyPairedError` - Already paired with accessory - `AuthenticationError` - Authentication failed - `UnpairedError` - Accessory not paired - `ConfigurationError` - Configuration issue - `ConfigLoadingError` - Error loading config - `ConfigSavingError` - Error saving config - `BackoffError` - Backoff in progress - `BluetoothAdapterError` - Bluetooth adapter issue - `BusyError` - Device busy - `CharacteristicPermissionError` - Permission denied on characteristic - `FormatError` - Format error - `HttpException` - HTTP error - `IncorrectPairingIdError` - Incorrect pairing ID - `InvalidAuthTagError` - Invalid authentication tag - `InvalidError` - Invalid value - `InvalidSignatureError` - Invalid signature - `MaxPeersError` - Maximum peers reached - `MaxTriesError` - Maximum retries exceeded - `ProtocolError` - Protocol error - `RequestRejected` - Request was rejected - `UnavailableError` - Service unavailable - `UnknownError` - Unknown error ## Transport Types aiohomekit supports multiple transport protocols through the `TransportType` enum: - **IP** - HomeKit over IP protocol - **BLE** - Bluetooth Low Energy (Note: Currently supported via bleak library) - **CoAP** - Constrained Application Protocol for low-power devices ## Project Structure ### Core Modules - `aiohomekit.controller` - Main Controller class and TransportType - `aiohomekit.protocol` - TLV8 and status code handling - `aiohomekit.crypto` - Cryptographic primitives (ChaCha20-Poly1305, SRP, HKDF) - `aiohomekit.tlv8` - TLV8 encoding/decoding - `aiohomekit.pdu` - Protocol Data Unit handling - `aiohomekit.characteristic_cache` - Caching for characteristics ### Protocol Implementation The library implements the HomeKit Accessory Protocol (HAP) with: - TLV8 (Type-Length-Value) encoding for HomeKit protocol messages - ChaCha20-Poly1305 authenticated encryption - SRP (Secure Remote Password) for secure pairing - HKDF (HMAC-based Key Derivation Function) for key derivation ### BLE Support BLE (Bluetooth Low Energy) support includes: - Device discovery via Bluetooth advertisements - BLE-specific client implementation - Manufacturer data parsing - Key management for BLE connections ## DeviceCompatibility The library is primarily tested with: - Phillips Hue Bridge - Eve Extend Bridge - Various other HomeKit accessories See the [Home Assistant homekit_controller](https://github.com/home-assistant/core/tree/dev/homeassistant/components/homekit_controller) for real-world usage examples. ## Device Compatibility Issues The library handles known quirks with HomeKit devices: - **JSON Formatting**: Some devices cannot handle spaces in JSON (e.g., `{"foo": "bar"}` fails, `{"foo":"bar"}` works) - **Boolean Encoding**: Some devices only support specific boolean representations; the library uses `0` or `1` - **HTTP Headers**: Some devices are sensitive to header ordering and presence; the library matches iOS client behavior - **TCP Packet Splitting**: Some devices are sensitive to message splitting; the library ensures atomic writes ## Configuration The library uses configuration files to store: - Pairing information - Controller identity - Accessory metadata - Characteristic cache Configuration can be loaded and saved through the Controller API. ## Home Assistant Integration aiohomekit is the primary HomeKit integration library for Home Assistant: - Repository: [homekit_controller](https://github.com/home-assistant/core/tree/dev/homeassistant/components/homekit_controller) - Used for discovering and controlling HomeKit devices - Handles characteristic updates and notifications ## Contributing Contributions are welcome but should be tested with real HomeKit devices when possible. The project prioritizes compatibility with real devices over strict spec compliance. ## FAQ ### Does aiohomekit support creating HomeKit accessories? No, aiohomekit is client-only. For server/accessory implementations, see: - [homekit_python](https://github.com/jlusiardi/homekit_python/) - [HAP-python](https://github.com/ikalchev/HAP-python) ### Why not use a standard HTTP library like aiohttp? The library uses hand-rolled HTTP for several reasons: - Need for custom session security without monkey patching - HAP requires handling responses without corresponding requests (events) - Real-world devices require strict formatting compliance - Some responses are technically non-standard HTTP - Maintaining byte-for-byte accuracy on the write path is critical ### Why does aiohomekit use hand-rolled cryptography implementations? For compatibility with Home Assistant's build process: - All dependencies must be pip-installable - Cross-platform wheels must be available (including Raspberry Pi) - Should not introduce hard system library dependencies - Libraries used should already be in Home Assistant dependencies ## License Apache License 2.0 ## Repository GitHub: https://github.com/Jc2k/aiohomekit --- # aiohomekit Architecture ## Module Organization ```text aiohomekit/ ├── __init__.py # Main exports: Controller, exceptions ├── controller/ # Core controller implementation │ ├── abstract.py # Abstract base classes and TransportType │ ├── controller.py # Main Controller class │ ├── ip/ # IP protocol implementation │ ├── ble/ # Bluetooth Low Energy implementation │ └── coap/ # CoAP protocol implementation ├── crypto/ # Cryptographic primitives │ ├── chacha20poly1305.py # AEAD cipher │ ├── hkdf.py # Key derivation │ └── srp.py # Secure Remote Password ├── protocol/ # HomeKit protocol │ ├── tlv.py # TLV8 encoding/decoding │ └── statuscodes.py # HAP status codes ├── tlv8.py # TLV8 utilities ├── pdu.py # Protocol Data Units └── characteristic_cache.py # Caching layer ``` ## Protocol Layers ### Layer 1: Transport - **IP**: TCP/HTTP with mDNS discovery - **BLE**: Bluetooth Low Energy with characteristic notifications - **CoAP**: Constrained Application Protocol for low-power devices ### Layer 2: Security - TLV8-encoded messages - ChaCha20-Poly1305 AEAD encryption for IP transport - SRP 3072-bit for pairing - HKDF for key derivation ### Layer 3: HAP (HomeKit Accessory Protocol) - Service discovery and enumeration - Characteristic read/write operations - Event subscriptions and notifications - Accessory metadata (name, category, features) ### Layer 4: Application - Controller class - main entry point - Pairing management - Accessory and characteristic caching - Event handling ## Async Architecture All operations are async-first using Python's `asyncio`: - Non-blocking network I/O - Concurrent operations on multiple accessories - Event-driven subscription handling - Timeout management with `async-timeout` ## Data Flow Examples ### Pairing Flow 1. Start pairing via Controller 2. Exchange SRP identity and salt 3. Compute session key using HKDF 4. Exchange pairing information (TLV8 encoded) 5. Store pairing credentials in configuration ### Read Characteristic Flow 1. Open connection to accessory (cached if possible) 2. Build HAP read request (TLV8 format) 3. Encrypt with session key (ChaCha20-Poly1305) 4. Send via transport (IP/BLE/CoAP) 5. Receive and decrypt response 6. Parse characteristic value 7. Return to caller ### Subscribe to Events Flow 1. Open connection to accessory 2. Send subscription request (HAP format) 3. Keep connection open 4. Receive event notifications 5. Parse and deliver to subscribers 6. Maintain connection until unsubscribed ## Key Design Decisions ### Why Hand-Rolled HTTP? - Need for low-level control over request/response handling - HAP requires handling unsolicited responses (events) - Device compatibility requires strict formatting - Some device quirks require non-standard behavior ### Why Hand-Rolled Crypto? - Minimize external dependencies - Ensure cross-platform wheel availability - Avoid hard system library dependencies - Integrate seamlessly with Home Assistant ### Why Async-First? - Home Assistant is fully async - Allows concurrent operations on multiple devices - Better resource utilization - Natural fit for event-driven HomeKit protocol ## Configuration Storage The library stores configuration in JSON format containing: - Pairing ID and keys for each accessory - Controller identity (UUID, LTPK) - Characteristic metadata cache - Device-specific quirk information Configuration files are typically stored in Home Assistant's config directory. ## Error Handling Strategy The library uses specific exception types to allow fine-grained error handling: - Transient errors (backoff, busy) trigger retries - Pairing errors (already paired) are fatal - Connection errors attempt reconnection - Protocol errors are reported with context ## Testing The library uses pytest for testing with: - Unit tests for cryptography and protocol layers - Integration tests with real HomeKit devices - Mock tests for accessories and protocols - Coverage targets for critical paths --- # Source: https://github.com/Jc2k/aiohomekit/blob/master/pyproject.toml ```toml [project] name = "aiohomekit" version = "3.2.20" description = "An asyncio HomeKit client" authors = [{ name = "John Carr", email = "john.carr@unrouted.co.uk" }] license = "Apache-2.0" readme = "README.md" requires-python = ">=3.10" keywords = ["HomeKit", "home", "automation"] dynamic = ["classifiers", "dependencies"] [project.urls] "Homepage" = "https://github.com/Jc2k/aiohomekit" "Repository" = "https://github.com/Jc2k/aiohomekit" [tool.poetry] classifiers = [ "Topic :: Home Automation", "Intended Audience :: Developers", "Intended Audience :: End Users/Desktop", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11" ] include = ["aiohomekit/py.typed"] [tool.poetry.dependencies] python = "^3.10,<3.14" cryptography = ">=2.9.2" zeroconf = ">=0.132.2" commentjson = "^0.9.0" aiocoap = ">=0.4.5" bleak = ">=0.22.0" chacha20poly1305-reuseable = ">=0.12.1" bleak-retry-connector = ">=2.9.0" orjson = ">=3.7.8" async-timeout = {version = ">=4.0.2", python = "<3.11"} chacha20poly1305 = "^0.0.3" async-interrupt = ">=1.1.1" aiohappyeyeballs = ">=2.3.0" [tool.poetry.group.dev.dependencies] mypy = "^1.18" flake8 = ">=4.0.1,<8.0.0" pytest = ">=7.2,<9.0" coverage = ">=6.3,<8.0" pylint = ">=2.12.2,<4.0.0" pytest-aiohttp = "^1.0.3" pyupgrade = ">=2.31,<4.0" pytest-cov = ">=5,<8" asynctest = "^0.13.0" aiohttp = ">=3.8.3" ruff = "^0.14.2" [tool.poetry.scripts] aiohomekitctl = "aiohomekit.__main__:sync_main" [tool.pytest.ini_options] minversion = "6.0" asyncio_mode = "auto" [tool.coverage.run] omit = ["tests/*"] [tool.isort] profile = "black" indent = " " force_sort_within_sections = "true" sections = "FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" known_first_party = "aiohomekit,tests" forced_separate = "tests" combine_as_imports = "true" extra_standard_library = "_socket" [build-system] requires = ["poetry_core>=2.1.0"] build-backend = "poetry.core.masonry.api" [tool.ruff] line-length = 110 [tool.ruff.lint] ignore = [ "S101", # use of assert "S104", # S104 Possible binding to all interfaces "PLR0912", # too many to fix right now "TID252", # skip "PLR0913", # too late to make changes here "PLR0911", # would be breaking change "TRY003", # too many to fix "SLF001", # design choice "PLR2004" , # too many to fix "PGH004", # too many to fix "PGH003", # too many to fix "SIM110", # this is slower "PYI034", # enable when we drop Py3.10 "PYI032", # breaks Cython "PYI041", # breaks Cython "PERF401", # Cython: closures inside cpdef functions not yet supported ] select = [ "ASYNC", # async rules "B", # flake8-bugbear "C4", # flake8-comprehensions "S", # flake8-bandit "F", # pyflake "E", # pycodestyle "W", # pycodestyle "UP", # pyupgrade "I", # isort "RUF", # ruff specific "FLY", # flynt "G", # flake8-logging-format , "PERF", # Perflint "PGH", # pygrep-hooks "PIE", # flake8-pie "PL", # pylint "PT", # flake8-pytest-style "PTH", # flake8-pathlib "PYI", # flake8-pyi "RET", # flake8-return "RSE", # flake8-raise , "SIM", # flake8-simplify "SLF", # flake8-self "SLOT", # flake8-slots "T100", # Trace found: {name} used "T20", # flake8-print "TID", # Tidy imports "TRY", # tryceratops ] [tool.ruff.lint.per-file-ignores] "tests/**/*" = [ "D100", "D101", "D102", "D103", "D104", "S101", "SLF001", "PLR2004", # too many to fix right now "PT011", # too many to fix right now "PT006", # too many to fix right now "PGH003", # too many to fix right now "PT007", # too many to fix right now "PT027", # too many to fix right now "PLW0603" , # too many to fix right now "PLR0915", # too many to fix right now "FLY002", # too many to fix right now "PT018", # too many to fix right now "PLR0124", # too many to fix right now "SIM202" , # too many to fix right now "PT012" , # too many to fix right now "TID252", # too many to fix right now "PLR0913", # skip this one "SIM102" , # too many to fix right now "SIM108", # too many to fix right now "T201", # too many to fix right now "PT004", # nice to have ] "bench/**/*" = [ "T201", # intended ] "examples/**/*" = [ "T201", # intended ] "setup.py" = ["D100"] "conftest.py" = ["D100"] "docs/conf.py" = ["D100"] ```