homematicip_local
CLAUDE.md608 starsChanged 5 months agoMITView on GitHub
This document provides comprehensive guidance for AI assistants working with the Homematic(IP) Local for OpenCCU codebase. Project Name: Homematic(IP) Local for OpenCCU Type: Home Assistant Custom Integration Version: 2.12.0 Primary Language: Python 3.14+ Domain: homematicip_local This is a production-quality Home Assistant custom integration that enables local communication with Homematic and HomematicIP smart home devices through various hubs (CCU2/3, OpenCCU, Debmatic, Homegear). It provides bi-directional communication using XML-RPC for device control and push state updates, and JSON-RPC for fetching device names…
- Installs packages
- Commits and pushes
We split this CLAUDE.md into the rules it gives an agent
55 rules. About 0 are common, 43 are distinctive, and 12 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈43
Distinctive · estimate
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 230
MANDATORY: Every Python file MUST start with:
- Distinctive: line 359
IMPORTANT: All code must follow the project's docstring conventions for consistency and maintainability.
- Distinctive: line 364
Programmer-Focused: Write for developers using or maintaining the code
- Distinctive: line 366
Consistency: Follow established patterns for similar constructs
- Very specific: line 370
Punctuation: Always end docstrings with a period (.)
- Very specific: line 529
All entities inherit from AioHomematicGenericEntity:
- Distinctive: line 613
Follow the established entity base class pattern
- Distinctive: line 617
All functions must have full type annotations
- Very specific: line 622
Add tests in /tests/ directory
- Very specific: line 624
Use existing test fixtures from conftest.py
- Very specific: line 627
Update strings.json for UI text
- Very specific: line 628
Update services.yaml for new services
- Distinctive: line 629
Consider updating README.md if user-facing
- Distinctive: line 661
Don't fetch state manually unless absolutely necessary
- Very specific: line 676
Use async_register_admin_service for admin services
- Distinctive: line 677
Use appropriate service domain
- Distinctive: line 680
Add service title and description
- Distinctive: line 681
Add field labels and descriptions
- Very specific: line 684
Add service tests in /tests/
- Distinctive: line 685
Test success and failure cases
- Distinctive: line 711
MANDATORY: When making significant changes, verify and update all relevant documentation.
- Distinctive: line 715
After any major feature, refactoring, or API change, check:
- Distinctive: line 780
Follow conventional commit format:
- Distinctive: line 828
MANDATORY: Every refactoring or feature implementation MUST complete ALL items before merging:
- Distinctive: line 847
Every decision point must have a concrete answer
- Distinctive: line 848
If multiple approaches exist, one MUST be selected and documented
- Distinctive: line 858
All imports required for each file
- Distinctive: line 864
Before/after code blocks for changes
- Distinctive: line 876
Test function names and what they verify
- Distinctive: line 880
When implementing new features or refactoring existing code:
- Distinctive: line 882
No Legacy Code: After implementation, the codebase must be clean without any legacy compatibility layers, deprecated aliases, or …
- Distinctive: line 884
No Backward Compatibility Layers: Do not create:
- Distinctive: line 891
Complete Migration: All usages of changed APIs must be updated in a single change.
- Distinctive: line 893
Documentation Updates: All changes must include:
- Distinctive: line 899
Run these commands before considering any implementation complete:
- Distinctive: line 937
Always increment version number in migration
- Distinctive: line 939
Test migration path from each previous version
- Distinctive: line 940
Document migration in changelog
- Distinctive: line 941
Never skip version numbers
- Distinctive: line 951
Describe before coding: Before writing any code, describe the planned approach and wait for explicit approval.
- Distinctive: line 952
Clarify ambiguities first: If requirements are ambiguous or incomplete, ask clarifying questions before writing any code.
- Distinctive: line 953
Suggest edge cases after coding: After finishing any code change, list edge cases and suggest test cases to cover them.
- Distinctive: line 954
Bug fix = test first: When fixing a bug, start by writing a test that reproduces it, then fix the code until the test passes.
- Distinctive: line 955
Learn from corrections: When corrected, reflect on what went wrong and describe a concrete plan to avoid repeating the mistake.
- Distinctive: line 995
Only system variables are polled (every 30s)
- Distinctive: line 999
All functions need full type annotations
- Very specific: line 1005
Use --no-verify only in emergencies
- Distinctive: line 1088
When performing a refactoring task, follow this workflow:
- Distinctive: line 1090
Plan: Create a detailed implementation plan (see Implementation Plan Requirements)
- Distinctive: line 1093
Clean: Remove all legacy code, aliases, and compatibility shims
- Very specific: line 1094
Test: Run make test - all tests must pass
- Very specific: line 1095
Lint: Run make prek - no errors allowed
- Distinctive: line 1097
Verify: Check for leftover TODOs related to migration
- Distinctive: line 1103
Before finalizing any implementation plan, verify:
- Very specific: line 1125
All development tasks run via the Makefile.
≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md - AI Assistant Guide for Homematic(IP) Local Integration
This document provides comprehensive guidance for AI assistants working with the Homematic(IP) Local for OpenCCU codebase.
Table of Contents
- Project Overview
- Repository Structure
- Key Technologies & Dependencies
- Development Environment Setup
- Code Quality & Standards
- Docstring Standards
- Testing Guidelines
- Architecture & Design Patterns
- Common Development Tasks
- Git Workflow
- Implementation Policy
- Tips for AI Assistants
- Quick Reference
Project Overview
Project Name: Homematic(IP) Local for OpenCCU
Type: Home Assistant Custom Integration
Version: 2.12.0
Primary Language: Python 3.14+
Domain: homematicip_local
This is a production-quality Home Assistant custom integration that enables local communication with Homematic and HomematicIP smart home devices through various hubs (CCU2/3, OpenCCU, Debmatic, Homegear). It provides bi-directional communication using XML-RPC for device control and push state updates, and JSON-RPC for fetching device names and room information.
Repository Structure
homematicip_local/
├── custom_components/homematicip_local/ # Main integration code (~7,752 lines)
│ ├── __init__.py # Integration setup & entry point
│ ├── config_flow.py # Configuration UI & validation (26.8K lines)
│ ├── control_unit.py # Central control logic (33.9K lines)
│ ├── services.py # Service registration (42.1K lines)
│ ├── services.yaml # Service schema definitions
│ ├── generic_entity.py # Base entity class (21.8K lines)
│ ├── entity_helpers.py # Entity creation utilities (44.4K lines)
│ ├── const.py # Constants & enums
│ ├── support.py # Helper functions
│ │
│ ├── Platform Implementations (entity types):
│ │ ├── binary_sensor.py, button.py
│ │ ├── climate.py # Thermostat/climate (18.5K lines)
│ │ ├── cover.py, light.py, lock.py
│ │ ├── number.py, select.py, sensor.py
│ │ ├── siren.py, switch.py, text.py
│ │ ├── update.py, valve.py
│ │
│ ├── Integration Features:
│ │ ├── device_action.py # Device automation actions
│ │ ├── device_trigger.py # Device automation triggers
│ │ ├── event.py # Custom event handling
│ │ ├── mqtt.py # MQTT integration
│ │ ├── logbook.py # Logbook integration
│ │ ├── diagnostics.py # Diagnostic data export
│ │ ├── repairs.py # Issue registry integration
│ │
│ ├── Configuration:
│ │ ├── manifest.json # Integration metadata
│ │ ├── strings.json # Translatable strings (52K+ lines)
│ │ ├── translations/ # Language files (en.json, de.json)
│ │ ├── icons.json # Custom icon mappings
│ │ └── quality_scale.yaml # HACS quality score
│ │
├── tests/ # Test suite (~1,898 lines, pytest-based)
│ ├── conftest.py # Test fixtures & mocks
│ ├── test_config_flow.py # Configuration flow tests (38K+ lines)
│ ├── test_init.py # Integration initialization tests
│ ├── test_*.py # Platform-specific tests
│ │
├── .github/workflows/ # CI/CD pipelines
│ ├── test-run.yaml # Main test pipeline (Python 3.14)
│ ├── e2e-parity.yaml # Three-way backend parity gate (see tests/e2e/README.md)
│ ├── pre-commit.yml # Code quality gate
│ ├── hacs_validate.yaml # HACS validation
│ └── hassfest.yaml # HA manifest validation
│ │
├── blueprints/automation/ # HA automation blueprints
├── script/ # Development helper scripts
│ ├── bootstrap # Initialize dev environment
│ ├── setup # Setup script
│ ├── run-in-env.sh # Run commands in virtualenv
│ ├── sort_class_members.py # Class member ordering
│ └── check_translations.py # Translation validation
│ │
├── Configuration Files:
│ ├── Makefile # Entry point for all dev tasks (`make help`)
│ ├── pyproject.toml # Project config (Python, testing, linting)
│ ├── .pre-commit-config.yaml # Pre-commit hooks
│ ├── .yamllint # YAML linting rules
│ ├── codecov.yml # Code coverage config
│ ├── requirements_test.txt # Testing dependencies
│ ├── Dockerfile.dev # Development container
│ └── .devcontainer/ # VS Code DevContainer setup
│
├── docs/ # Additional documentation
│ ├── naming.md # Device and entity naming conventions
│
├── Documentation:
│ ├── README.md # Comprehensive user documentation (1,295 lines)
│ ├── CLAUDE.md # AI Assistant development guide
│ ├── CONTRIBUTING.md # Contribution guidelines
│ └── changelog.md # Release historyKey Technologies & Dependencies
Runtime Dependencies
- aiohomematic (v2026.10.5) - Core async library for Homematic device communication
- aiohomematic-config (v2026.10.1) - Device configuration metadata
- openccu-data (v2026.9.1) - CCU configuration metadata (translations, easymodes, link profiles); pulled in by aiohomematic and pinned in the manifest, not imported here
- openccu-loom-client (v2026.10.9) - Client for the openccu-loom backend (Beta)
- Home Assistant Core - Minimum version: 2026.9.0+
- Python 3.14+ (target version for development)
Development Dependencies
- pytest-homeassistant-custom-component-framework (1.0.61) - HA test framework
- mypy (
<=2.4.0, a ceiling rather than a pin; 2.1.0 installed) - Static type checker (strict mode) - pylint (4.1.3) - Code linting
- ruff (0.17.1) - Fast Python linter and formatter
- prek (0.5.5) - Git hooks manager (Rust-based pre-commit alternative)
- aiohomematic-test-support (2026.10.5) - Mock test data
- async-upnp-client (0.49.0) - UPnP discovery
- uv - Fast Python package installer (preferred over pip)
Development Environment Setup
Prerequisites
- Python: 3.14 or higher
- Package Manager: pip, uv (recommended)
- Git: For version control
Initial Setup
Using DevContainer (Recommended):
# Open in VS Code with DevContainer extension # Container will auto-configure with script/setup and script/bootstrapManual Setup:
# Clone the repository git clone https://github.com/sukramj/homematicip_local.git cd homematicip_local # Create virtual environment (./venv, Python 3.14) make venv # Install dependencies and prek hooks make setup
The Makefile
The Makefile is the entry point for every development task; make help lists all
targets grouped by topic (setup, code quality, tests, validation, run, housekeeping).
Targets run through script/run-in-env.sh, which activates ./venv (or .venv) if
present, so they work with or without an activated virtual environment. The command
examples throughout this document use make; the underlying commands still work if
run directly.
Code Quality & Standards
Type Checking (mypy - STRICT MODE)
CRITICAL: This project uses mypy in strict mode. All code MUST be fully typed.
# pyproject.toml settings:
strict = true
check_untyped_defs = true
disallow_incomplete_defs = true
warn_unused_ignores = trueType Annotation Requirements
# ✅ CORRECT - All parameters and return types annotated
def get_device_by_address(self, address: str) -> Device | None:
"""Return device by address."""
return self._devices.get(address)
# ❌ INCORRECT - Missing type annotations
def get_device_by_address(self, address):
return self._devices.get(address)
# ✅ CORRECT - Complex types properly annotated
async def fetch_devices(
self,
*,
include_internal: bool = False,
) -> dict[str, DeviceDescription]:
"""Fetch all device descriptions."""
...
# ✅ CORRECT - Using TYPE_CHECKING for imports
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from aiohomematic.central import CentralUnitImport Requirements
MANDATORY: Every Python file MUST start with:
from __future__ import annotationsThis is enforced by ruff's required-imports setting.
Import Sorting (isort via ruff)
# Correct import order:
from __future__ import annotations
# 1. Standard library
import asyncio
from collections.abc import Callable
from typing import TYPE_CHECKING
# 2. Third-party
from homeassistant.core import HomeAssistant
# 3. First-party (aiohomematic)
from aiohomematic.const import Interface
# 4. Local imports
from .const import DOMAIN
# 5. TYPE_CHECKING imports (to avoid circular imports)
if TYPE_CHECKING:
from aiohomematic.central import CentralUnitCommon Import Aliases
import probatio as vol
from aiohomematic.central import CentralUnit as hmcu
from aiohomematic.client import Client as hmcl
from aiohomematic.model.custom import definition as hmed
from aiohomematic.support import support as hmsCode Style Conventions
# Use keyword-only arguments for ALL parameters (excluding self/cls)
def create_entity(
*, # Force keyword-only
control_unit: ControlUnit,
data_point: DataPoint,
) -> Entity:
"""Create a new entity."""
...
# Even single parameter must be keyword-only
def get_device(self, *, address: str) -> Device | None:
"""Return device by address."""
...
# Docstrings required for all public classes and methods
class HmipDevice:
"""Representation of a Homematic device."""
def get_channel(self, channel_no: int) -> Channel | None:
"""Return channel by number."""
...
# Use descriptive variable names (avoid single letters except in comprehensions)
# ✅ CORRECT
for device_address in device_addresses:
...
# ⚠️ ACCEPTABLE only in simple comprehensions
devices = [d for d in all_devices if d.is_ready]Linting (ruff + pylint)
Ruff Configuration
- Target: Python 3.14
- Line Length: 120 characters
- Enabled Rules: B, C, D, E, F, G, I, LOG, PT, SIM, and more
- Auto-fix enabled in prek
# Auto-format and auto-fix all code (ruff format + ruff check --fix)
make format
# Lint code without auto-fix
make lint
# Check formatting without changes
make format-checkPrek Hooks (Run on Every Commit)
The following hooks run automatically before each commit:
- sort-class-members - Enforces class member ordering
- ruff - Linting and auto-formatting
- codespell - Spell checking
- bandit - Security linting
- yamllint - YAML validation
- prettier - Code formatting
- mypy - Type checking (strict mode)
- pylint - Code linting
- check-translations - String/translation validation
- check-flow-translations - Config flow & repair translation validation
To run all hooks manually:
make prekBypass hooks (NOT recommended):
git commit --no-verify -m "message"Docstring Standards
IMPORTANT: All code must follow the project's docstring conventions for consistency and maintainability.
Key Principles
- Short and Precise: Docstrings should be concise yet informative
- Programmer-Focused: Write for developers using or maintaining the code
- Type Hints First: Rely on type annotations; avoid repeating type information
- Consistency: Follow established patterns for similar constructs
Quick Rules
Punctuation: Always end docstrings with a period (.)
Verb Usage:
- Functions/Methods: Use imperative mood ("Return the device", not "Returns" or "Gets")
- Classes: Use declarative statements ("Represents a device")
Method Docstrings
# Simple methods (one-line)
def get_device(self, address: str) -> Device | None:
"""Return device by address."""
# Complex methods (with Args/Returns)
def create_device(
self,
*,
interface_id: str,
device_address: str,
) -> Device:
"""
Create and register a new device instance.
Args:
interface_id: Interface identifier.
device_address: Unique device address.
Returns:
Created device instance.
"""Property Docstrings
@property
def device_address(self) -> str:
"""Return the device address."""Common Anti-Patterns to Avoid
❌ Don't repeat type information:
# Bad
def get_address(self) -> str:
"""Return the address as a string."""
# Good
def get_address(self) -> str:
"""Return the device address."""❌ Don't use inconsistent verbs:
# Bad
def get_device(...): """Gets device."""
def fetch_value(...): """Fetches value."""
# Good
def get_device(...): """Return device by address."""
def fetch_value(...): """Return parameter value."""❌ Don't forget periods:
# Bad
"""Clear the cache"""
# Good
"""Clear the cache."""Testing Guidelines
Running Tests
# Run all tests
make test
# Run all tests with coverage
make test-cov
# Run exactly as in CI (asyncio-mode=legacy)
make test-ci
# Run specific test file
make test-file FILE=tests/test_config_flow.py
# Pass extra pytest arguments
make test PYTEST_ARGS="-x -vv"
# Generate HTML coverage report (htmlcov/index.html)
make test-cov-html
# Run the opt-in three-way parity e2e tests (see tests/e2e/README.md)
make test-e2eTest Configuration
- Framework: pytest with pytest-homeassistant-custom-component
- Test location:
/tests/ - Asyncio mode:
auto(default),legacy(CI) - Coverage requirements:
- 100% coverage required for: config_flow.py, device_action.py, device_trigger.py, diagnostics.py, logbook.py
- Default patch threshold: 0.09%
Test Fixtures (conftest.py)
Key fixtures available:
- Mock configuration entries (v1, v2 migrations)
- SSDP discovery fixtures
- Control unit mocks
- SessionPlayer for replay-based testing
- Recorder integration fixtures
Writing Tests
"""Test for config flow."""
from __future__ import annotations
import pytest
from homeassistant.core import HomeAssistant
from custom_components.homematicip_local.const import DOMAIN
@pytest.mark.asyncio
async def test_config_flow_user_init(
hass: HomeAssistant,
) -> None:
"""Test user-initiated config flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "user"},
)
assert result["type"] == "form"
assert result["step_id"] == "central"Architecture & Design Patterns
Entity Base Class Pattern
All entities inherit from AioHomematicGenericEntity:
class SomeEntity(AioHomematicGenericEntity[GenericDataPoint], SomePlatformMixin):
"""Entity description with docstring."""
_attr_has_entity_name = True
_attr_should_poll = False # Push-based updates
def __init__(self, control_unit: ControlUnit, data_point: GenericDataPoint) -> None:
super().__init__(control_unit, data_point)
# Platform-specific initialization
@property
def current_state(self) -> StateType:
"""Return current value."""
return self._data_point.valueControl Unit Architecture
- BaseControlUnit - Base class
- ControlUnit - Main control instance managing one CCU/hub
- ControlConfig - Configuration wrapper
- Central handles XML-RPC callbacks, device state management
Configuration Flow Pattern
The integration uses a modern, user-friendly multi-step configuration flow:
Flow Steps:
- Central (Step 1/2): CCU connection settings (host, credentials, TLS)
- Interface (Step 2/2): Device interface selection (HmIP-RF, BidCos-RF, etc.)
- Menu (finish_or_advanced): Choose between completing setup or configuring advanced options
- Advanced (optional): System variables, MQTT, device behavior settings
- Reconfigure (entry point): Quick updates to CCU connection settings without full re-setup
Key Features:
- ✅ Early Validation: Connection validated immediately after credential entry
- ✅ Enhanced Error Messages: All errors include
error_detailandretry_hintplaceholders - ✅ Progress Indicators: Each step shows "Step X of Y"
- ✅ Menu-Based Navigation: Clear choice between finishing setup or accessing advanced options
- ✅ Reconfigure Flow: Update connection settings without deleting and re-adding integration
- ✅ Relevance-Gated Loom Backend: There is no master switch for the openccu-loom backend.
_loom_is_relevant()decides whetherasync_step_usershows the backend menu: it returnsTruewhen a loom entry is already configured or a daemon discovery flow is in progress (async_progress_by_handler(DOMAIN, match_context={"source": SOURCE_ZEROCONF})). Setups without a daemon go straight tocentraland never see a backend choice.async_step_zeroconfis ungated — an announcing daemon always produces a discovery card. Do not reintroduce a compile-time flag, and do not useshow_advanced_optionsas a gate (deprecated in HA, returnsTrueunconditionally, removal in 2027.6) - ✅ Loom Beta Marking: The loom backend is marked Beta at every entry point —
TITLE_BACKEND_LOOM = "openccu-loom Beta"(discovery card, reauth, reconfigure) plus(Beta)titles and a leading Beta note onuser.menu_options.loom,step.loom,step.loom_credentialsandstep.loom_tokeninstrings.json/en.json/de.json. Steps that act on an existing entry stay unmarked —options.step.loom_connection,step.reauth_loom,step.reauth_loom_boxandstep.reconfigure_loom; their dialogs carry Beta through the{backend}flow title. Remove the marking only when the client consumes the full daemon wire surface — seedocs/architecture-comparison-aiohomematic-vs-loom.md§10 - ✅ Backend-Aware Flow Titles:
flow_titlecarries a{backend}placeholder (aiohomematic/openccu-loom Beta), so discovery cards (SSDP vs. mDNS), reauth and reconfigure dialogs show which backend a flow targets - ✅ Backend-Neutral Device Identifiers: The HA device identifier is composed by the integration —
ControlUnit.device_identifier()/support.get_device_identifier()build<address>@<instance_name>-<interface>from the interface type, never from a backend's owninterface_id. The two backends disagree on that id's leading component (aiohomematic uses the HA instance name, the openccu-loom daemon its own CCU name), so keying devices on it re-created every device entry on a backend switch and lost thedevice_idwith its area and automations. For the direct-CCU backend the composed value is byte-identical toDevice.identifier._async_migrate_device_identifiersmoves legacy entries over before the platforms are forwarded (plain rename; merge into the older entry when both exist). Never key a device onhm_device.identifieragain, and never parse an interface id back out of an identifier — device actions/triggers/services resolve the device by address and readinterface_idoff the device - ✅ In-Place Backend Switch: Setting up a CCU whose serial is already configured on the other backend switches the existing entry in place (abort reason
backend_switched) instead of aborting withalready_configured. The entry keeps its entry_id, instance name and advanced config (incl.sub_devices_enabled); the previous backend's connection keys stay in the entry so a switch back is lossless - ✅ Loom Sub-Device Toggle: The openccu-loom setup flow (manual form and discovered-daemon token step) offers
sub_devices_enableddirectly, defaulting to enabled (DEFAULT_LOOM_ENABLE_SUB_DEVICES) - ✅ Serial-Keyed Manual Loom Setup: The manual loom form validates the daemon, lists its CCUs and routes into the shared CCU-selection step, so manual entries get the same serial-based dedup and in-place backend switch as discovered daemons (the user-chosen instance name is kept)
- ✅ Single-Reload Entry Updates:
_async_update_reload_and_abort_entry/_async_update_entry_and_reload(used by reauth, both backend-switch paths and the mDNS host follow-up) schedule a reload only when the entry's update listener will not already reload it — never useConfigFlow.async_update_reload_and_aborthere (double reload, deprecated with HA 2026.12) - ✅ Loom Discovery Dedup:
async_step_zeroconfsuppresses the daemon card once it is configured — per CCU serial when the daemon announces them (TXT keyccus, incl. automatic host/port follow-up on IP change; a serial on the CCU backend keeps the card as the backend-switch path), with a host/port fallback for pre-ccusdaemons;_async_create_loom_entryclears lingering passive cards of the daemon it just handled
Service Registration Pattern
async_register_admin_service(
hass,
DOMAIN,
service_name,
async_service_handler,
schema=SERVICE_SCHEMA,
supports_response=SupportsResponse.OPTIONAL
)Event System
Events fired by the integration:
homematic.keypress- Button press eventshomematic.device_availability- Device online/offlinehomematic.device_error- Error state notifications
Common Development Tasks
When Adding New Features
- Check existing patterns first:
- Review similar entity platforms (binary_sensor.py, sensor.py, etc.)
- Follow the established entity base class pattern
- Maintain consistency with existing code structure
- Type hints are mandatory:
- All functions must have full type annotations
- Use
from __future__ import annotations - Pass mypy strict mode checks
- Write tests:
- Add tests in
/tests/directory - Aim for 100% coverage for critical files
- Use existing test fixtures from conftest.py
- Add tests in
- Update documentation:
- Update strings.json for UI text
- Update services.yaml for new services
- Consider updating README.md if user-facing
Run quality checks before committing:
# Run prek hooks and tests with coverage make check
When Modifying Entity Platforms
Inherit from the correct base class:
class MyEntity(AioHomematicGenericEntity[GenericDataPoint], SensorEntity):Set required attributes:
_attr_has_entity_name = True _attr_should_poll = False- Implement required properties:
- Platform-specific properties (e.g.,
statefor sensors,is_onfor binary sensors) - Use
@propertydecorators - Return proper types
- Platform-specific properties (e.g.,
- Handle state updates:
- Integration uses push-based updates (no polling)
- State updates come via XML-RPC callbacks
- Don't fetch state manually unless absolutely necessary
When Adding New Services
- Define service in services.yaml:
- Add schema definition
- Include description and field descriptions
- Specify response type if applicable
- Implement service handler in services.py:
- Follow existing pattern
- Use proper validation
- Handle errors gracefully
- Register service:
- Use
async_register_admin_servicefor admin services - Use appropriate service domain
- Use
- Add to strings.json:
- Add service title and description
- Add field labels and descriptions
- Write tests:
- Add service tests in
/tests/ - Test success and failure cases
- Add service tests in
Adding a Translation
IMPORTANT: strings.json is the primary source for all translation keys.
Add to translation files (in this order):
# Step 1: Add key to strings.json (PRIMARY SOURCE) # Step 2: Add to translations/en.json # Step 3: Add German translation to translations/de.jsonValidate translations:
# Validate strings.json / en.json / de.json (use `make translations-fix` to sort and sync) make translations # Validate config flow & repair translations make flow-translations
Updating Documentation
MANDATORY: When making significant changes, verify and update all relevant documentation.
Documentation Update Checklist
After any major feature, refactoring, or API change, check:
□ 1. Module/Class/Method Docstrings - Updated with current behavior
□ 2. CLAUDE.md - Version numbers, dependencies, architecture changes
□ 3. CONTRIBUTING.md - Development process, workflow changes
□ 4. README.md - User-facing features, setup instructions
□ 5. docs/*.md - Relevant technical documentation
□ 6. changelog.md - Version entry with breaking changesKey Documentation Files
| File | Update When |
| Module/Class/Method docstrings | Code behavior changes |
| CLAUDE.md | Architecture, dependencies, dev processes |
| CONTRIBUTING.md | Git workflow, contribution process |
| README.md | User features, setup, troubleshooting |
| docs/naming.md | Entity/device naming logic |
| changelog.md | Every release, every breaking change |
When to Update Documentation
Always update when:
- ✅ Adding new features or services
- ✅ Changing public APIs (events, services, entity attributes)
- ✅ Modifying configuration flow
- ✅ Updating dependencies (aiohomematic, Home Assistant minimum version)
- ✅ Changing architecture or design patterns
- ✅ Breaking backward compatibility
Documentation update workflow:
- Update code docstrings (inline with code changes)
- Update CLAUDE.md (if dev process/architecture changed)
- Update README.md (if user-facing changes)
- Update docs/*.md (if relevant to that topic)
- Update changelog.md (required for all releases)
Verify documentation:
# Check for outdated version numbers
grep -r "2025.12.33" CLAUDE.md README.md docs/
# Check for broken internal references
grep -r "TODO\|FIXME\|XXX" *.md docs/*.md
# Validate markdown formatting
make prettierGit Workflow
Branch Structure
- Default Branch:
main(protected) - Feature Branches:
feature/description - Bug Fix Branches:
fix/description
Commit Messages
Follow conventional commit format:
<type>(<scope>): <subject>
<body>
<footer>Types: feat, fix, docs, style, refactor, test, chore
Examples:
feat(climate): Add support for new thermostat device
Implements custom entity class for HmIP-eTRV-2 with support for
boost mode and window open detection.
Closes #123fix(config_flow): Handle connection timeout gracefully
Added retry logic with exponential backoff for validation calls.Pull Request Process
- Create feature branch from
main - Make changes with tests
- Run the quality gate:
make check(prek hooks + tests with coverage) - Commit changes with descriptive messages
- Push to remote:
git push -u origin feature/branch-name - Create Pull Request to
mainbranch - Wait for CI to pass
- Request review from maintainers
Implementation Policy
This section defines mandatory rules for all implementations in this project.
Refactoring Completion Checklist
MANDATORY: Every refactoring or feature implementation MUST complete ALL items before merging:
□ 1. Clean Code - No legacy compatibility layers, deprecated aliases, or shims
□ 2. Tests - make test passes without errors
□ 3. Linting - make prek passes without errors
□ 4. Documentation - Docstrings, CLAUDE.md, README.md, docs/*.md updated as needed
□ 5. Changelog - changelog.md updated with version entryImplementation Plan Requirements
CRITICAL: Implementation plans created by Opus/Sonnet MUST be executable by Haiku without errors.
A valid implementation plan must:
Resolve ALL Ambiguities Upfront
- No "decide later" or "TBD" items
- Every decision point must have a concrete answer
- If multiple approaches exist, one MUST be selected and documented
Provide Exact File Paths
- Full paths to all files to be created/modified
- Example:
custom_components/homematicip_local/new_platform.py(not just "create a new file")
Include Complete Code Snippets
- Entire function/class implementations, not partial examples
- All imports required for each file
- Exact method signatures with full type annotations
Specify Exact Locations for Edits
- Line numbers or unique context strings for modifications
- Before/after code blocks for changes
- Example: "In
control_unit.py, replace lines 145-150 with..."
Document Dependencies and Order
- Which steps depend on which
- Exact execution order
- Files that must exist before others can be created
- Include Test Requirements
- Specific test cases to add
- Expected test file locations
- Test function names and what they verify
Clean Code Policy
When implementing new features or refactoring existing code:
- No Legacy Code: After implementation, the codebase must be clean without any legacy compatibility layers, deprecated aliases, or backward-compatibility shims introduced during the change.
- No Backward Compatibility Layers: Do not create:
- Type aliases for old names
- Re-exports of renamed symbols
- Deprecation warnings for removed APIs
- Compatibility adapters or wrappers
# TODO: remove after migrationcomments
- Complete Migration: All usages of changed APIs must be updated in a single change. Partial migrations that leave legacy code are not acceptable.
- Documentation Updates: All changes must include:
- Updated docstrings for modified classes/methods
- Updated
*.mddocumentation files
Quality Gate Commands
Run these commands before considering any implementation complete:
# 1. Run all prek hooks (ruff, mypy, pylint, etc.) and tests with coverage
make check
# 2. Verify no TODO/FIXME related to migration
grep -r "TODO.*migration\|FIXME.*migration\|TODO.*remove\|TODO.*deprecated" custom_components/
# 3. Check for unused imports/code
ruff check --select F401,F841 custom_components/Config Entry Migrations
When migrations are required:
Config entry migrations (async_migrate_entry in __init__.py) are only needed when the data structure stored in config entries changes.
Current version: 12 (as of 2025-12)
Migration is required when:
- ✅ Adding new keys to config entry data
- ✅ Removing keys from config entry data
- ✅ Changing data types of existing keys
- ✅ Restructuring nested data dictionaries
Migration is NOT required for:
- ❌ UI/UX changes (new flow steps, different input methods)
- ❌ Changing how data is displayed or collected
- ❌ Adding/removing UI elements (checkboxes, menus)
- ❌ Behavioral changes that don't affect stored data
- ❌ Translation updates
Migration best practices:
- Always increment version number in migration
- Handle all previous versions in migration chain
- Test migration path from each previous version
- Document migration in changelog
- Never skip version numbers
Tips for AI Assistants
Interaction Protocol
These rules govern how the AI assistant communicates and works with the developer:
- Describe before coding: Before writing any code, describe the planned approach and wait for explicit approval.
- Clarify ambiguities first: If requirements are ambiguous or incomplete, ask clarifying questions before writing any code.
- Suggest edge cases after coding: After finishing any code change, list edge cases and suggest test cases to cover them.
- Bug fix = test first: When fixing a bug, start by writing a test that reproduces it, then fix the code until the test passes.
- Learn from corrections: When corrected, reflect on what went wrong and describe a concrete plan to avoid repeating the mistake.
Do's
✅ Always include from __future__ import annotations at the top of Python files
✅ Always provide complete type annotations for all functions and methods
✅ Always run prek hooks before committing
✅ Always write tests for new functionality
✅ Always update documentation when changing public APIs (see "Updating Documentation")
✅ Always check and update module/class/method docstrings after code changes
✅ Always use keyword-only arguments for ALL parameters (excluding self/cls)
✅ Always use descriptive variable names
✅ Always handle exceptions with proper context
✅ Always complete the Refactoring Completion Checklist before finishing
✅ Always update changelog.md with breaking changes
✅ Always create implementation plans that are Haiku-executable
✅ Always verify docs/*.md files are current after major changes
✅ Always describe your approach and wait for approval before writing code
✅ Always ask clarifying questions when requirements are ambiguous
Don'ts
❌ Never commit without type annotations
❌ Never skip prek hooks
❌ Never commit to main directly
❌ Never use Any type without justification
❌ Never use bare except: clauses
❌ Never break backward compatibility without major version bump
❌ Never leave legacy compatibility layers or deprecated aliases
❌ Never create plans with ambiguities or "TBD" items
❌ Never poll for state updates (use push-based updates)
❌ Never create entities that poll (_attr_should_poll = False)
❌ Never start coding without describing the approach first
❌ Never assume ambiguous requirements — ask instead
Common Pitfalls to Avoid
- Don't poll for state updates:
- This integration uses push-based updates
- State comes via XML-RPC callbacks
- Only system variables are polled (every 30s)
- Don't skip type hints:
- Mypy strict mode is enforced
- All functions need full type annotations
- Will fail CI if types are missing
- Don't ignore prek hooks:
- Hooks enforce code quality
- Fix issues before committing
- Use
--no-verifyonly in emergencies
- Don't modify paramsets excessively:
- Warning: "Too much writing to device MASTER paramset could kill device's storage"
- Be cautious with
put_paramsetoperations
- Don't forget translation files:
- Update strings.json
- Sync with translations/en.json and translations/de.json
- Run check-translations hook
- Don't reorder
manifest.jsonrequirements — keepaiohomematicLAST:- The
aiohomematic==...pin MUST remain the final entry in therequirementslist. - HA installs each requirement as its own
uv pip install --upgrade <req>call (no atomic resolve, no--constraint). The sub-packagesaiohomematic-configandopenccu-loom-clientdepend onaiohomematicvia open, upper-bound-less>=ranges, so a sibling install can pull a newer aiohomematic than the manifest pin. Whicheveraiohomematicline runs LAST wins, so keeping the exact pin last lets it override any newer transitively-pulled version. - Moving it earlier reopens the dependency-version bug fixed in aiohomematic#2981 (and
related to the version gate in
__init__.py, see aiohomematic#3275). - The version gate in
async_setup_entryis a minimum-version check: setup is only blocked when the installed aiohomematic is older than the manifest pin; a newer patch is allowed. manifest.jsonandrequirements_test.txtmust declare the same versions —tests/test_dependency_pins.pyfails by name when they drift, so a dependency bump always touches both files. CI runs againstrequirements_test.txt, so without that test a release can ship a pin nothing ever tested.
- The
volis not voluptuous — don't rely on marker-replacement inSchema.extend:- From HA 2026.9 on,
homeassistant/__init__.pycallsinstall_as_voluptuous(), soimport voluptuousresolves to the probatio shim (probatio._vol_shim), not to voluptuous. The two disagree on marker identity: in voluptuousRemove("x") == Required("x")isTrue(measured on 0.13.1 … 0.16.0), in probatio 0.11.3 it isFalseandRemovecarries its own__hash__. - Consequence:
BASE.extend({vol.Remove("x"): …})keeps both markers under probatio, so a key the schema was meant to drop staysRequired.Required→Optionalstill replaces correctly; onlyRemoveis affected. - Build a schema that differs structurally from its base by spelling it out, not by
extending the base and undoing parts of it. See
CLICK_EVENT_SCHEMAinsupport.pyand aiohomematic#3374, where this silently stopped everyhomematic.keypressevent. - Validation failures on the event path are DEBUG-level and non-fatal, so a broken schema produces no error and no trace — cover such paths end to end (payload → schema → bus), never each half on its own.
- From HA 2026.9 on,
- The button blueprints trigger on event entities, the other blueprints on the bus event:
blueprints/automation/*actions-for-*use HA'sevent.receivedentity trigger with the selected devices astarget.device_idandoptions.event_type: [press_short, press_long]. They readtrigger.to_state.attributes.channel_no/event_typeand resolve the device withdevice_id(trigger.entity_id)— there is notrigger.event.dataany more, and no device condition.min_versionis 2026.8.0 (purpose-specific triggers left HA Labs in 2026.7).blueprints/community/*still usehomematic.keypress; both paths are supported and both are covered intests/test_blueprints.py.- A device target is expanded through the device and entity registry, so a test needs a
real device entry plus registered event entities (
register_remote()intests/test_blueprints.py); firing a bus event with an inventeddevice_idno longer reaches anything. HA skips hidden entities and entities with anentity_categoryon that expansion (helpers/target.py), which is why event entities must keep neither. channel_nois derived inevent.pyfromchannel.address, never read off the channel: aiohomematic spells itChannelProtocol.no, the loom client's compat channel.number. The address is the one value both backends carry in<address>:<no>form.
- A repair issue outlives the object that would withdraw it:
- The issue registry survives a reload, a reconfigure and a restart; the per-session
objects that take a repair back do not.
CentralConnectionStatepublishes theconnected=Trueevent only for an interface it holds and is rebuilt empty with the central, and a client's_is_callback_alivestartsTrue, so a healthy fresh client never reports its callback as restored. A repair of that shape is therefore permanent once its session ends — which is why_STALE_ISSUE_TYPESin__init__.pywithdrawsconnectionandcallbackon every setup of the entry. Before adding a type there, check that a fault which is still present raises it again after the start;clientis not in the list because a fresh client's CONNECTED transition withdraws it anyway. - Compose every interface-scoped repair id with
support.get_issue_id()and sweep withsupport.async_delete_issues(), which matches the prefix that helper produces. Do not rebuild an id from{instance_name}-{interface}: that is the aiohomematic interface id, while the loom daemon names the leading component itself. The trailing part is not always an interface id either — a JSON-RPC session issue carries the url there. - Cover such a path end to end (raise the repair through the handler, then sweep), never each half on its own: the type token in the id is the only thing that links them, and the issue registry does not persist the translation key.
- The issue registry survives a reload, a reconfigure and a restart; the per-session
objects that take a repair back do not.
Refactoring Workflow
When performing a refactoring task, follow this workflow:
- Plan: Create a detailed implementation plan (see Implementation Plan Requirements)
- Clarify: Resolve ALL ambiguities before starting implementation
- Implement: Make all changes following the exact plan
- Clean: Remove all legacy code, aliases, and compatibility shims
- Test: Run
make test- all tests must pass - Lint: Run
make prek- no errors allowed - Document: Update changelog.md
- Verify: Check for leftover TODOs related to migration
Implementation Plan Quality Standard
A plan is only complete when Haiku can execute it without asking questions.
Before finalizing any implementation plan, verify:
- Every file path is absolute and correct
- Every code block is complete (not "..." or "similar to above")
- Every import statement is explicitly listed
- Every type annotation is complete
- Every step has a clear verification method
- No decisions are deferred to implementation time
When in Doubt
- Check existing patterns: Review similar code in the codebase
- Run the tests:
make test - Check the type hints:
make mypywill guide you - Review the changelog:
changelog.mdfor recent changes
Quick Reference
Running Common Commands
All development tasks run via the Makefile. make help lists every target.
# Show all available targets
make help
# Setup development environment (dependencies + prek hooks)
make setup
# (Re-)install dependencies via uv
make install
# Format and auto-fix code
make format
# Lint code
make lint
# Type check (mypy strict)
make mypy
# Run all prek hooks
make prek
# Run tests with coverage
make test-cov
# Run specific test file
make test-file FILE=tests/test_config_flow.py
# Check translations
make translations
# Full quality gate (prek + tests with coverage)
make check
# Validate manifest/repository like CI (requires Docker)
make validate
# Start Home Assistant against the local ./config directory
make hassImportant Files Reference
| File | Purpose |
__init__.py | Integration entry point |
config_flow.py | Configuration UI & validation |
control_unit.py | Central control logic |
services.py | Service definitions & handlers |
generic_entity.py | Base entity class |
entity_helpers.py | Entity creation utilities |
const.py | Constants & enums |
manifest.json | Integration metadata |
strings.json | UI strings (primary source) |
services.yaml | Service schema definitions |
Version Information
- Current Version: 2.12.0
- Minimum HA Version: 2026.9.0+
- Python Target: 3.14+ (CI tests on 3.14)
- aiohomematic Version: 2026.10.5
- openccu-loom-client Version: 2026.10.9. Its wire layer is generated against daemon API
13.9.0(openccu_loom_client.wire.const.DAEMON_API_VERSION), but that number no longer gates the connection:_report_api_versionlogs and never raises — a warning when the majors differ, an info line when only the minors do. Refusing on it was wrong in both directions, because the daemon's major moved 7 → 8 → 9 → 10 → 11 inside one release window without touching surface this client calls, and because HACS updates the integration before the operator updates the daemon, sominor(daemon) < minor(types)is the ordinary state of an additive daemon release. The hard compatibility gate is the capability handshake (_assert_capabilities), which raisesLoomIncompatibleVersionErrorwhen the daemon does not advertise a feature the caller declared as required; the schema digest is likewise only logged. Do not reintroduce a version-number gate here.openccu-loom-typesis not a separate dependency, it is folded into the client
External Resources
- Main Documentation: https://github.com/sukramj/homematicip_local
- Contributing Guide: See CONTRIBUTING.md
- Issues: https://github.com/sukramj/aiohomematic/issues
- Discussions: https://github.com/sukramj/aiohomematic/discussions
- Changelog: changelog.md
- HACS: Integration available via HACS
Last Updated: 2026-09-12 Version: 2.12.0
In this repository3 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- add-deviceSkill
- add-translationSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

