ADR 0022: Unified Schedule Access via WeekProfileDataPoint¶
Status¶
Accepted (2026-02-05)
Context¶
Schedule operations were split across two access paths: non-climate devices used device.week_profile_data_point, climate devices used 12 methods on BaseCustomDpClimate. This required device-type-specific code in HA. The 12 CDP methods were pure delegation wrappers containing no climate-specific logic. Additionally, ClimateWeekProfile.copy_schedule() accepted BaseCustomDpClimate as parameter, coupling the schedule layer to the CDP layer.
Decision¶
device.week_profile_data_point is the single, unified entry point for all schedule operations -- both climate and non-climate.
Architecture¶
Class Hierarchy¶
WeekProfileDataPointProtocol (base protocol)
+-- ClimateWeekProfileDataPointProtocol (climate-specific)
WeekProfileDataPoint(BaseDataPoint) (handles Default)
+-- ClimateWeekProfileDataPoint(WeekProfileDataPoint) (handles Climate)
API Surface¶
WeekProfileDataPoint (all devices):
| Member | Type | Purpose |
|---|---|---|
schedule | property | Cached schedule as ScheduleDict |
schedule_type | property | ScheduleType.CLIMATE or ScheduleType.DEFAULT |
schedule_channel_address | property | Channel address for RPC calls |
max_entries | property | Maximum schedule entries |
min_temp / max_temp | property | Temperature bounds (climate only, else None) |
available_target_channels | property | Target channel mapping (non-climate only) |
value | state property | Number of active entries |
get_schedule() | async method | Fetch schedule from CCU |
set_schedule() | async method | Write schedule to CCU |
reload_schedule() | async method | Force reload from CCU |
fire_schedule_updated() | method | Notify subscribers of changes |
ClimateWeekProfileDataPoint (climate devices, extends above):
| Member | Type | Purpose |
|---|---|---|
available_profiles | property | Available profiles (P1-P6) |
current_schedule_profile | property | Current active schedule profile |
current_profile_schedule | property | Schedule data for current profile |
device_active_profile_index | property | 1-based device profile index |
schedule_profile_nos | property | Number of supported profiles |
get_schedule_profile() | async method | Read single profile |
set_schedule_profile() | async method | Write single profile |
get_schedule_weekday() | async method | Read single weekday |
set_schedule_weekday() | async method | Write single weekday |
copy_schedule() | async method | Copy schedule to target device |
copy_schedule_profile() | async method | Copy profile to target |
Factory: Two-Path Channel Resolution¶
The create_week_profile_data_point factory resolves the schedule channel via two paths:
Path 1: default_schedule_channel (non-climate)
Device has a channel with type WEEK_PROFILE -> use that channel
Path 2: schedule_channel_no (climate)
CDP.device_config.schedule_channel_no -> resolve to Channel object
- BIDCOS_DEVICE_CHANNEL_DUMMY (999) -> device channel (bare address)
- Integer N -> channel N (e.g., "VCU0000001:1")
| Device | schedule_channel_no | Gets Data Point? |
|---|---|---|
| HM-TC-IT-WM-W-EU | DUMMY (999) | Yes -- device channel |
| HM-CC-VG-1 | DUMMY (999) | Yes -- device channel |
| HmIP-BWTH/STH/eTRV/... | 1 | Yes -- channel 1 |
| HM-CC-TC, HM-CC-RT-DN | None | No -- no schedule |
| ALPHA-IP-RBG | None | No -- no schedule |
Copy Method Decoupling¶
Copy operations use two layers to decouple the data point from ClimateWeekProfile internals:
# Public API (data point layer):
ClimateWeekProfileDataPoint.copy_schedule(
*, target_data_point: ClimateWeekProfileDataPointProtocol
)
# Internal (schedule layer, decoupled from CDP):
ClimateWeekProfile.copy_schedule_to(
*, target_week_profile: ClimateWeekProfile
)
The data point extracts target_data_point._week_profile and delegates to ClimateWeekProfile.copy_schedule_to().
Key Implementation Details¶
schedule_profile_nos sourcing: Determined at factory time by inspecting the climate CDP. Passed to the ClimateWeekProfileDataPoint constructor to avoid coupling the data point to the CDP.
Event propagation: reload_and_cache_schedule() calls fire_schedule_updated() on the data point, which publishes a data_point_updated event via the EventBus.
Type narrowing: ClimateWeekProfileDataPoint narrows _week_profile from ClimateWeekProfile | DefaultWeekProfile to ClimateWeekProfile via a class-level annotation, avoiding runtime casts.
Pessimistic cache: Preserved unchanged. Schedule writes go to CCU via put_paramset, cache updates only on CONFIG_PENDING = False.
Consequences¶
Positive¶
- Single access path for all devices -- no device-type branching in HA
- Separation of concerns -- schedule logic on schedule data point, climate CDPs focus on temperature control
- Decoupled copy operations --
ClimateWeekProfileno longer referencesBaseCustomDpClimate - Protocol-typed API -- consumers depend on protocols, not concrete classes
Negative¶
- Breaking change for HA -- mitigated by migration guide with search-and-replace patterns
- Runtime isinstance check required for climate-specific methods (follows existing pattern)
- 20 additional data point instances at startup (minimal memory with
__slots__)
Alternatives Considered¶
Keep Schedule Methods on CDP as Secondary Path¶
Rejected: Doubles API surface and creates ambiguity about the authoritative path.
Expose WeekProfile Directly (No Data Point)¶
Rejected: WeekProfile lacks EventBus integration, BaseDataPoint metadata, and HA entity compatibility.
Single WeekProfileDataPoint for Both Types (No Subclass)¶
Rejected: Climate operations (profile/weekday/copy) are fundamentally different from simple schedule operations. A subclass with dedicated protocol provides type safety.
Deprecation Period Before Removal¶
Rejected: Both projects are maintained together. Atomic migration avoids complexity without benefit.
References¶
- Proposal:
docs/proposals/climate_schedule_sensor_migration.md - Migration Guide:
docs/migrations/week_profile_data_point_migration_2026_02.md aiohomematic/model/week_profile_data_point.pyaiohomematic/interfaces/model.py- ADR 0002: Protocol-Based Dependency Injection
Created: 2026-02-05 Author: Architecture Review