diff --git a/BREAKING_CHANGES_V3.md b/BREAKING_CHANGES_V3.md new file mode 100644 index 0000000..dc80230 --- /dev/null +++ b/BREAKING_CHANGES_V3.md @@ -0,0 +1,122 @@ +# Breaking Changes for v3.0.0 + +This document outlines the breaking changes planned for nwp500-python v3.0.0. + +## Overview + +Version 3.0.0 will be the first major version to include breaking changes since the library's initial release. The primary focus is removing deprecated functionality and improving type safety. + +## Scheduled Removals + +### OperationMode Enum Removal + +**Target**: v3.0.0 +**Current Status**: Deprecated in v2.0.0 +**Migration Period**: v2.x series (minimum 6 months) + +The original `OperationMode` enum will be completely removed and replaced with: +- `DhwOperationSetting` for user-configured mode preferences +- `CurrentOperationMode` for real-time operational states + +#### Impact Assessment +- **High Impact**: Code using direct enum comparisons +- **Medium Impact**: Type hints and function signatures +- **Low Impact**: Display-only usage (`.name` attribute) + +#### Required Actions +1. Replace all `OperationMode` imports with specific enum imports +2. Update enum comparisons to use appropriate enum type +3. Update type hints in function signatures +4. Remove any `OperationMode` references from custom code + +#### Migration Tools Available +- `MIGRATION.md` - Comprehensive migration guide +- `migrate_operation_mode_usage()` - Programmatic guidance +- `enable_deprecation_warnings()` - Runtime warning system +- Updated documentation with new enum usage patterns + +## Pre-3.0 Checklist + +Before releasing v3.0.0, ensure: + +### Code Removal +- [ ] Remove `OperationMode` enum class from `models.py` +- [ ] Remove `OperationMode` from package exports (`__init__.py`) +- [ ] Remove deprecation warning infrastructure +- [ ] Update all internal references to use new enums + +### Documentation Updates +- [ ] Update all documentation to remove `OperationMode` references +- [ ] Update `MIGRATION.md` to reflect completed migration +- [ ] Update API documentation +- [ ] Update example scripts if any still reference old enum + +### Testing +- [ ] Ensure all tests pass without `OperationMode` +- [ ] Add tests to verify enum removal +- [ ] Test that import errors are clear and helpful +- [ ] Validate all examples work with new enums only + +### Communication +- [ ] Update CHANGELOG.rst with breaking changes +- [ ] Release notes highlighting breaking changes +- [ ] GitHub release with migration guidance +- [ ] Consider blog post or announcement for major users + +## Timeline + +``` +v2.0.0 (Current) +├── OperationMode deprecated +├── New enums introduced +├── Migration tools provided +└── Backward compatibility maintained + +v2.1.0 (Optional) +├── Optional deprecation warnings +├── Enhanced migration tools +└── Continued compatibility + +v2.x.x (6+ months) +├── Migration period +├── User feedback collection +└── Migration assistance + +v3.0.0 (Target) +├── OperationMode removed +├── Breaking changes implemented +└── Type safety improvements +``` + +## Rollback Plan + +If breaking changes cause significant issues: + +1. **Emergency Patch**: Revert breaking changes in v3.0.1 +2. **Deprecation Extension**: Extend deprecation period to v4.0.0 +3. **Enhanced Migration**: Provide additional migration tools +4. **Communication**: Clear messaging about timeline changes + +## Version Support + +- **v2.x**: Long-term support with security fixes +- **v3.x**: Current development branch +- **v1.x**: End-of-life (security fixes only if critical) + +## Migration Support + +Migration support will be available: +- **During v2.x**: Full backward compatibility + new enums +- **During v3.0 beta**: Migration warnings and testing +- **After v3.0**: Documentation and examples only + +## Contact + +For questions about breaking changes or migration assistance: +- File GitHub issues with "migration" label +- Reference `MIGRATION.md` for detailed guidance +- Use `migrate_operation_mode_usage()` for programmatic help + +--- + +*This document will be updated as v3.0.0 approaches with more specific details and timelines.* \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e4931f..30120ec 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,65 @@ Changelog ========= +Version 2.0.0 (Unreleased) +========================== + +**Breaking Changes (Planned for v3.0.0)** + +- **DEPRECATION**: ``OperationMode`` enum is deprecated and will be removed in v3.0.0 + + - Use ``DhwOperationSetting`` for user-configured mode preferences (values 1-6) + - Use ``CurrentOperationMode`` for real-time operational states (values 0, 32, 64, 96) + - See ``MIGRATION.md`` for detailed migration guide + +Added +----- + +- **Enhanced Type Safety**: Split ``OperationMode`` into semantically distinct enums + + - ``DhwOperationSetting``: User-configured mode preferences (HEAT_PUMP, ELECTRIC, ENERGY_SAVER, HIGH_DEMAND, VACATION, POWER_OFF) + - ``CurrentOperationMode``: Real-time operational states (STANDBY, HEAT_PUMP_MODE, HYBRID_EFFICIENCY_MODE, HYBRID_BOOST_MODE) + - Prevents accidental comparison of user preferences with real-time states + - Better IDE support with more specific enum types + +- **Migration Support**: Comprehensive tools for smooth migration + + - ``migrate_operation_mode_usage()`` helper function with programmatic guidance + - ``MIGRATION.md`` with step-by-step migration instructions + - Value mappings and common usage pattern examples + - Backward compatibility preservation during transition + +- **Documentation Updates**: Updated all documentation to reflect new enum structure + + - ``DEVICE_STATUS_FIELDS.rst`` updated with new enum types + - Code examples use new enums with proper imports + - Clear distinction between configuration vs real-time status + +Changed +------- + +- **DeviceStatus Model**: Updated to use specific enum types + + - ``operationMode`` field now uses ``CurrentOperationMode`` type + - ``dhwOperationSetting`` field now uses ``DhwOperationSetting`` type + - Maintains backward compatibility through value preservation + +- **Example Scripts**: Updated to demonstrate new enum usage + + - ``event_emitter_demo.py`` updated to use ``CurrentOperationMode`` + - Fixed incorrect enum references (HEAT_PUMP_ONLY → HEAT_PUMP_MODE) + - All examples remain functional with new type system + +Deprecated +---------- + +- **OperationMode enum**: Will be removed in v3.0.0 + + - All functionality preserved for backward compatibility + - Migration guide available in ``MIGRATION.md`` + - Helper function ``migrate_operation_mode_usage()`` provides guidance + - Original enum remains available during transition period + Version 1.2.2 (2025-10-17) ========================== diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..3e5e8e4 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,217 @@ +# Migration Guide: OperationMode → DhwOperationSetting & CurrentOperationMode + +## Overview + +In version 2.x of nwp500-python, the `OperationMode` enum has been split into two more specific enums to improve code clarity and type safety: + +- **`DhwOperationSetting`**: User-configured mode preferences (values 1-6) +- **`CurrentOperationMode`**: Real-time operational states (values 0, 32, 64, 96) + +This change clarifies the distinction between "what mode the user has configured" vs "what the device is doing right now." + +## Why This Change? + +The original `OperationMode` enum mixed two different concepts: +1. **User preferences** (1-6): What heating mode should be used when heating is needed +2. **Real-time status** (0, 32, 64, 96): What the device is actively doing right now + +This led to confusion and potential bugs. The new enums provide: +- **Type safety**: Can't accidentally compare user preferences with real-time states +- **Clarity**: Clear distinction between configuration and current operation +- **Better IDE support**: More specific autocomplete and type checking + +## Migration Steps + +### 1. Update Imports + +**Before:** +```python +from nwp500 import OperationMode +``` + +**After:** +```python +from nwp500 import DhwOperationSetting, CurrentOperationMode +# or keep the old import during transition: +from nwp500 import OperationMode, DhwOperationSetting, CurrentOperationMode +``` + +### 2. Update DHW Operation Setting Comparisons + +**Before:** +```python +if status.dhwOperationSetting == OperationMode.ENERGY_SAVER: + print("Device configured for Energy Saver mode") + +if status.dhwOperationSetting == OperationMode.POWER_OFF: + print("Device is powered off") +``` + +**After:** +```python +if status.dhwOperationSetting == DhwOperationSetting.ENERGY_SAVER: + print("Device configured for Energy Saver mode") + +if status.dhwOperationSetting == DhwOperationSetting.POWER_OFF: + print("Device is powered off") +``` + +### 3. Update Operation Mode Comparisons + +**Before:** +```python +if status.operationMode == OperationMode.STANDBY: + print("Device is idle") + +if status.operationMode == OperationMode.HEAT_PUMP_MODE: + print("Heat pump is running") +``` + +**After:** +```python +if status.operationMode == CurrentOperationMode.STANDBY: + print("Device is idle") + +if status.operationMode == CurrentOperationMode.HEAT_PUMP_MODE: + print("Heat pump is running") +``` + +### 4. Update Type Hints + +**Before:** +```python +def handle_mode_change(old_mode: OperationMode, new_mode: OperationMode): + pass + +def check_dhw_setting(setting: OperationMode) -> bool: + pass +``` + +**After:** +```python +def handle_mode_change(old_mode: CurrentOperationMode, new_mode: CurrentOperationMode): + pass + +def check_dhw_setting(setting: DhwOperationSetting) -> bool: + pass +``` + +### 5. Update Command Sending Logic + +**Before:** +```python +# Vacation mode check +if mode_id == OperationMode.VACATION.value: + # handle vacation days parameter +``` + +**After:** +```python +# Vacation mode check +if mode_id == DhwOperationSetting.VACATION.value: + # handle vacation days parameter +``` + +## Value Mappings + +### DhwOperationSetting (User Preferences) +```python +DhwOperationSetting.HEAT_PUMP = 1 # Heat Pump Only +DhwOperationSetting.ELECTRIC = 2 # Electric Only +DhwOperationSetting.ENERGY_SAVER = 3 # Energy Saver (default) +DhwOperationSetting.HIGH_DEMAND = 4 # High Demand +DhwOperationSetting.VACATION = 5 # Vacation Mode +DhwOperationSetting.POWER_OFF = 6 # Device Powered Off +``` + +### CurrentOperationMode (Real-time States) +```python +CurrentOperationMode.STANDBY = 0 # Device idle +CurrentOperationMode.HEAT_PUMP_MODE = 32 # Heat pump active +CurrentOperationMode.HYBRID_EFFICIENCY_MODE = 64 # Energy Saver mode heating +CurrentOperationMode.HYBRID_BOOST_MODE = 96 # High Demand mode heating +``` + +## Common Migration Patterns + +### Event Handlers +**Before:** +```python +def on_mode_change(old_mode: OperationMode, new_mode: OperationMode): + if new_mode == OperationMode.HEAT_PUMP_MODE: + print("Heat pump started") +``` + +**After:** +```python +def on_mode_change(old_mode: CurrentOperationMode, new_mode: CurrentOperationMode): + if new_mode == CurrentOperationMode.HEAT_PUMP_MODE: + print("Heat pump started") +``` + +### Status Display Logic +**Before:** +```python +def get_device_status(status: DeviceStatus) -> dict: + return { + 'mode': status.dhwOperationSetting.name, # User's setting + 'active': status.operationMode != OperationMode.STANDBY, # Currently heating? + 'powered_on': status.dhwOperationSetting != OperationMode.POWER_OFF + } +``` + +**After:** +```python +def get_device_status(status: DeviceStatus) -> dict: + return { + 'mode': status.dhwOperationSetting.name, # User's setting + 'active': status.operationMode != CurrentOperationMode.STANDBY, # Currently heating? + 'powered_on': status.dhwOperationSetting != DhwOperationSetting.POWER_OFF + } +``` + +## Backward Compatibility + +The original `OperationMode` enum remains available for backward compatibility but is **deprecated**: + +```python +# Still works, but deprecated +from nwp500 import OperationMode +if status.dhwOperationSetting == OperationMode.ENERGY_SAVER: + pass +``` + +**Note**: The deprecated enum will be removed in a future major version (3.0+). + +## Testing Your Migration + +Use the migration helper to validate your changes: + +```python +from nwp500 import migrate_operation_mode_usage + +# Get migration guidance +guide = migrate_operation_mode_usage() +print(guide['migration_examples']) +``` + +## Timeline + +- **Current (2.x)**: Both old and new enums available, old enum deprecated +- **Next minor (2.x+1)**: Deprecation warnings added +- **Future major (3.0)**: `OperationMode` enum removed + +## Need Help? + +- Check the [documentation](docs/DEVICE_STATUS_FIELDS.rst) for detailed explanations +- Use the `migrate_operation_mode_usage()` helper function for guidance +- Look at updated examples in the `examples/` directory +- File an issue if you encounter migration problems + +## Benefits After Migration + +1. **Type Safety**: Compiler/IDE catches enum mismatches +2. **Clarity**: Clear distinction between user preferences and device state +3. **Better Documentation**: Each enum has focused, specific documentation +4. **Future-Proof**: Easier to extend either enum independently +5. **Reduced Bugs**: Impossible to accidentally compare incompatible concepts \ No newline at end of file diff --git a/docs/DEVICE_STATUS_FIELDS.rst b/docs/DEVICE_STATUS_FIELDS.rst index abe2a00..b3c5767 100644 --- a/docs/DEVICE_STATUS_FIELDS.rst +++ b/docs/DEVICE_STATUS_FIELDS.rst @@ -44,7 +44,7 @@ This document lists the fields found in the ``status`` object of device status m - Sub error code providing additional error details. See ERROR_CODES.rst for details. - None * - ``operationMode`` - - OperationMode + - CurrentOperationMode - None - The current **actual operational state** of the device (what it's doing RIGHT NOW). Reports status values: 0=Standby, 32=Heat Pump active, 64=Energy Saver active, 96=High Demand active. See Operation Modes section below for the critical distinction between this and ``dhwOperationSetting``. - None @@ -229,9 +229,9 @@ This document lists the fields found in the ``status`` object of device status m - Type of program reservation. - None * - ``dhwOperationSetting`` - - OperationMode + - DhwOperationSetting - None - - User's configured DHW operation mode preference. This field uses the same ``OperationMode`` enum as ``operationMode`` but contains command mode values (1=HEAT_PUMP, 2=ELECTRIC, 3=ENERGY_SAVER, 4=HIGH_DEMAND, 5=VACATION, 6=POWER_OFF). When the device is powered off via the power-off command, this field will show 6 (POWER_OFF). This is how to distinguish between "powered off" vs "on but in standby". See the Operation Modes section below for details. + - User's configured DHW operation mode preference. This field uses the ``DhwOperationSetting`` enum (separate from ``CurrentOperationMode``) and contains command mode values (1=HEAT_PUMP, 2=ELECTRIC, 3=ENERGY_SAVER, 4=HIGH_DEMAND, 5=VACATION, 6=POWER_OFF). When the device is powered off via the power-off command, this field will show 6 (POWER_OFF). This is how to distinguish between "powered off" vs "on but in standby". See the Operation Modes section below for details. - None * - ``temperatureType`` - integer @@ -533,10 +533,10 @@ These two fields serve different purposes and it's critical to understand their Field Definitions ^^^^^^^^^^^^^^^^^ -**dhwOperationSetting** (OperationMode enum with command values 1-5) +**dhwOperationSetting** (DhwOperationSetting enum with command values 1-6) The user's **configured mode preference** - what heating mode the device should use when it needs to heat water. This is set via the ``dhw-mode`` command and persists until changed by the user or device. - * Type: ``OperationMode`` enum + * Type: ``DhwOperationSetting`` enum * Values: * 1 = ``HEAT_PUMP`` (Heat Pump Only) @@ -551,10 +551,10 @@ Field Definitions * Meaning: "When heating is needed, use this mode" OR "I'm powered off" (if value is 6) * Value 6 (``POWER_OFF``) indicates the device was powered off via the power-off command. This is how to distinguish between "powered off" and "on but idle". -**operationMode** (OperationMode enum with status values 0, 32, 64, 96) +**operationMode** (CurrentOperationMode enum with status values 0, 32, 64, 96) The device's **current actual operational state** - what the device is doing RIGHT NOW. This reflects real-time operation and changes automatically based on whether the device is idle or actively heating. - * Type: ``OperationMode`` enum + * Type: ``CurrentOperationMode`` enum * Values: * 0 = ``STANDBY`` (Idle, not heating) @@ -661,11 +661,13 @@ For user-facing applications, follow these guidelines: **Code Example** .. code-block:: python + from nwp500.models import DeviceStatus, DhwOperationSetting, CurrentOperationMode + def format_mode_display(status: DeviceStatus) -> dict: """Format mode and status for UI display.""" # Check if device is powered off first - if status.dhwOperationSetting == OperationMode.POWER_OFF: + if status.dhwOperationSetting == DhwOperationSetting.POWER_OFF: return { 'configured_mode': 'Off', 'operational_state': 'Powered Off', @@ -678,16 +680,16 @@ For user-facing applications, follow these guidelines: configured_mode = status.dhwOperationSetting.name.replace('_', ' ').title() # Current operational state - if status.operationMode == OperationMode.STANDBY: + if status.operationMode == CurrentOperationMode.STANDBY: operational_state = "Idle" is_heating = False - elif status.operationMode == OperationMode.HEAT_PUMP_MODE: + elif status.operationMode == CurrentOperationMode.HEAT_PUMP_MODE: operational_state = "Heating (Heat Pump)" is_heating = True - elif status.operationMode == OperationMode.HYBRID_EFFICIENCY_MODE: + elif status.operationMode == CurrentOperationMode.HYBRID_EFFICIENCY_MODE: operational_state = "Heating (Energy Saver)" is_heating = True - elif status.operationMode == OperationMode.HYBRID_BOOST_MODE: + elif status.operationMode == CurrentOperationMode.HYBRID_BOOST_MODE: operational_state = "Heating (High Demand)" is_heating = True else: @@ -712,9 +714,9 @@ For user-facing applications, follow these guidelines: 4. **operationMode changes automatically** - you cannot directly set this; it changes based on device operation -5. **Both fields use OperationMode enum** - but different value ranges (1-6 for dhwOperationSetting, 0/32/64/96 for operationMode) +5. **Separate enum types provide clarity** - ``DhwOperationSetting`` (values 1-6) for user preferences, ``CurrentOperationMode`` (values 0/32/64/96) for real-time states -6. **Power off detection** - Check if ``dhwOperationSetting == 6`` (``POWER_OFF``) to determine if device is powered off vs just idle +6. **Power off detection** - Check if ``dhwOperationSetting == DhwOperationSetting.POWER_OFF`` to determine if device is powered off vs just idle Technical Notes --------------- diff --git a/examples/event_emitter_demo.py b/examples/event_emitter_demo.py index a9db9e4..545d64a 100644 --- a/examples/event_emitter_demo.py +++ b/examples/event_emitter_demo.py @@ -29,7 +29,7 @@ ) from nwp500 import NavienAPIClient, NavienAuthClient, NavienMqttClient -from nwp500.models import DeviceStatus, OperationMode +from nwp500.models import DeviceStatus, CurrentOperationMode # Example 1: Multiple listeners for the same event @@ -52,16 +52,18 @@ async def save_temperature_to_db(old_temp: float, new_temp: float): # Example 2: Mode change handlers -def log_mode_change(old_mode: OperationMode, new_mode: OperationMode): +def log_mode_change(old_mode: CurrentOperationMode, new_mode: CurrentOperationMode): """Log operation mode changes.""" print(f"🔄 [Mode] Changed from {old_mode.name} to {new_mode.name}") -def optimize_on_mode_change(old_mode: OperationMode, new_mode: OperationMode): +def optimize_on_mode_change( + old_mode: CurrentOperationMode, new_mode: CurrentOperationMode +): """Optimization handler.""" - if new_mode == OperationMode.HEAT_PUMP_ONLY: + if new_mode == CurrentOperationMode.HEAT_PUMP_MODE: print("♻️ [Optimizer] Heat pump mode - maximum efficiency!") - elif new_mode == OperationMode.HIGH_DEMAND: + elif new_mode == CurrentOperationMode.HYBRID_BOOST_MODE: print("⚡ [Optimizer] High demand mode - fast recovery!") diff --git a/src/nwp500/__init__.py b/src/nwp500/__init__.py index 80cccad..37ccd5d 100644 --- a/src/nwp500/__init__.py +++ b/src/nwp500/__init__.py @@ -34,10 +34,12 @@ EventListener, ) from nwp500.models import ( + CurrentOperationMode, Device, DeviceFeature, DeviceInfo, DeviceStatus, + DhwOperationSetting, EnergyUsageData, EnergyUsageResponse, EnergyUsageTotal, @@ -46,10 +48,11 @@ MonthlyEnergyData, MqttCommand, MqttRequest, - OperationMode, + OperationMode, # Deprecated - use DhwOperationSetting or CurrentOperationMode TemperatureUnit, TOUInfo, TOUSchedule, + enable_deprecation_warnings, ) from nwp500.mqtt_client import ( MqttConnectionConfig, @@ -68,7 +71,9 @@ "FirmwareInfo", "TOUSchedule", "TOUInfo", - "OperationMode", + "OperationMode", # Deprecated - use DhwOperationSetting or CurrentOperationMode + "DhwOperationSetting", # New: User-configured mode preferences + "CurrentOperationMode", # New: Real-time operational states "TemperatureUnit", "MqttRequest", "MqttCommand", @@ -99,4 +104,57 @@ # Event Emitter "EventEmitter", "EventListener", + # Migration helpers + "migrate_operation_mode_usage", + "enable_deprecation_warnings", ] + + +# Migration helper for backward compatibility +def migrate_operation_mode_usage(): + """ + Helper function to guide migration from OperationMode to specific enums. + + This function provides guidance on migrating from the deprecated OperationMode + enum to the new DhwOperationSetting and CurrentOperationMode enums. + + Returns: + dict: Migration guidance with examples + """ + return { + "deprecated": "OperationMode", + "replacements": { + "dhw_operation_setting": "DhwOperationSetting", + "operation_mode": "CurrentOperationMode", + }, + "migration_examples": { + "dhw_setting_comparison": { + "old": "status.dhwOperationSetting == OperationMode.ENERGY_SAVER", + "new": "status.dhwOperationSetting == DhwOperationSetting.ENERGY_SAVER", + }, + "operation_mode_comparison": { + "old": "status.operationMode == OperationMode.STANDBY", + "new": "status.operationMode == CurrentOperationMode.STANDBY", + }, + "imports": { + "old": "from nwp500 import OperationMode", + "new": "from nwp500 import DhwOperationSetting, CurrentOperationMode", + }, + }, + "value_mappings": { + "DhwOperationSetting": { + "HEAT_PUMP": 1, + "ELECTRIC": 2, + "ENERGY_SAVER": 3, + "HIGH_DEMAND": 4, + "VACATION": 5, + "POWER_OFF": 6, + }, + "CurrentOperationMode": { + "STANDBY": 0, + "HEAT_PUMP_MODE": 32, + "HYBRID_EFFICIENCY_MODE": 64, + "HYBRID_BOOST_MODE": 96, + }, + }, + } diff --git a/src/nwp500/models.py b/src/nwp500/models.py index 8057632..1a53da2 100644 --- a/src/nwp500/models.py +++ b/src/nwp500/models.py @@ -6,6 +6,7 @@ """ import logging +import warnings from dataclasses import dataclass, field from enum import Enum from typing import Any, Optional, Union @@ -14,6 +15,38 @@ _logger = logging.getLogger(__name__) +# Flag to control deprecation warnings (disabled by default for backward compatibility) +_ENABLE_DEPRECATION_WARNINGS = False + + +def enable_deprecation_warnings(enabled: bool = True): + """ + Enable or disable deprecation warnings for the OperationMode enum. + + Args: + enabled: If True, using OperationMode will emit deprecation warnings. + If False (default), no warnings are emitted for backward compatibility. + + Example: + >>> from nwp500.models import enable_deprecation_warnings + >>> enable_deprecation_warnings(True) # Enable warnings + >>> # Now using OperationMode will emit warnings + """ + global _ENABLE_DEPRECATION_WARNINGS + _ENABLE_DEPRECATION_WARNINGS = enabled + + +def _warn_deprecated_operation_mode(): + """Emit deprecation warning for OperationMode usage if enabled.""" + if _ENABLE_DEPRECATION_WARNINGS: + warnings.warn( + "OperationMode is deprecated and will be removed in v3.0.0. " + "Use DhwOperationSetting for user preferences or CurrentOperationMode for " + "real-time states. See MIGRATION.md for migration guidance.", + DeprecationWarning, + stacklevel=3, + ) + def _decicelsius_to_fahrenheit(raw_value: float) -> float: """ @@ -33,9 +66,71 @@ def _decicelsius_to_fahrenheit(raw_value: float) -> float: return (celsius * 9 / 5) + 32 +class DhwOperationSetting(Enum): + """Enumeration for DHW operation setting modes (user-configured preferences). + + This enum represents the user's configured mode preference - what heating mode + the device should use when it needs to heat water. These values appear in the + dhwOperationSetting field and are set via user commands. + + These modes balance energy efficiency and recovery time based on user needs: + - Higher efficiency = longer recovery time, lower operating costs + - Lower efficiency = faster recovery time, higher operating costs + + Values are based on the MQTT protocol dhw-mode command parameter. + """ + + HEAT_PUMP = 1 # Heat Pump Only - most efficient, slowest recovery + ELECTRIC = 2 # Electric Only - least efficient, fastest recovery + ENERGY_SAVER = 3 # Hybrid: Efficiency - balanced, good default + HIGH_DEMAND = 4 # Hybrid: Boost - maximum heating capacity + VACATION = 5 # Vacation mode - suspends heating to save energy + POWER_OFF = 6 # Device powered off - appears when device is turned off + + +class CurrentOperationMode(Enum): + """Enumeration for current operation mode (real-time operational state). + + This enum represents the device's current actual operational state - what + the device is doing RIGHT NOW. These values appear in the operationMode + field and change automatically based on heating demand. + + Unlike DhwOperationSetting (user preference), this reflects real-time + operation and changes dynamically as the device starts/stops heating. + + Values are based on device status responses in MQTT messages. + """ + + STANDBY = 0 # Device is idle, not actively heating + HEAT_PUMP_MODE = 32 # Heat pump is actively running to heat water + HYBRID_EFFICIENCY_MODE = 64 # Device actively heating in Energy Saver mode + HYBRID_BOOST_MODE = 96 # Device actively heating in High Demand mode + + class OperationMode(Enum): """Enumeration for the operation modes of the device. + .. deprecated:: + The ``OperationMode`` enum is deprecated and will be removed in a future version. + Use ``DhwOperationSetting`` for user-configured mode preferences (values 1-6) + or ``CurrentOperationMode`` for real-time operational states (values 0, 32, 64, 96). + + Migration guide: + - Replace ``OperationMode`` enum references in dhwOperationSetting contexts with + ``DhwOperationSetting`` + - Replace ``OperationMode`` enum references in operationMode contexts with + ``CurrentOperationMode`` + - Update type hints accordingly + + Example: + # Old (deprecated): + status.dhwOperationSetting == OperationMode.ENERGY_SAVER + status.operationMode == OperationMode.STANDBY + + # New (recommended): + status.dhwOperationSetting == DhwOperationSetting.ENERGY_SAVER + status.operationMode == CurrentOperationMode.STANDBY + The first set of modes (0-6) are used when commanding the device or appear in dhwOperationSetting, while the second set (32, 64, 96) are observed in the operationMode status field. @@ -64,6 +159,12 @@ class OperationMode(Enum): HYBRID_EFFICIENCY_MODE = 64 HYBRID_BOOST_MODE = 96 + def __getattribute__(self, name): + """Override to emit deprecation warning on value access when enabled.""" + if name == "value" or name == "name": + _warn_deprecated_operation_mode() + return super().__getattribute__(name) + class TemperatureUnit(Enum): """Enumeration for temperature units.""" @@ -228,7 +329,7 @@ class DeviceStatus: didReload: bool errorCode: int subErrorCode: int - operationMode: OperationMode + operationMode: CurrentOperationMode operationBusy: bool freezeProtectionUse: bool dhwUse: bool @@ -265,7 +366,7 @@ class DeviceStatus: antiLegionellaPeriod: int antiLegionellaOperationBusy: bool programReservationType: int - dhwOperationSetting: OperationMode # User's configured mode preference (command modes: 1-6) + dhwOperationSetting: DhwOperationSetting # User's configured mode preference temperatureType: TemperatureUnit tempFormulaType: str errorBuzzerUse: bool @@ -446,18 +547,20 @@ def from_dict(cls, data: dict): # Convert enum fields with error handling for unknown values if "operationMode" in converted_data: try: - converted_data["operationMode"] = OperationMode(converted_data["operationMode"]) + converted_data["operationMode"] = CurrentOperationMode( + converted_data["operationMode"] + ) except ValueError: _logger.warning( "Unknown operationMode: %s. Defaulting to STANDBY.", converted_data["operationMode"], ) # Default to a safe enum value so callers can rely on .name - converted_data["operationMode"] = OperationMode.STANDBY + converted_data["operationMode"] = CurrentOperationMode.STANDBY if "dhwOperationSetting" in converted_data: try: - converted_data["dhwOperationSetting"] = OperationMode( + converted_data["dhwOperationSetting"] = DhwOperationSetting( converted_data["dhwOperationSetting"] ) except ValueError: @@ -466,7 +569,7 @@ def from_dict(cls, data: dict): converted_data["dhwOperationSetting"], ) # Default to ENERGY_SAVER as a safe default - converted_data["dhwOperationSetting"] = OperationMode.ENERGY_SAVER + converted_data["dhwOperationSetting"] = DhwOperationSetting.ENERGY_SAVER if "temperatureType" in converted_data: try: diff --git a/src/nwp500/mqtt_client.py b/src/nwp500/mqtt_client.py index 5376ae2..3cf0424 100644 --- a/src/nwp500/mqtt_client.py +++ b/src/nwp500/mqtt_client.py @@ -47,8 +47,8 @@ Device, DeviceFeature, DeviceStatus, + DhwOperationSetting, EnergyUsageResponse, - OperationMode, ) __author__ = "Emmanuel Levijarvi" @@ -1289,7 +1289,7 @@ async def set_dhw_mode( - 4: High Demand (maximum heating capacity) - 5: Vacation Mode (requires vacation_days parameter) """ - if mode_id == OperationMode.VACATION.value: + if mode_id == DhwOperationSetting.VACATION.value: if vacation_days is None: raise ValueError("Vacation mode requires vacation_days (1-30)") if not 1 <= vacation_days <= 30: