Repository: XKNX/xknx
Pull request #1519 (2024-07-20 16:22:52)

Add DPT 1.007 and 1.008
<!--
  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.
-->


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.
-->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] 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

- [ ] The documentation has been adjusted accordingly
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog (docs/changelog.md)

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/XKNX/xknx/pull/1519?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.68%. Comparing base [(`9822460`)](https://app.codecov.io/gh/XKNX/xknx/commit/982246082eb8947c7cf3b6ed3deb49a77983663b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) to head [(`5ca793d`)](https://app.codecov.io/gh/XKNX/xknx/commit/5ca793d0851236a813139b61be1156794b4954b0?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/1519/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/1519?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@           Coverage Diff           @@
##             main    #1519   +/-   ##
=======================================
  Coverage   96.67%   96.68%           
=======================================
  Files         157      157           
  Lines       10149    10171   +22     
=======================================
+ Hits         9812     9834   +22     
  Misses        337      337           
```

| [Files](https://app.codecov.io/gh/XKNX/xknx/pull/1519?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/dpt/\_\_init\_\_.py](https://app.codecov.io/gh/XKNX/xknx/pull/1519?src=pr&el=tree&filepath=xknx%2Fdpt%2F__init__.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kcHQvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [xknx/dpt/dpt\_1.py](https://app.codecov.io/gh/XKNX/xknx/pull/1519?src=pr&el=tree&filepath=xknx%2Fdpt%2Fdpt_1.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kcHQvZHB0XzEucHk=) | `100.00% <100.00%> (ø)` | |

</details>

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index e4bac2cb..eff0cf0d 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -46,7 +46,9 @@ nav_order: 2
   - 251.600 - DPTColorRGBW - RGBWColor
   - 20.60102 - DPTHVACStatus - HVACStatus (removed DPTControllerStatus in favour of this)
 - DPTEnum: Common interface for DPT transcoders with enumueration values. Transcoders accept Enum or string values for encoding.
-  - 1.100 - DPTHeatCool - HeatCool
+  - 1.007 - DPTStep
+  - 1.008 - DPTUpDown
+  - 1.100 - DPTHeatCool
   - 20.102 - DPTHVACMode - HVACOperationMode
     - rename "NIGHT" to "ECONOMY" and "FROST_PROTECTION" to "BUILDING_PROTECTION" according to KNX specifications
   - 20.105 - DPTHVACContrMode - HVACControllerMode
diff --git a/xknx/dpt/__init__.py b/xknx/dpt/__init__.py
index 88a1fe46..335d6af9 100644
--- a/xknx/dpt/__init__.py
+++ b/xknx/dpt/__init__.py
@@ -7,7 +7,7 @@ Module for encoding and decoding KNX datatypes.
 
 # ruff: noqa: F401
 from .dpt import DPTBase, DPTComplex, DPTComplexData, DPTEnum, DPTNumeric
-from .dpt_1 import DPTHeatCool
+from .dpt_1 import DPTHeatCool, DPTStep, DPTUpDown
 from .dpt_3 import (
     DPTControlStartStop,
     DPTControlStartStopBlinds,
diff --git a/xknx/dpt/dpt_1.py b/xknx/dpt/dpt_1.py
index 7426a58f..4f30f307 100644
--- a/xknx/dpt/dpt_1.py
+++ b/xknx/dpt/dpt_1.py
@@ -16,6 +16,56 @@ class DPT1BitEnum(DPTEnum[EnumT]):
     dpt_main_number = 1
 
 
+class Step(Enum):
+    """Enum for dimming control."""
+
+    DECREASE = False
+    INCREASE = True
+
+
+class DPTStep(DPT1BitEnum[Step]):
+    """
+    Abstraction for KNX 1-Bit step value.
+
+    DPT 1.007
+    """
+
+    dpt_main_number = 1
+    dpt_sub_number = 7
+    value_type = "step"
+    data_type = Step
+
+    @classmethod
+    def _to_knx(cls, value: Step) -> DPTBinary:
+        """Return the raw KNX value for an Enum member."""
+        return DPTBinary(value.value)
+
+
+class UpDown(Enum):
+    """Enum for up/down."""
+
+    UP = False
+    DOWN = True
+
+
+class DPTUpDown(DPT1BitEnum[UpDown]):
+    """
+    Abstraction for KNX 1-Bit up/down value.
+
+    DPT 1.008
+    """
+
+    dpt_main_number = 1
+    dpt_sub_number = 8
+    value_type = "up_down"
+    data_type = UpDown
+
+    @classmethod
+    def _to_knx(cls, value: UpDown) -> DPTBinary:
+        """Return the raw KNX value for an Enum member."""
+        return DPTBinary(value.value)
+
+
 class HeatCool(Enum):
     """Enum for heat/cool."""
 


Test patch:
diff --git a/test/dpt_tests/dpt_1_test.py b/test/dpt_tests/dpt_1_test.py
index 7665a9aa..6f4777c7 100644
--- a/test/dpt_tests/dpt_1_test.py
+++ b/test/dpt_tests/dpt_1_test.py
@@ -3,43 +3,51 @@
 import pytest
 
 from xknx.dpt import DPTArray, DPTBinary
-from xknx.dpt.dpt_1 import DPTHeatCool, HeatCool
+from xknx.dpt.dpt_1 import DPTHeatCool, DPTStep, DPTUpDown, HeatCool, Step, UpDown
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 
-class TestDPTHeatCool:
-    """Test class for KNX DPT HVAC Operation modes."""
+@pytest.mark.parametrize(
+    ("dpt", "value_false", "value_true"),
+    [
+        (DPTStep, Step.DECREASE, Step.INCREASE),
+        (DPTUpDown, UpDown.UP, UpDown.DOWN),
+        (DPTHeatCool, HeatCool.COOL, HeatCool.HEAT),
+    ],
+)
+class TestDPT1:
+    """Test class for KNX DPT 1 values."""
 
-    def test_to_knx(self):
+    def test_to_knx(self, dpt, value_true, value_false):
         """Test parsing to KNX."""
-        assert DPTHeatCool.to_knx(HeatCool.HEAT) == DPTBinary(1)
-        assert DPTHeatCool.to_knx(HeatCool.COOL) == DPTBinary(0)
+        assert dpt.to_knx(value_true) == DPTBinary(1)
+        assert dpt.to_knx(value_false) == DPTBinary(0)
 
-    def test_to_knx_by_string(self):
+    def test_to_knx_by_string(self, dpt, value_true, value_false):
         """Test parsing string values to KNX."""
-        assert DPTHeatCool.to_knx("heat") == DPTBinary(1)
-        assert DPTHeatCool.to_knx("cool") == DPTBinary(0)
+        assert dpt.to_knx(value_true.name.lower()) == DPTBinary(1)
+        assert dpt.to_knx(value_false.name.lower()) == DPTBinary(0)
 
-    def test_to_knx_by_value(self):
+    def test_to_knx_by_value(self, dpt, value_true, value_false):
         """Test parsing string values to KNX."""
-        assert DPTHeatCool.to_knx(True) == DPTBinary(1)
-        assert DPTHeatCool.to_knx(1) == DPTBinary(1)
-        assert DPTHeatCool.to_knx(False) == DPTBinary(0)
-        assert DPTHeatCool.to_knx(0) == DPTBinary(0)
+        assert dpt.to_knx(True) == DPTBinary(1)
+        assert dpt.to_knx(1) == DPTBinary(1)
+        assert dpt.to_knx(False) == DPTBinary(0)
+        assert dpt.to_knx(0) == DPTBinary(0)
 
-    def test_to_knx_wrong_value(self):
+    def test_to_knx_wrong_value(self, dpt, value_true, value_false):
         """Test serializing to KNX with wrong value."""
         with pytest.raises(ConversionError):
-            DPTHeatCool.to_knx(2)
+            dpt.to_knx(2)
 
-    def test_mode_from_knx(self):
-        """Test parsing DPTHVACMode from KNX."""
-        assert DPTHeatCool.from_knx(DPTBinary(0)) == HeatCool.COOL
-        assert DPTHeatCool.from_knx(DPTBinary(1)) == HeatCool.HEAT
+    def test_from_knx(self, dpt, value_true, value_false):
+        """Test parsing from KNX."""
+        assert dpt.from_knx(DPTBinary(0)) == value_false
+        assert dpt.from_knx(DPTBinary(1)) == value_true
 
-    def test_mode_from_knx_wrong_value(self):
-        """Test parsing of DPTHVACMode with wrong value)."""
+    def test_from_knx_wrong_value(self, dpt, value_true, value_false):
+        """Test parsing with wrong value)."""
         with pytest.raises(CouldNotParseTelegram):
-            DPTHeatCool.from_knx(DPTArray((1,)))
+            dpt.from_knx(DPTArray((1,)))
         with pytest.raises(CouldNotParseTelegram):
-            DPTHeatCool.from_knx(DPTBinary(2))
+            dpt.from_knx(DPTBinary(2))
diff --git a/test/dpt_tests/dpt_lookup_test.py b/test/dpt_tests/dpt_lookup_test.py
index d85ac584..3af80884 100644
--- a/test/dpt_tests/dpt_lookup_test.py
+++ b/test/dpt_tests/dpt_lookup_test.py
@@ -163,6 +163,7 @@ from xknx.dpt import *
         ("solid_angle", DPTSolidAngle, 14, 63, "sr"),
         ("sound_intensity", DPTSoundIntensity, 14, 64, "W/m²"),
         ("speed", DPTSpeed, 14, 65, "m/s"),
+        ("step", DPTStep, 1, 7, None),
         ("stepwise_blinds", DPTControlStepwiseBlinds, 3, 8, "%"),
         ("stepwise_dimming", DPTControlStepwiseDimming, 3, 7, "%"),
         ("stress", DPTStress, 14, 66, "Pa"),
@@ -188,6 +189,7 @@ from xknx.dpt import *
         ("time_period_sec", DPTTimePeriodSec, 7, 5, "s"),
         ("time_seconds", DPTTimeSeconds, 14, 74, "s"),
         ("torque", DPTTorque, 14, 75, "Nm"),
+        ("up_down", DPTUpDown, 1, 8, None),
         ("voltage", DPTVoltage, 9, 20, "mV"),
         ("volume", DPTVolume, 14, 76, "m³"),
         ("volume_flow", DPTVolumeFlow, 9, 25, "L/h"),