This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
For comprehensive developer documentation, see docs/DEVELOPMENT.md - This file contains instructions for Claude Code specifically, while docs/DEVELOPMENT.md is the human-readable developer guide.
At the start of every new session, read HANDOFF.md before doing anything else. It contains:
- Current version and branch state
- Test suite status and coverage summary
- Open GitHub issues (backlog)
- Key gotchas and architectural reminders
- Release checklist
# Quick orientation at session start
cat HANDOFF.md
git status
git log --oneline -5HANDOFF.md is the project's living status document. Update it whenever any of the following change:
| Event | What to update in HANDOFF.md |
|---|---|
| New release cut | Current Version, Recent Releases table |
| Issue opened or closed | Open Issues table |
| PR merged | Recent Releases, remove from open PRs if listed |
| Test count or coverage changes | Tests section |
| New architectural pattern established | Key Patterns or Known Gotchas section |
| Bug fixed that was a known gotcha | Remove or update that gotcha |
When to update: At the end of any session where code was merged, a release was cut, or an issue was opened/closed. Do not update mid-feature-branch — wait until changes land on main.
How to update: Edit HANDOFF.md directly with the Write or Edit tool. Keep entries concise — it is a quick-reference document, not a changelog.
Adaptive Cover Pro is a Home Assistant custom integration that automatically controls vertical blinds, horizontal awnings, and tilted/venetian blinds based on the sun's position. It calculates optimal positions to filter direct sunlight while maximizing natural light and supporting climate-aware operation.
Language: Python 3.11+
Framework: Home Assistant Core (async architecture)
Current Version: See manifest.json
Requires: Home Assistant 2024.5.0+
This integration follows Home Assistant's Data Coordinator Pattern:
coordinator.py - Central hub for all state management
AdaptiveDataUpdateCoordinatormanages async updates, entity listeners, and position calculations- Tracks state changes from sun position, temperature, weather, presence entities
- Handles manual override detection and control
- Orchestrates position calculations and cover service calls
calculation.py - Position calculation algorithms
AdaptiveVerticalCover- Up/down blind calculations with enhanced geometric accuracycalculate_position()- Main calculation with safety margins and window depth support_calculate_safety_margin()- Angle-dependent safety margins (0-35% at extremes)_handle_edge_cases()- Robust fallbacks for extreme sun angles- Supports optional
window_depthparameter for advanced precision
AdaptiveHorizontalCover- In/out awning calculationsAdaptiveTiltCover- Slat rotation calculationsNormalCoverState- Basic sun position modeClimateCoverState- Climate-aware mode with temperature/presence/weather
config_flow.py - Multi-step UI configuration
- Separate flows for vertical/horizontal/tilt cover types
- Common options, automation settings, climate mode, blind spots
- Option validation and context-aware forms
sensor.py- Position, control method, start/end sun timesswitch.py- Automatic control, climate mode, manual override detectionbinary_sensor.py- Sun visibility, manual override statusbutton.py- Manual override reset
- Initialization: Config flow creates
ConfigEntry→ coordinator setup - Listeners: Coordinator registers listeners on sun, temperature, weather, presence entities
- State Change: Entity change triggers
async_check_entity_state_change() - Calculation:
_async_update_data()calls appropriate cover class to calculate position - Update: Coordinator updates data → platform entities refresh
- Control: If enabled and not manually overridden → calls cover service to move blinds
./scripts/setup # Install dev dependencies and setup pre-commit hooks./scripts/develop # Start Home Assistant in debug mode with this integration loadedThe development server:
- Creates
config/directory if not present - Sets
PYTHONPATHto includecustom_components/ - Starts Home Assistant with debug logging
- Uses
config/configuration.yamlfor test setup with mock entities
./scripts/lint # Run ruff linting with auto-fix
ruff check . --fix # Direct ruff invocation
ruff format . # Format codePre-commit hooks run automatically on commit:
- Ruff linting and formatting
- Prettier for YAML/JSON
- Trailing whitespace cleanup
BEFORE MAKING ANY CODE CHANGES:
- Check current branch:
git branch --show-current - Create feature branch from main (REQUIRED)
# ALWAYS branch from main
git checkout main
git pull origin main
# Create feature branch
git checkout -b <prefix>/<description>Branch Naming Conventions:
| Type | Prefix | Example |
|---|---|---|
| New feature | feature/ |
feature/add-shade-control |
| Bug fix | fix/ |
fix/climate-mode-bug |
| Documentation | docs/ |
docs/update-readme |
| GitHub issue (bug) | fix/issue-NNN- |
fix/issue-123-sensor-unavailable |
| GitHub issue (feature) | feature/issue-NNN- |
feature/issue-67-entity-picture |
Rules:
- ✅ ALWAYS create a feature branch FIRST (before any edits)
- ✅ ALWAYS branch from
main(never from other feature branches) - ✅ ALWAYS create a pull request after pushing the branch
- ✅ Keep commits atomic and focused
- ✅ Test changes on the feature branch
- ❌ NEVER commit directly to
mainbranch - ❌ NEVER skip feature branches "because it's a small change"
- ❌ NEVER merge to main without a pull request
When the user references an issue number (e.g., "fix issue #123"):
- Fetch details:
gh issue view 123 - Create branch:
git checkout -b fix/issue-123-short-description(bugs) orfeature/issue-123-... - Commit with reference — closing keywords:
Fixes #123,Closes #123,Resolves #123; non-closing:Related to #123 - Push and create PR immediately:
git push -u origin fix/issue-123-short-description gh pr create --title "fix: Short description (#123)" --body "Fixes #123" --base main
Issues auto-close when PR is merged if body contains Fixes #123.
CRITICAL: All branches MUST have a PR before merging to main — always, even single-commit changes.
PR Title: Conventional format (fix:, feat:, docs:, chore:, test:) + optional (#123), under 70 chars.
PR Body Template:
## Summary
What changed and why.
## Testing
- ✅ X tests passing
- ✅ Scenario tested
## Related Issues
Fixes #123Common operations:
gh pr view / gh pr status / gh pr list
gh pr merge --squash # or --merge / --rebaseCRITICAL: Context-Aware Merge Behavior
When Exiting Plan Mode:
- ✅ ALWAYS ask the user if they want to:
- Create a pull request (if not already created)
- Merge the PR and create a production release
- Create a beta release (without merging to main)
- Use the AskUserQuestion tool to present these options
- Wait for user confirmation before proceeding with PR creation, merge, or release
When NOT in Plan Mode (ad-hoc changes):
- ✅ Create a pull request after pushing changes (always required)
- ✅ Stay on the feature/fix branch after pushing changes
- ❌ DO NOT ask about merging to main
- ❌ DO NOT merge automatically
- The user will decide when to merge the PR separately
gh CLI Quick Reference:
| Task | Command |
|---|---|
| View issue | gh issue view 123 |
| List open issues | gh issue list |
| List by label | gh issue list --label bug |
| Add comment | gh issue comment 123 --body "Message" |
| Close manually | gh issue close 123 --comment "Fixed in vX.Y.Z" |
| Create PR | gh pr create --title "Fix: Description (#123)" --body "Fixes #123" |
| Link PR to issue | Include "Fixes #123" in PR body |
| View PR | gh pr view 456 |
| List PRs | gh pr list |
| Merge PR | gh pr merge 456 --squash (or --merge/--rebase) |
| Check PR status | gh pr status |
CRITICAL: Git commits must NOT include Claude attribution:
- ❌ NEVER add
Co-Authored-By: Claudelines - ❌ NEVER add
Generated with Claude Code - ✅ Commit messages should only describe the changes made
This applies to ALL commits (regular commits, merge commits, etc.) and release notes.
Release Notes Directory:
- All release notes stored in
release_notes/directory - Filename format:
vX.Y.Z.mdorvX.Y.Z-beta.N.md - Provides historical tracking of all releases
- Committed to git for version control
Feature Branch:
- Create BETA releases:
./scripts/release beta --notes release_notes/v2.7.0-beta.1.md --yes - Beta version format:
v2.7.0-beta.1 - Mark as prerelease for testing
Main Branch:
- Create STABLE releases:
./scripts/release patch --notes release_notes/v2.7.0.md --yes - Only merge to main AFTER successful beta testing
- Stable version format:
v2.7.0
- ❌ NEVER create a release proactively
- ✅ ONLY create releases when user explicitly asks
For comprehensive testing documentation, see docs/UNIT_TESTS.md
For algorithm testing and visualization without Home Assistant, see Manual Testing > Jupyter Notebook Testing section below.
IMPORTANT: Always activate the virtual environment first:
# Activate virtual environment (REQUIRED)
source venv/bin/activate
# Run all tests
python -m pytest tests/ -v
# Run specific test file
python -m pytest tests/test_calculation.py -v
# Run with coverage
python -m pytest tests/ --cov=custom_components/adaptive_cover_pro --cov-report=term
# One-liner (activate + run)
source venv/bin/activate && python -m pytest tests/ -vCurrent test count and per-module coverage are tracked in HANDOFF.md (updated each session). Target: 90%+ coverage for all calculation logic.
CRITICAL: Always add or update tests when making code changes.
Add new tests when:
- Adding new features or functionality
- Adding new classes, methods, or functions
- Implementing new calculation logic
- Adding climate mode features
Update existing tests when:
- Changing calculation algorithms
- Modifying state determination logic
- Changing default behaviors
- Fixing bugs (add regression test)
Testing checklist:
- ✅ Write tests for new code before committing
- ✅ Ensure all tests pass:
source venv/bin/activate && python -m pytest tests/ -v - ✅ Check coverage for modified files
- ✅ Aim for 90%+ coverage for calculation logic
- ✅ Follow existing patterns in test files
The release script automates version management, git tagging, and GitHub release creation.
Usage:
./scripts/release [VERSION_SPEC] [OPTIONS]| Spec | Description | Example Result |
|---|---|---|
patch |
Increment patch version (X.Y.Z+1) | 2.6.8 → 2.6.9 |
minor |
Increment minor version (X.Y+1.0) | 2.6.8 → 2.7.0 |
major |
Increment major version (X+1.0.0) | 2.6.8 → 3.0.0 |
beta |
Auto-increment beta version | 2.6.8 → 2.6.9-beta.1 2.6.8-beta.1 → 2.6.8-beta.2 |
X.Y.Z |
Explicit version number | 2.7.0 (explicit) |
X.Y.Z-beta.N |
Explicit beta version | 2.7.0-beta.5 (explicit) |
| (omitted) | Interactive mode (prompts for choice) | User selects from menu |
How beta works:
- If current version is stable (e.g.,
2.6.8) → creates2.6.9-beta.1(next patch's first beta) - If current version is beta (e.g.,
2.6.9-beta.1) → creates2.6.9-beta.2(increment beta number)
| Option | Short | Description |
|---|---|---|
--dry-run |
Preview operations without executing any changes | |
--yes |
-y |
Skip all confirmation prompts (CI/automation mode) |
--notes FILE |
Read release notes from specified file | |
--auto-notes |
Use auto-generated template notes only | |
--force-branch |
Skip branch validation (use with caution) | |
--help |
-h |
Show help text and exit |
Note: --editor option exists in script but should NOT be used per CLAUDE.md guidelines. Always use --notes parameter instead.
Standard workflow (recommended):
# 1. Create release_notes directory if it doesn't exist
mkdir -p release_notes
# 2. Generate release notes (use version number in filename)
cat > release_notes/v2.6.11.md << 'EOF'
## 🎯 Release Title
### ✨ Features
- Feature 1
### 🐛 Bug Fixes
- Fix 1
### 🧪 Testing
- Tested with Python 3.11 and 3.12
- Home Assistant 2024.5.0+
EOF
# 3. Commit the release notes to git FIRST (release script requires clean working directory)
git add release_notes/v2.6.11.md
git commit -m "docs: Add release notes for v2.6.11"
# 4. Create release (handles version bump, tag, and push automatically)
./scripts/release patch --notes release_notes/v2.6.11.md --yesCRITICAL: The release script requires a clean working directory. You MUST commit the release notes file before running the release script, not after. The release script will automatically:
- Update manifest.json version
- Create version bump commit
- Create annotated git tag
- Push both commits and tag to GitHub
- Create GitHub release with ZIP asset
Quick beta release:
# Auto-increment beta, skip confirmations, use auto-generated notes
./scripts/release beta --yes --auto-notesPreview before executing:
# See what would happen without making changes
./scripts/release patch --dry-runExplicit version:
# Use specific version number (create release notes first)
./scripts/release 2.7.0 --notes release_notes/v2.7.0.md --yesInteractive mode:
# Select version type from menu
./scripts/releaseCRITICAL Rules:
- NEVER include
Co-Authored-By:lines - NEVER include Claude/AI attributions
- ALWAYS use
--notesparameter with versioned filename:release_notes/vX.Y.Z.md - NEVER use
--editorparameter - ALWAYS commit release notes to git after creating release
File Naming Convention:
- Production releases:
release_notes/v2.6.11.md - Beta releases:
release_notes/v2.7.0-beta.1.md - Directory:
release_notes/(tracked in git)
Content Guidelines:
- Use clear, user-friendly language
- Include emoji section headers (🎯, ✨, 🐛, 📚, 🧪)
- Document: Features, Bug Fixes, Documentation, Technical Details, Installation, Testing
- For beta releases: Include testing instructions and warnings
| Branch Type | Release Type | Command | Version Format |
|---|---|---|---|
feature/*, fix/* |
Beta (prerelease) | ./scripts/release beta --notes release_notes/v2.7.0-beta.1.md --yes |
v2.7.0-beta.1 |
main |
Stable (production) | ./scripts/release patch --notes release_notes/v2.7.0.md --yes |
v2.7.0 |
Workflow:
- Feature branch → Create beta release → Test
- Merge to main → Create stable release
The script includes automatic rollback on failure:
- Deletes created tags (local and remote)
- Resets commits if version bump was created
- Prevents partial releases
If a release fails:
- Script automatically rolls back changes
- Check error message for cause
- Fix issue and retry
- Use
--dry-runto preview before retry
Common issues:
| Issue | Cause | Solution |
|---|---|---|
| "Working directory not clean" | Uncommitted changes | Commit or stash changes first |
| "Tag already exists" | Tag created previously | Delete tag or use different version |
| "GitHub CLI not authenticated" | Not logged into gh | Run gh auth login |
| "Production releases must be from main" | Wrong branch | Switch to main or use --force-branch (not recommended) |
| "ZIP asset not found" | Workflow failed | Check GitHub Actions logs |
Check workflow status:
gh run list --workflow=publish-release.ymlView release:
gh release view <version-tag>Always update docs alongside code changes:
| Changed | Update |
|---|---|
| User-visible feature | README.md (Features, Entities, Variables sections) |
| Development process | docs/DEVELOPMENT.md |
| VS Code testing | docs/VSCODE_TESTING_GUIDE.md |
| Feature in "Features Planned" | Mark as ~~Completed~~ in README, mention in release notes |
- Async-first - All I/O is async (state tracking, cover commands)
- Never block the event loop - Use async/await
- Coordinator pattern - Use for entity updates
- Entity naming -
{domain}.{type}_{description}_{name} - Store data - In
coordinator.datafor entity access - Update handler - Use
_handle_coordinator_update()in entities
- Add constant to
const.py(CONF_*) - Add to
config_flow.py(appropriate step schema + validation) - Read in
coordinator.pyfromself.config_entry.options - Expose via sensor/switch if needed
- Add translations to
translations/en.json(and other languages if feasible) - Update
CLAUDE.mdConfiguration Structure section
When creating diagnostic sensors:
class MyDiagnosticSensor(AdaptiveCoverDiagnosticSensor):
"""Diagnostic sensor description."""
# For text/status sensors: empty unit excludes from logbook
_attr_native_unit_of_measurement = "" # Prevents activity log entries
# For numeric sensors: MUST have proper unit for statistics
_attr_native_unit_of_measurement = "retries" # Enables statistics tracking
# For P0 sensors (basic diagnostics), enable by default
_attr_entity_registry_enabled_default = TrueRules:
- ✅ Text/status sensors → empty unit
""to exclude from logbook - ✅ Numeric sensors → proper unit (
"retries","°",PERCENTAGE) for statistics - ✅ History is still recorded for debugging
- ❌ Don't use empty unit for numeric sensors (breaks statistics)
Added in v2.7.5 - Occupancy-based automatic control with debouncing.
Motion control enables/disables automatic sun positioning based on room occupancy using binary motion sensors.
Key Design:
- OR Logic - ANY sensor detecting motion enables automatic positioning
- Debounce "no motion" only - Immediate response when motion detected, timeout when motion stops
- Priority: Force override (safety) > Motion timeout > Manual override
- Asyncio task-based -
_start_motion_timeout()/_cancel_motion_timeout()/_motion_timeout_handler()incoordinator.py
Use Cases:
- Glare control when present - Use sun positioning when someone is in the room
- Energy savings when away - Return to default (closed) when room is empty
- Privacy when unoccupied - Close covers automatically after no motion
- Multi-room coverage - OR logic means ANY room with motion uses automatic
Testing:
- See
tests/test_motion_control.pyfor 22 comprehensive test cases - Tests cover OR logic, debouncing, priority, edge cases, shutdown cleanup
Edge Cases Handled:
- Unavailable sensors treated as "off" (no motion)
- Empty sensor list disables feature (backward compatible)
- Double-check prevents false timeout if motion detected during sleep
- Cleanup cancels task on shutdown or config change
CRITICAL: Do Not Change This Behavior
The inverse_state feature handles covers that don't follow Home Assistant guidelines (0=closed, 100=open):
For Position-Capable Covers:
- Calculated position is inverted:
state = 100 - state - Inverted position is sent to the cover entity
For Open/Close-Only Covers:
- Calculated position is inverted:
state = 100 - state - Inverted position is compared to threshold
Code Flow:
- Calculate position
- Invert (if enabled and interpolation not used)
- Apply threshold (for open/close-only covers)
NEVER:
- Change the order of inverse_state application and threshold checking
- Skip inverse_state for open/close-only covers when enabled
- Apply inverse_state after the threshold check
Added in v2.7.0 to fix Issue #1 (shadow calculation accuracy at extreme angles).
The integration includes sophisticated geometric enhancements to ensure accurate sun blocking across all sun positions. These improvements are implemented in AdaptiveVerticalCover.calculate_position() in calculation.py.
-
Edge Case Handling (
_handle_edge_cases())- Elevation < 2°: Returns full window coverage (h_win)
- |Gamma| > 85°: Returns full window coverage (h_win)
- Elevation > 88°: Uses simplified calculation (distance × tan(elevation))
- Returns tuple:
(is_edge_case: bool, position: float)
-
Safety Margins (
_calculate_safety_margin())- Returns multiplier ≥1.0 to increase blind extension
- Gamma margin: Smoothstep interpolation from 1.0 (at 45°) to 1.2 (at 90°)
- Low elevation margin: Linear from 1.0 (at 10°) to 1.15 (at 0°)
- High elevation margin: Linear from 1.0 (at 75°) to 1.1 (at 90°)
- Margins combine multiplicatively (margin = gamma_margin × elev_margin)
-
Window Depth Parameter (optional) —
window_depthfield onAdaptiveVerticalCover(default 0.0), configured viaCONF_WINDOW_DEPTH(0.0–0.5m). Whenwindow_depth > 0and|gamma| > 10°, adds horizontal offsetdepth_contribution = window_depth × sin(|gamma|)to account for window reveals/frames. -
Sill Height Parameter (optional) —
sill_heightfield onAdaptiveVerticalCover(default 0.0), configured viaCONF_SILL_HEIGHT(0.0–3.0m). For windows not starting at floor level, the sill provides natural sun blocking: sunlight entering at the sill height cannot reach closer thansill_height / tan(elevation)meters from the wall. This reduces theeffective_distanceneeded, raising the blind. Not applicable to horizontal awnings or tilt covers. -
Calculation Flow — edge cases → window depth offset → sill height offset → base calculation → safety margin → clip to h_win. See
calculate_position()incalculation.py.
When modifying geometric accuracy calculations:
-
Run existing tests to ensure no regression:
source venv/bin/activate python -m pytest tests/test_geometric_accuracy.py -v -
Test coverage requirements:
- Safety margin behavior at all angle ranges
- Edge case handling at thresholds
- Smooth transitions across ranges
- Regression tests (<5% deviation at normal angles)
- Backward compatibility (window_depth=0 produces identical results)
-
Key test files:
tests/test_geometric_accuracy.py- 34 dedicated teststests/test_calculation.py- Integration with existing calculation tests
DO:
- Test all changes with full test suite (214 tests must pass)
- Maintain backward compatibility (existing installations unaffected)
- Keep safety margins conservative (always ≥ baseline position)
- Use smoothstep interpolation for smooth transitions
- Document changes in commit messages and release notes
DON'T:
- Remove or reduce safety margins without thorough testing
- Change edge case thresholds without testing transitions
- Break backward compatibility (window_depth and sill_height must default to 0.0)
- Introduce numerical instability (NaN, infinity)
- Skip regression testing at normal angles
Users can monitor geometric accuracy via diagnostic sensors:
- "Calculated Position" - Raw position before adjustments
- "Sun Gamma" - Horizontal angle from window normal
- "Sun Elevation" - Vertical angle above horizon
- Compare calculated vs actual position to see safety margin effects
config_entry.data (setup phase):
name- Instance namesensor_type- cover_blind/cover_awning/cover_tilt
config_entry.options (configurable):
- Window azimuth, field of view, elevation limits
- Cover-specific dimensions (height, length, slat properties)
- Enhanced geometric accuracy:
window_depth- Optional window reveal/frame depth (0.0-0.5m, default 0.0)sill_height- Optional height from floor to window bottom (0.0-3.0m, default 0.0). Raises the blind for windows above floor level — accounting for the sill's geometric effect of reducing sun penetration into the room- Safety margins and edge case handling apply automatically (not configurable)
- Position limits:
min_position/max_position- Absolute position boundaries (0-99%, 1-100%)enable_min_position/enable_max_position- When limits apply:- False (default): Limits always enforced
- True: Limits only during direct sun tracking
- Automation settings (delta position/time, start/end times, manual override)
- Force override settings:
force_override_sensors- Optional list of binary sensor entity IDs that globally disable automatic control when any sensor is "on"force_override_position- Position (0-100%) to move covers to when force override is active (default: 0%)
- Motion control settings:
motion_sensors- Optional list of binary sensor entity IDs for occupancy-based control. When ANY sensor detects motion, covers use automatic positioning. When ALL sensors show no motion for timeout duration, covers return to default position. Empty list = feature disabled (default: [])motion_timeout- Duration in seconds to wait after last motion before using default position. Debounces rapid sensor toggling (range: 30-3600, default: 300)
- Climate settings (temperature entities/thresholds, presence, weather)
- Light settings (lux/irradiance entities and thresholds)
- Blind spot areas
Use ./scripts/develop to start a development instance with the integration loaded:
./scripts/developWhat it does:
- Creates
config/directory if not present - Sets
PYTHONPATHto includecustom_components/ - Starts Home Assistant with debug logging
- Loads the integration automatically
- Uses
config/configuration.yamlfor test setup with mock entities
Access:
- Home Assistant UI: http://localhost:8123
- Logs: Real-time debug output in terminal
- Changes: Python file changes require restart to take effect
notebooks/test_env.ipynb — test calculation algorithms without a full HA instance. Produces visual plots of vertical and horizontal cover positions over 24 hours.
Setup: ./scripts/setup installs Jupyter. Open with jupyter notebook or in VS Code (code notebooks/test_env.ipynb, requires Jupyter extension).
Update notebook when: dataclass signatures change in calculation.py or SunData API changes in sun.py. Verify by running all cells and confirming two plots appear with no errors.
Troubleshooting:
ModuleNotFoundError: adaptive_cover_pro→ ensuresys.path.append("../custom_components")is in first cellTypeError: missing argument 'logger'→ addMockedLoggerno-op class, passlogger=mocked_loggerto constructors- Plot not appearing (Jupyter) → add
%matplotlib inlineto first cell SunDataunexpected kwarg → use positional args:SunData(timezone, mocked_hass)
custom_components/adaptive_cover_pro/simulation/sim_plot.png — example plot. Regenerate by uncommenting cells 5-14 in test_env.ipynb.
Algorithm changes: edit calculation.py → validate visually in notebook → add unit tests → test live with ./scripts/develop.
adaptive-cover/
├── custom_components/adaptive_cover_pro/
│ ├── __init__.py # Integration entry point
│ ├── coordinator.py # Data coordinator (primary hub)
│ ├── calculation.py # Position calculation engine
│ ├── config_flow.py # Configuration UI
│ ├── sensor.py # Sensor platform
│ ├── switch.py # Switch platform
│ ├── binary_sensor.py # Binary sensor platform
│ ├── button.py # Button platform
│ ├── sun.py # Solar calculations
│ ├── helpers.py # Utility functions
│ ├── const.py # Constants
│ ├── manifest.json # Integration metadata
│ └── translations/ # i18n files (13 languages)
├── scripts/
│ ├── setup # Development environment setup
│ ├── develop # Start Home Assistant dev server
│ ├── lint # Run linting
│ └── release # Create releases (automated)
├── tests/ # Unit tests (303 tests)
│ ├── conftest.py # Shared fixtures
│ ├── test_calculation.py # Core calculation tests
│ ├── test_geometric_accuracy.py
│ ├── test_helpers.py
│ ├── test_inverse_state.py
│ ├── test_motion_control.py
│ ├── test_startup_grace_period.py
│ ├── test_force_override_sensors.py
│ ├── test_control_state_reason.py
│ ├── test_interpolation.py
│ ├── test_delta_position.py
│ └── test_manual_override.py
├── release_notes/ # Historical release notes
│ ├── README.md # Release notes documentation
│ └── vX.Y.Z.md # Individual release notes (versioned)
├── docs/ # Documentation directory
│ ├── ARCHITECTURE.md # Architecture documentation
│ ├── CONTRIBUTING.md # Contributing guidelines
│ ├── DEVELOPMENT.md # Developer documentation
│ ├── UNIT_TESTS.md # Unit test documentation
│ └── VSCODE_TESTING_GUIDE.md # VS Code testing guide
├── CLAUDE.md # Claude Code instructions (this file)
├── README.md # User documentation
└── pyproject.toml # Python project configuration
Production:
homeassistant~=2024.5- Core frameworkpandas~=2.2- Solar data calculationsastral- Sun position/timing
Development:
ruff~=0.4- Linting and formattingpre-commit~=3.7- Git hookspytest- Testing frameworkpvlib~=0.11- Photovoltaic simulationsmatplotlib~=3.9- Plotting for simulations
- Vertical (
cover_blind) - Up/down movement - Horizontal (
cover_awning) - In/out movement - Tilt (
cover_tilt) - Slat rotation
- Basic Mode - Sun position-based calculation only
- Climate Mode - Enhanced with temperature, presence, weather
- Winter strategy: Open fully when cold and sunny
- Summer strategy: Close fully when hot
- Intermediate: Use calculated position with weather awareness
The integration tracks when users manually change cover positions:
- Compares actual position to calculated position
- Configurable threshold and duration
- Option to reset timer on subsequent changes
- Reset button available to clear override status
Default sunny weather states: sunny, windy, partlycloudy, cloudy
- Configurable in weather options
- Used to determine if calculated position should be used vs. default
Configured in pyproject.toml:
select = ["ALL"]- Enable all rules by default- Specific ignores for formatter conflicts
- Home Assistant import conventions (cv, dr, er, ir, vol)
- Force sorting within sections for imports