Repository: XKNX/xknx
Pull request #917 (2022-04-13 21:13:37)

Add `value_type` attribute to Notification for text encoding
<!--
  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.
-->
Add value_type to Notification for text encoding
Currently accepted value types:
- "string" (default)
- "latin_1"

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)
- [ ] This change requires a documentation update

## Checklist:

- [ ] I have performed a self-review of my own code
- [ ] The documentation has been adjusted accordingly
- [x] The changes generate no new warnings
- [x] Tests have been added that prove the fix is effective or that the feature works
- [x] The changes are documented in the changelog
- [ ] The Homeassistant plugin has been adjusted in case of new config options


codecov[bot]: # [Codecov](https://codecov.io/gh/XKNX/xknx/pull/917?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) Report
> Merging [#917](https://codecov.io/gh/XKNX/xknx/pull/917?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) (b7c7710) into [main](https://codecov.io/gh/XKNX/xknx/commit/86f71d38086009376a65389ecfdab4ace2ce3717?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) (86f71d3) will **decrease** coverage by `0.00%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/XKNX/xknx/pull/917/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://codecov.io/gh/XKNX/xknx/pull/917?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX)

```diff
@@            Coverage Diff             @@
##             main     #917      +/-   ##
==========================================
- Coverage   95.61%   95.61%   -0.01%     
==========================================
  Files         131      130       -1     
  Lines        7808     7799       -9     
==========================================
- Hits         7466     7457       -9     
  Misses        342      342              
```


| [Impacted Files](https://codecov.io/gh/XKNX/xknx/pull/917?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX) | Coverage Δ | |
|---|---|---|
| [xknx/devices/notification.py](https://codecov.io/gh/XKNX/xknx/pull/917/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9kZXZpY2VzL25vdGlmaWNhdGlvbi5weQ==) | `100.00% <ø> (ø)` | |
| [xknx/remote\_value/remote\_value\_raw.py](https://codecov.io/gh/XKNX/xknx/pull/917/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9yZW1vdGVfdmFsdWUvcmVtb3RlX3ZhbHVlX3Jhdy5weQ==) | `100.00% <ø> (ø)` | |
| [xknx/\_\_version\_\_.py](https://codecov.io/gh/XKNX/xknx/pull/917/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9fX3ZlcnNpb25fXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [xknx/remote\_value/\_\_init\_\_.py](https://codecov.io/gh/XKNX/xknx/pull/917/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9yZW1vdGVfdmFsdWUvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [xknx/remote\_value/remote\_value\_sensor.py](https://codecov.io/gh/XKNX/xknx/pull/917/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=XKNX#diff-eGtueC9yZW1vdGVfdmFsdWUvcmVtb3RlX3ZhbHVlX3NlbnNvci5weQ==) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/changelog.md b/changelog.md
index 2fee9926..03939c70 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,13 @@
 # Changelog
 
+### Devices
+
+- Notification: add `value_type` argument to set "string" or "latin_1" text encoding
+
+### Internal
+
+- Change RemoteValueString to _RemoteValueGeneric subclass
+
 ## 0.20.2 Handle shutdown properly
 
 ### Bug fixes
diff --git a/xknx/devices/notification.py b/xknx/devices/notification.py
index df02e432..87d4710a 100644
--- a/xknx/devices/notification.py
+++ b/xknx/devices/notification.py
@@ -25,6 +25,7 @@ class Notification(Device):
         group_address: GroupAddressesType | None = None,
         group_address_state: GroupAddressesType | None = None,
         sync_state: bool | int | float | str = True,
+        value_type: str | None = None,
         device_updated_cb: DeviceCallbackType | None = None,
     ):
         """Initialize notification class."""
@@ -35,6 +36,7 @@ class Notification(Device):
             group_address=group_address,
             group_address_state=group_address_state,
             sync_state=sync_state,
+            value_type=value_type,
             device_name=name,
             feature_name="Message",
             after_update_cb=self.after_update,
diff --git a/xknx/remote_value/__init__.py b/xknx/remote_value/__init__.py
index ba8c3a19..c3f0ac61 100644
--- a/xknx/remote_value/__init__.py
+++ b/xknx/remote_value/__init__.py
@@ -18,10 +18,13 @@ from .remote_value_dpt_value_1_ucount import RemoteValueDptValue1Ucount
 from .remote_value_raw import RemoteValueRaw
 from .remote_value_scaling import RemoteValueScaling
 from .remote_value_scene_number import RemoteValueSceneNumber
-from .remote_value_sensor import RemoteValueNumeric, RemoteValueSensor
+from .remote_value_sensor import (
+    RemoteValueNumeric,
+    RemoteValueSensor,
+    RemoteValueString,
+)
 from .remote_value_setpoint_shift import RemoteValueSetpointShift
 from .remote_value_step import RemoteValueStep
-from .remote_value_string import RemoteValueString
 from .remote_value_switch import RemoteValueSwitch
 from .remote_value_temp import RemoteValueTemp
 from .remote_value_updown import RemoteValueUpDown
diff --git a/xknx/remote_value/remote_value_raw.py b/xknx/remote_value/remote_value_raw.py
index 76bc61f0..a1e43915 100644
--- a/xknx/remote_value/remote_value_raw.py
+++ b/xknx/remote_value/remote_value_raw.py
@@ -31,7 +31,7 @@ class RemoteValueRaw(RemoteValue[Union[DPTArray, DPTBinary], int]):
         feature_name: str = "Raw",
         after_update_cb: AsyncCallbackType | None = None,
     ):
-        """Initialize RemoteValueSensor class."""
+        """Initialize RemoteValueRaw class."""
         self.payload_length = payload_length
         super().__init__(
             xknx,
diff --git a/xknx/remote_value/remote_value_sensor.py b/xknx/remote_value/remote_value_sensor.py
index 7d384690..3b5aa757 100644
--- a/xknx/remote_value/remote_value_sensor.py
+++ b/xknx/remote_value/remote_value_sensor.py
@@ -9,7 +9,7 @@ from __future__ import annotations
 from abc import abstractmethod
 from typing import TYPE_CHECKING, TypeVar, Union
 
-from xknx.dpt import DPTArray, DPTBase, DPTBinary, DPTNumeric
+from xknx.dpt import DPTArray, DPTBase, DPTBinary, DPTNumeric, DPTString
 from xknx.exceptions import ConversionError, CouldNotParseTelegram
 
 from .remote_value import AsyncCallbackType, GroupAddressesType, RemoteValue
@@ -24,6 +24,7 @@ class _RemoteValueGeneric(RemoteValue[DPTArray, ValueT]):
     """Abstraction for generic DPT types."""
 
     dpt_base_class: type[DPTBase]
+    _default_dpt_class: type[DPTBase] | None = None
 
     def __init__(
         self,
@@ -37,12 +38,16 @@ class _RemoteValueGeneric(RemoteValue[DPTArray, ValueT]):
         after_update_cb: AsyncCallbackType | None = None,
     ):
         """Initialize RemoteValueSensor class."""
-        if value_type is None:
-            raise ConversionError("no value type given", device_name=device_name)
-        _dpt_class = self.dpt_base_class.parse_transcoder(value_type)
+        _dpt_class = (
+            self.dpt_base_class.parse_transcoder(value_type)
+            if value_type is not None
+            else self._default_dpt_class
+        )
         if _dpt_class is None:
             raise ConversionError(
-                "invalid value type", value_type=value_type, device_name=device_name
+                f"invalid value type for base class {self.dpt_base_class.__name__}",
+                value_type=value_type,
+                device_name=device_name,
             )
         self.dpt_class = _dpt_class
         super().__init__(
@@ -103,3 +108,15 @@ class RemoteValueNumeric(_RemoteValueGeneric[Union[int, float]]):
     def from_knx(self, payload: DPTArray) -> int | float:
         """Convert current payload to value."""
         return self.dpt_class.from_knx(payload.value)
+
+
+class RemoteValueString(_RemoteValueGeneric[str]):
+    """Abstraction for string DPT types."""
+
+    dpt_base_class = DPTString
+    dpt_class: type[DPTString]
+    _default_dpt_class = DPTString
+
+    def from_knx(self, payload: DPTArray) -> str:
+        """Convert current payload to value."""
+        return self.dpt_class.from_knx(payload.value)
diff --git a/xknx/remote_value/remote_value_string.py b/xknx/remote_value/remote_value_string.py
deleted file mode 100644
index c2c5c7e8..00000000
--- a/xknx/remote_value/remote_value_string.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
-Module for managing a remote string value.
-
-DPT 16.000
-"""
-from __future__ import annotations
-
-from xknx.dpt import DPTArray, DPTBinary, DPTString
-from xknx.exceptions import CouldNotParseTelegram
-
-from .remote_value import RemoteValue
-
-
-class RemoteValueString(RemoteValue[DPTArray, str]):
-    """Abstraction for remote value of KNX 16.000 (DPT_String_ASCII)."""
-
-    def payload_valid(self, payload: DPTArray | DPTBinary | None) -> DPTArray:
-        """Test if telegram payload may be parsed."""
-        if (
-            isinstance(payload, DPTArray)
-            and len(payload.value) == DPTString.payload_length
-        ):
-            return payload
-        raise CouldNotParseTelegram("Payload invalid", payload=str(payload))
-
-    def to_knx(self, value: str) -> DPTArray:
-        """Convert value to payload."""
-        return DPTArray(DPTString.to_knx(value))
-
-    def from_knx(self, payload: DPTArray) -> str:
-        """Convert current payload to value."""
-        return DPTString.from_knx(payload.value)


Test patch:
diff --git a/test/remote_value_tests/remote_value_sensor_test.py b/test/remote_value_tests/remote_value_sensor_test.py
index eb491171..4e37aee2 100644
--- a/test/remote_value_tests/remote_value_sensor_test.py
+++ b/test/remote_value_tests/remote_value_sensor_test.py
@@ -29,6 +29,8 @@ class TestRemoteValueSensor:
             RemoteValueSensor(xknx=xknx, value_type=1)
         with pytest.raises(ConversionError):
             RemoteValueSensor(xknx=xknx, value_type=2)
+        with pytest.raises(ConversionError):
+            RemoteValueSensor(xknx=xknx, value_type=None)
         with pytest.raises(ConversionError):
             RemoteValueSensor(xknx=xknx)
 
diff --git a/test/remote_value_tests/remote_value_string_test.py b/test/remote_value_tests/remote_value_string_test.py
index f47de16e..29194e4a 100644
--- a/test/remote_value_tests/remote_value_string_test.py
+++ b/test/remote_value_tests/remote_value_string_test.py
@@ -2,7 +2,7 @@
 import pytest
 
 from xknx import XKNX
-from xknx.dpt import DPTArray, DPTBinary
+from xknx.dpt import DPTArray, DPTBinary, DPTLatin1, DPTString
 from xknx.exceptions import ConversionError
 from xknx.remote_value import RemoteValueString
 from xknx.telegram import GroupAddress, Telegram
@@ -15,8 +15,7 @@ class TestRemoteValueString:
     def test_to_knx(self):
         """Test to_knx function with normal operation."""
         xknx = XKNX()
-        remote_value = RemoteValueString(xknx)
-        assert remote_value.to_knx("KNX is OK") == DPTArray(
+        dpt_array_string = DPTArray(
             (
                 0x4B,
                 0x4E,
@@ -34,6 +33,13 @@ class TestRemoteValueString:
                 0x00,
             )
         )
+        remote_value_ascii = RemoteValueString(xknx)
+        assert remote_value_ascii.dpt_class == DPTString
+        assert remote_value_ascii.to_knx("KNX is OK") == dpt_array_string
+
+        remote_value_latin1 = RemoteValueString(xknx, value_type="latin_1")
+        assert remote_value_latin1.dpt_class == DPTLatin1
+        assert remote_value_latin1.to_knx("KNX is OK") == dpt_array_string
 
     def test_from_knx(self):
         """Test from_knx function with normal operation."""