Repository: XKNX/xknx
Pull request #1573 (2024-09-22 17:58:59)

new field for fan speed mode
<!--
  You are awesome! Thanks for contributing to our project!
  Please, DO NOT DELETE ANY TEXT from this template!.
-->
## Description
<!--
Please include a summary of the change and which issue is fixed.
Please also include relevant motivation and context.
List any dependencies that are required for this change.
-->

New field for fan speed mode, instead of `fan_max_steps`.

Fixes # (issue)

## Type of change
<!--
Please tick the applicable options.
NOTE: Ticking multiple options most likely indicates
that your change is to big and it is suggested to split it into several smaller PRs.
-->

- [X] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Code quality improvements to existing code or addition of tests

## Checklist

- [X] The documentation has been adjusted accordingly
- [X] Tests have been added that prove the fix is effective or that the feature works
- [ ] The changes are documented in the changelog (docs/changelog.md)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/XKNX/xknx/pull/1573?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 96.92%. Comparing base [(`a94b860`)](https://app.codecov.io/gh/XKNX/xknx/commit/a94b86042bbdbe21c17b26749ae6e27167caa613?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) to head [(`8fc9196`)](https://app.codecov.io/gh/XKNX/xknx/commit/8fc9196d870cef6b8b5d245e8f8cda4d8a6f7d6d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX).

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/XKNX/xknx/pull/1573/graphs/tree.svg?width=650&height=150&src=pr&token=irWbIygS84&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)](https://app.codecov.io/gh/XKNX/xknx/pull/1573?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@            Coverage Diff             @@
##             main    #1573      +/-   ##
==========================================
- Coverage   96.92%   96.92%   -0.01%     
==========================================
  Files         157      157              
  Lines       10570    10569       -1     
==========================================
- Hits        10245    10244       -1     
  Misses        325      325              
```

| [Files with missing lines](https://app.codecov.io/gh/XKNX/xknx/pull/1573?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/devices/climate.py](https://app.codecov.io/gh/XKNX/xknx/pull/1573?src=pr&el=tree&filepath=xknx%2Fdevices%2Fclimate.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL2NsaW1hdGUucHk=) | `100.00% <100.00%> (ø)` | |

</details>

Patch:
diff --git a/docs/climate.md b/docs/climate.md
index 97cbd7d8..5fd0111d 100644
--- a/docs/climate.md
+++ b/docs/climate.md
@@ -34,7 +34,7 @@ Climate are representations of KNX HVAC/Climate controls.
 - `sync_state` defines if and how often the value should be actively read from the bus. If `False` no GroupValueRead telegrams will be sent to its group address. Defaults to `True`
 - `max_temp` Maximum value for target temperature.
 - `min_temp` Minimum value for target temperature.
-- `fan_max_step` Maximum step amount for fans which are controlled with steps and not percentage. If this attribute is set, the fan is controlled by sending the step value in the range `0` and `max_step`. In that case, the group address DPT changes from *DPT 5.001* to *DPT 5.010*. Default: None.
+- `fan_speed_mode` Defines the mode of fan speed control. Can be either `FanSpeedMode.STEP` for step-wise control or `FanSpeedMode.PERCENT` for percentage control. For `STEP` mode, the fan speed is controlled by sending integer values, and the group address uses *DPT 5.010*. For `PERCENT` mode, the fan speed is controlled by sending percentage values (0-100%), and the group address uses *DPT 5.001*. Default: `FanSpeedMode.PERCENT`.
 - `mode` ClimateMode instance for this climate device
 - `group_address_operation_mode` KNX address for operation mode. *DPT 20.102*
 - `group_address_operation_mode_state` KNX address for operation mode status. *DPT 20.102*
@@ -93,7 +93,7 @@ climate = Climate(
     max_temp=26,
     mode=climate_mode,
     device_updated_cb=None,
-    fan_max_step=None,
+    fan_speed_mode=FanSpeedMode.STEP,
 )
 xknx.devices.async_add(climate)
 xknx.devices.async_add(climate_mode)
diff --git a/xknx/devices/climate.py b/xknx/devices/climate.py
index 48ec3350..dc54c75b 100644
--- a/xknx/devices/climate.py
+++ b/xknx/devices/climate.py
@@ -67,7 +67,7 @@ class Climate(Device):
         device_updated_cb: DeviceCallbackType[Climate] | None = None,
         group_address_fan_speed: GroupAddressesType = None,
         group_address_fan_speed_state: GroupAddressesType = None,
-        fan_max_step: int | None = None,
+        fan_speed_mode: FanSpeedMode = FanSpeedMode.PERCENT,
     ):
         """Initialize Climate class."""
         super().__init__(xknx, name, device_updated_cb)
@@ -141,10 +141,9 @@ class Climate(Device):
         )
 
         self.fan_speed: RemoteValueDptValue1Ucount | RemoteValueScaling
-        self.fan_mode = FanSpeedMode.STEP if fan_max_step else FanSpeedMode.PERCENT
-        self.fan_max_step = fan_max_step
+        self.fan_speed_mode = fan_speed_mode
 
-        if self.fan_mode == FanSpeedMode.STEP:
+        if self.fan_speed_mode == FanSpeedMode.STEP:
             self.fan_speed = RemoteValueDptValue1Ucount(
                 xknx,
                 group_address_fan_speed,


Test patch:
diff --git a/requirements/testing.txt b/requirements/testing.txt
index 503063b8..efd26f13 100644
--- a/requirements/testing.txt
+++ b/requirements/testing.txt
@@ -7,7 +7,7 @@ pytest==8.3.3
 pytest-asyncio==0.24.0
 pytest-cov==5.0.0
 pytest-icdiff==0.9
-ruff==0.6.5
-setuptools==75.0.0
-tox==4.18.1
+ruff==0.6.7
+setuptools==75.1.0
+tox==4.20.0
 tox-gh-actions==3.2.0
diff --git a/test/devices_tests/climate_test.py b/test/devices_tests/climate_test.py
index d6f5d8ca..63fef737 100644
--- a/test/devices_tests/climate_test.py
+++ b/test/devices_tests/climate_test.py
@@ -6,7 +6,7 @@ import pytest
 
 from xknx import XKNX
 from xknx.devices import Climate, ClimateMode
-from xknx.devices.climate import SetpointShiftMode
+from xknx.devices.climate import FanSpeedMode, SetpointShiftMode
 from xknx.dpt import (
     DPT2ByteFloat,
     DPTArray,
@@ -1571,7 +1571,7 @@ class TestClimate:
             name="TestClimate",
             group_address_fan_speed="1/2/3",
             group_address_fan_speed_state="1/2/4",
-            fan_max_step=3,
+            fan_speed_mode=FanSpeedMode.STEP,
         )
         xknx.devices.async_add(climate_step)
         climate_percent = Climate(
@@ -1580,6 +1580,7 @@ class TestClimate:
             group_address_fan_speed="1/2/5",
             group_address_fan_speed_state="1/2/6",
         )
+        assert climate_percent.fan_speed_mode == FanSpeedMode.PERCENT
         xknx.devices.async_add(climate_percent)
 
         # Test initial state