-
Notifications
You must be signed in to change notification settings - Fork 14
Developer Architecture
Adaptive Cover Pro is a Home Assistant custom integration that automatically controls blinds, awnings, and venetian blinds based on sun position. The integration uses a layered architecture that separates HA state access, pure calculation logic, an override priority pipeline, and focused manager classes from a thin coordinator orchestrator.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Home Assistant Core β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ
β Config Flow (UI Setup) β
β - Multi-step wizard for vertical/horizontal/tilt covers β
β - Options flow for configuration updates β
βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββ
β AdaptiveDataUpdateCoordinator (~1,477 lines) β
β - Thin orchestrator: runs update cycle, routes events β
β - Schedules refreshes (end-time, timed) β
β - Manages toggle properties (automatic_control, etc.) β
β - Delegates to managers, providers, pipeline, diagnostics β
ββββ¬βββββββββββ¬ββββββββββββ¬βββββββββββββ¬βββββββββββββββββββββββββββ
β β β β
βΌ βΌ βΌ βΌ
State Managers Pipeline Diagnostics
Providers (5 classes) (6 handlers) Builder
Role: Thin orchestrator β delegates, does not implement
Responsibilities:
- Runs
_async_update_data()update cycle - Routes entity state changes (sun, cover, motion)
- Schedules timed and end-time refreshes
- Manages toggle properties (
automatic_control,switch_mode,manual_override_mode) - Wires together providers, managers, pipeline, and diagnostics builder
All Home Assistant state reads are isolated here. The rest of the codebase has zero HA imports for data access.
| File | Class | Reads |
|---|---|---|
climate_provider.py |
ClimateProvider |
Temp/weather/presence/lux/irradiance entities β ClimateReadings frozen dataclass |
sun_provider.py |
SunProvider |
Astral location from HA β pure SunData instance |
cover_provider.py |
CoverProvider |
Cover entity state from HA (position, state) |
snapshot.py |
SunSnapshot, CoverStateSnapshot
|
Frozen dataclasses holding unified state for each update cycle |
Benefit: Calculation engine and pipeline handlers are fully testable without HA mocks.
Zero homeassistant imports. Receives pre-computed data from providers.
- Receives
SunData(nothass) - Shared sun position calculations, FOV validation, elevation limits, blind spot detection
- Up/down blinds
- Projects sun rays to calculate required blind height
- Enhanced geometric accuracy: safety margins, edge case handling, optional window depth and sill height support
- Output: blind height in meters β percentage
- In/out awnings
- Uses vertical calculation + trigonometry for horizontal extension
- Output: extension length β percentage
- Slat rotation for venetian blinds
- Calculates optimal slat angle to block sun while allowing light
- Output: degrees β percentage
- NormalCoverState β basic sun position mode
-
ClimateCoverState β receives pre-read
ClimateReadingsfromClimateProvider(no direct HA reads)- Winter: open for solar heating
- Summer: close for heat blocking
- Presence-aware strategies
Focused classes extracted from the coordinator, each owning one responsibility:
| File | Class | Responsibility |
|---|---|---|
manual_override.py |
AdaptiveCoverManager |
Manual override detection and tracking |
grace_period.py |
GracePeriodManager |
Per-command and startup grace periods |
motion.py |
MotionManager |
Motion sensor timeout tracking |
position_verification.py |
PositionVerificationManager |
Periodic position verification and retry |
cover_command.py |
CoverCommandService |
Cover service calls, capability detection, delta checks |
A pluggable priority chain replaces the previous if/elif override logic.
Core types (pipeline/types.py):
-
PipelineContextβ snapshot of current state passed to all handlers -
PipelineResultβ winning handler's decision + full decision trace -
DecisionStepβ one handler's evaluation record
Registry (pipeline/registry.py):
-
PipelineRegistryevaluates handlers in descending priority order - Returns the first
PipelineResultthat takes effect
Handlers (pipeline/handlers/):
| Handler | Priority | Condition |
|---|---|---|
force_override.py |
100 | Force override sensor(s) active |
wind.py |
95 | Wind speed exceeds threshold (stub β passes through until sensors configured) |
motion_timeout.py |
80 | No motion detected within timeout |
manual_override.py |
70 | User manually moved the cover |
climate.py |
50 | Climate mode active and triggered |
solar.py |
40 | Sun in window FOV, direct sun tracking |
cloud_suppression.py |
35 | Cloud coverage suppresses solar radiation (stub β passes through until sensors configured) |
default.py |
0 | Fallback (default position) |
Adding a new override: create one file in pipeline/handlers/, implement OverrideHandler (pipeline/handler.py), register in coordinator.
DiagnosticsBuilder with DiagnosticContext β extracted from coordinator.
Builds:
- Solar position diagnostics
- Position and time window diagnostics
- Sun validity diagnostics
- Climate diagnostics
- Action diagnostics
- Configuration diagnostics
- Decision trace from
PipelineResult
New-generation calculation engine for advanced cover types.
| File | Class | Purpose |
|---|---|---|
engine/sun_geometry.py |
SunGeometry |
Pure sun angle math (gamma, elevation, azimuth relationships) |
engine/covers/venetian.py |
VenetianCoverCalculation |
Dual-axis venetian blind calculations (position + tilt) |
CoverConfig β typed dataclass consolidating all cover configuration options. Replaces raw dict lookups throughout the codebase.
| File | Purpose |
|---|---|
position_utils.py |
PositionConverter: percentage conversion and limit application |
geometry.py |
SafetyMarginCalculator, EdgeCaseHandler for geometric accuracy |
enums.py |
Type-safe enumerations (CoverType, TiltMode, ClimateStrategy, etc.) |
const.py |
Named constants for thresholds, multipliers, and defaults |
helpers.py |
General utility functions |
services/configuration_service.py |
Config entry parsing, parameter extraction |
-
AdaptiveCoverBaseEntityβ shareddevice_info, coordinator handling -
AdaptiveCoverSensorBaseβ base for sensors -
AdaptiveCoverDiagnosticSensorBaseβ base for diagnostic sensors
Sensor Platform (sensor.py): Cover Position (consolidated β includes position explanation and control method as attributes), Start/End Sun Times, diagnostic sensors (sun azimuth/elevation, control status, decision trace, and more)
Switch Platform (switch.py): Automatic Control, Climate Mode, Manual Override
Binary Sensor Platform (binary_sensor.py): Sun Visibility, Position Mismatch
Button Platform (button.py): Manual Override Reset
1. Entity state change (sun / cover / motion)
β
βΌ
2. Coordinator event handler
β
βΌ
3. State providers build snapshot
SunProvider β SunData
ClimateProvider β ClimateReadings
β
βΌ
4. Pure calculation engine
AdaptiveXXXCover.calculate_position()
β
βΌ
5. Override pipeline
PipelineRegistry.evaluate() β PipelineResult
β
βΌ
6. Post-processing
Interpolation, inverse state, position limits (PositionConverter)
β
βΌ
7. Cover commands
CoverCommandService β cover.set_cover_position
β
βΌ
8. DiagnosticsBuilder
Produces diagnostic data + decision trace from PipelineResult
β
βΌ
9. coordinator.data updated β entities refresh
-
nameβ instance name -
sensor_typeβcover_blind/cover_awning/cover_tilt
Window Properties: set_azimuth, fov_left, fov_right, min_elevation, max_elevation, window_height, distance_shaded_area, window_depth, sill_height
Position Limits: min_position, max_position, enable_min_position, enable_max_position
Automation: delta_position, delta_time, start_time, end_time, manual_override_duration, manual_threshold
Climate Mode: temp_entity, presence_entity, weather_entity, temp_low, temp_high, weather_state, lux_entity, lux_threshold, irradiance_entity, irradiance_threshold
Force Override: force_override_sensors, force_override_position
Motion Control: motion_sensors, motion_timeout
Blind Spots: blind_spot_left, blind_spot_right, blind_spot_elevation
The integration provides a comprehensive multi-step configuration UI (config_flow.py):
Enhanced user experience:
- Rich field descriptions: every configuration field includes detailed descriptions with practical examples, recommended values, and context
- Visual units: all numeric selectors display appropriate units (Β°, %, m, cm, minutes, lux, W/mΒ²)
-
Consistent interface:
NumberSelectorwith sliders for most numeric inputs, providing clear min/max bounds - Technical term explanations: concepts like azimuth, FOV, and elevation are explained in user-friendly language
Translation support:
- English is the single source of truth:
translations/en.json - Shipped languages: English (en), German (de), French (fr)
- Additional languages are added via the
acp-translateskill on maintainer request β see the Translations section inCLAUDE.md
Configuration steps:
- Initial setup: choose cover type (vertical/horizontal/tilt)
- Cover-specific settings: dimensions, orientation, tracking parameters
- Automation settings: delta position/time, manual override, start/end times
- Climate mode (optional): temperature, presence, weather, lux/irradiance sensors
- Weather conditions (if climate mode enabled)
- Blind spot (optional): define obstacles that block sun
- Interpolation (optional): custom position mapping for non-standard covers
Best practices for config flow changes:
- Always add
data_descriptionfor new fields intranslations/en.json, then run theacp-translateskill to propagate to DE/FR - Use
NumberSelectorwithunit_of_measurementfor all numeric inputs - Provide practical examples and typical values in descriptions
- Test configuration flow on mobile and desktop interfaces
- Keep descriptions concise but informative (2β4 sentences ideal)
CRITICAL: do not change this behavior without careful consideration.
The inverse_state feature handles covers that don't follow Home Assistant guidelines:
- Calculate position (0β100)
- Apply inverse if enabled:
state = 100 - state - For open/close-only covers: compare inverted state to threshold
- Send command to cover
See CLAUDE.md "Inverse State β DO NOT CHANGE" for full details.
- Create handler in
pipeline/handlers/implementingOverrideHandler - Set priority relative to existing handlers
- Register in coordinator
- Create class extending
AdaptiveGeneralCoverincalculation.py - Implement
calculate_position()andcalculate_percentage() - Add
CoverTypeenum value - Update coordinator to handle new type
- Create provider in
state/returning a frozen dataclass - Inject into coordinator and pass to calculation engine or pipeline context
- 751 tests, 61% coverage
- Calculation engine tests require no HA mocks (zero HA imports in
calculation.pyandsun.py) - Each manager, pipeline handler, state provider, and engine module has dedicated test coverage
- Key test files:
tests/test_calculation.py,tests/test_geometric_accuracy.py,tests/test_motion_control.py,tests/test_force_override_sensors.py,tests/test_control_state_reason.py,tests/test_engine/
custom_components/adaptive_cover_pro/
__init__.py # Integration entry point
coordinator.py # Thin orchestrator (~1,477 lines)
calculation.py # Pure calculation engine (0 HA imports)
sun.py # Pure solar calculations (0 HA imports)
config_flow.py # Configuration UI
config_types.py # CoverConfig typed dataclass
engine/ # Next-gen calculation engine
sun_geometry.py # SunGeometry dataclass
covers/
venetian.py # VenetianCoverCalculation (dual-axis)
managers/ # Focused coordinator responsibilities
manual_override.py # AdaptiveCoverManager
grace_period.py # GracePeriodManager
motion.py # MotionManager
position_verification.py # PositionVerificationManager
cover_command.py # CoverCommandService
state/ # HA boundary layer (all HA reads)
climate_provider.py # ClimateProvider β ClimateReadings
cover_provider.py # CoverProvider β cover entity state
snapshot.py # SunSnapshot, CoverStateSnapshot
sun_provider.py # SunProvider β SunData
pipeline/ # Override priority chain
registry.py # PipelineRegistry
types.py # PipelineContext, PipelineResult, DecisionStep
handler.py # OverrideHandler abstract base
handlers/
force_override.py # Priority 100
wind.py # Priority 95 (stub)
motion_timeout.py # Priority 80
manual_override.py # Priority 70
climate.py # Priority 50
solar.py # Priority 40
cloud_suppression.py # Priority 35 (stub)
default.py # Priority 0
diagnostics/
builder.py # DiagnosticsBuilder, DiagnosticContext
entity_base.py # Base entity classes
sensor.py # Sensor platform
switch.py # Switch platform
binary_sensor.py # Binary sensor platform
button.py # Button platform
helpers.py # Utility functions
const.py # Constants
enums.py # Type-safe enumerations
geometry.py # Geometric utilities
position_utils.py # Position conversion utilities
services/
configuration_service.py # Config entry parsing
- User Documentation: README
- AI Assistant Instructions: CLAUDE.md
- Home Assistant Docs: https://developers.home-assistant.io/
- Python Async Guide: https://docs.python.org/3/library/asyncio.html
- Ruff Documentation: https://docs.astral.sh/ruff/
- Issues: https://github.com/jrhubott/adaptive-cover-pro/issues
- Discussions: https://github.com/jrhubott/adaptive-cover-pro/discussions
- Home Assistant Community: https://community.home-assistant.io/
π Home Β· β¨ Features Β· π° What's New
π Getting Started
- Installation
- Migrating from Custom Repository
- Migrating from Adaptive Cover
- First-Time Setup
- Cover Types
π§ Core Concepts
π Cover Types
βοΈ Configuration
- Sun Tracking
- Position
- Position Matching
- Glare Zones
- Automation
- Custom Position
- Force Override
- Weather Safety
- Climate
- Templated Thresholds
- Blindspot
- Summary Screen
- Debug & Diagnostics
π Entities & Services
- Entities
- Proxy Cover Entity
- Position Verification
- My Position Support (Somfy RTS)
- Runtime Configuration Services
π οΈ Operations
π§ Advanced Use Cases
- Dynamic Temperature Thresholds
- Dynamic Tracking Window
- Bedroom Sleep Mode
- Handling Variable Cloud Cover
- Venetian Tilt-Only on Overcast Days
- Forecast-Based Shading
π¨ Dashboard
π§ͺ Testing & Simulation
π Reference
π©βπ» For Developers