Caching Architecture¶
This guide documents the caching system in aiohomematic, including storage types, invalidation strategies, and performance considerations.
Overview¶
The caching system is organized into three tiers:
| Tier | Storage | Purpose | Survives Restart |
|---|---|---|---|
| Persistent | Disk (JSON) | Device/paramset metadata | Yes |
| Dynamic | Memory | Runtime values, commands | No |
| Visibility | Memory | Parameter filtering rules | No |
Persistent Caches¶
Long-lived metadata stored on disk in JSON format.
Device Descriptions¶
Stores device and channel topology discovered from the CCU.
| Field | Description |
|---|---|
| Schema Version | 2 (normalized descriptions) |
| Contents | Device addresses, channels, types |
| Invalidation | Schema version bump or manual clear |
Paramset Descriptions¶
Stores parameter metadata (MIN/MAX/FLAGS/etc.) for each device type.
| Field | Description |
|---|---|
| Schema Version | 3 (with device-specific patches) |
| Contents | Parameter definitions per device/channel |
| Invalidation | Schema version bump or manual clear |
Incident Store¶
Stores diagnostic snapshots for troubleshooting.
| Field | Description |
|---|---|
| Retention | 7 days default |
| Max Per Type | 20 incidents |
| Loading | On-demand (diagnostics request) |
Dynamic Caches¶
Short-lived, memory-resident data that expires automatically.
Central Data Cache¶
Caches device parameter values for fast access.
| Setting | Value |
|---|---|
| Max Age | 15 seconds |
| Scope | Per interface |
| Invalidation | TTL expiry or event-driven |
Special Behavior: During initialization, expiration is disabled to prevent cache misses while devices are being created.
On-demand refresh: refresh_if_expired() refetches the bulk snapshot for an interface whose entry has expired. The initial value path uses it for the interfaces in INTERFACES_SKIPPING_INIT_GETVALUE_FALLBACK, where the snapshot is the only source of an initial value and the consumer (Home Assistant adding its entities) runs later than the TTL. Concurrent callers share one refresh per interface.
Device Details Cache¶
Caches enriched device metadata (names, rooms, functions).
| Setting | Value |
|---|---|
| Refresh Interval | 5 seconds (MAX_CACHE_AGE / 3) |
| Source | Rega script calls |
| Contents | Human-readable names, room assignments |
Command Tracker¶
Tracks recently sent commands per data point.
| Setting | Value |
|---|---|
| TTL | 60 seconds |
| Max Size | 500 entries per interface |
| Warning Threshold | 400 entries |
| Eviction | Remove oldest 20% when full |
Ping/Pong Tracker¶
Tracks connection health via ping/pong events.
| Setting | Value |
|---|---|
| TTL | 300 seconds |
| Max Size | 100 entries per interface |
| Structure | FIFO queue |
Cache Invalidation Patterns¶
Schema Migration (Coordinated Clear)¶
When the schema version changes:
- Both device and paramset caches are checked
- If either has a version mismatch, both are cleared
- Caches are rebuilt together from the CCU
- Ensures consistency between related caches
Pessimistic Updates (Schedule Data)¶
Climate schedules use pessimistic cache updates:
set_schedule() → CCU writes data → CONFIG_PENDING=False event
↓
Cache refreshed from CCU
↓
data_point_updated event
Benefits:
- Cache always matches CCU state
- Handles CCU-side validation/rounding
- No partial or inconsistent states
TTL-Based Expiration¶
Dynamic caches expire based on time-to-live:
| Cache | TTL | Behavior |
|---|---|---|
| Data Cache | 15s | Returns None after expiry |
| Command Tracker | 60s | Lazy cleanup on access |
| Ping/Pong | 300s | Removed during next operation |
Size-Based Eviction¶
When memory limits are reached:
- Cleanup Threshold: Remove expired entries first
- Warning Threshold: Log warning at 80% capacity
- Hard Eviction: Remove oldest 20% at max capacity
Performance Strategies¶
Hash-Based Change Detection¶
Avoids redundant disk writes when data hasn't changed.
Debounced Saves¶
Batches multiple updates into a single disk write.
Disabled Expiration During Init¶
During startup, cache expiration is disabled to prevent misses while devices are being created (which may take longer than the normal TTL).
Two-Phase Load¶
- Check schema version before processing
- If outdated: signal cache rebuild
- If valid: process and rebuild indexes
Ensures schema migrations are atomic.
Storage Locations¶
Default Location¶
Home Assistant Integration¶
When running in Home Assistant, caches use HA's native storage:
The StorageFactoryProtocol allows seamless substitution.
Cache Statistics¶
Each cache tracks performance metrics:
CacheStatistics:
hits: int # Successful lookups
misses: int # Failed lookups
evictions: int # Entries removed
hit_rate: float # (hits / total) * 100
Access via CacheCoordinator for diagnostics.
CacheCoordinator¶
The CacheCoordinator manages all caches centrally.
Lifecycle Methods¶
| Method | Purpose |
|---|---|
load_all() | Load persistent caches on startup |
save_all() | Save persistent caches on shutdown |
clear_all() | Clear all caches (rebuild required) |
clear_on_stop() | Clear memory caches only |
Event Subscriptions¶
| Event | Action |
|---|---|
DeviceRemovedEvent | Remove device from all caches |
DataFetchCompletedEvent | Trigger cache save |
Memory Limits Summary¶
| Component | Max Size | Notes |
|---|---|---|
| Command Tracker | 500 | Per interface |
| Ping/Pong Tracker | 100 | Per interface |
| Incident Store | 20 | Per incident type |
Key Constants¶
# Expiration
MAX_CACHE_AGE = 15 # Data cache (seconds)
LAST_COMMAND_SEND_STORE_TIMEOUT = 60 # Command tracker (seconds)
PING_PONG_MISMATCH_COUNT_TTL = 300 # Ping/pong (seconds)
# Memory limits
COMMAND_TRACKER_MAX_SIZE = 500
COMMAND_TRACKER_WARNING_THRESHOLD = 400
PING_PONG_CACHE_MAX_SIZE = 100
INCIDENT_STORE_MAX_PER_TYPE = 20
# Refresh intervals
periodic_refresh_interval = 15 # Device details (seconds)
Best Practices¶
For Library Consumers¶
- Don't bypass caches - Use provided APIs for data access
- Handle cache misses - Methods may return
Nonewhen data expires - Subscribe to events - Use
data_point_updatedevents instead of polling
For Contributors¶
- Coordinate cache clears - Use
CacheCoordinator.clear_all()not individual clears - Respect schema versions - Bump version when changing cache structure
- Add statistics - Track hits/misses for new caches