Skip to content

Commit

Permalink
Merge branch 'master' into climate-expose-temps
Browse files Browse the repository at this point in the history
  • Loading branch information
spacegaier committed Feb 16, 2021
2 parents be854ef + 7521754 commit 2c4cce1
Show file tree
Hide file tree
Showing 63 changed files with 969 additions and 657 deletions.
11 changes: 10 additions & 1 deletion changelog.md
@@ -1,12 +1,21 @@
# Changelog

## 0.xx
## Unreleased changes

### Devices

- BinarySensor: return `None` for `BinarySensor.counter` when context timeout is not used (and don't calculate it)
- Climate: Add `create_temperature_sensors` option to create dedicated sensors for current and target temperature.
- Weather (breaking change!): Renamed `expose_sensors` to `create_sensors` to prevent confusion with the XKNX `expose_sensor` device type.

### Internals

- RemoteValue is Generic now accepting DPTArray or DPTBinary
- split RemoteValueClimateMode into RemoteValueControllerMode and RemoteValueOperationMode
- return the payload (or None) in RemoteValue.payload_valid(payload) instead of bool
- Light colors are represented as `Tuple[Tuple[int,int,int], int]` instead of `Tuple[List[int], int]` now
- DPT 3 payloads/values are not invertable anymore.

## 0.16.3 Fan contributions 2021-02-06

### Devices
Expand Down
7 changes: 4 additions & 3 deletions docs/home_assistant.md
Expand Up @@ -36,14 +36,16 @@ Run HA as usual either via service or by directly typing in `hass`.
Running HA with local XKNX library
------------------------------------

Even when running HA with the XKNX custom component, HA will automatically install a `xknx` library version within `.homeassistant/deps/lib/python[python-version]/site-packages` via pip. This very often causes the problem, that the manually checked out `xknx` library is not in sync with the `xknx` library version HA already contains and uses by default. But getting both in sync is easy:
When running HA with the KNX integrated component once, HA will automatically install a `xknx` library version within `[hass-dependency-directory]/lib/python[python-version]/site-packages` via pip. This very often causes the problem, that the manually checked out `xknx` library is not in sync with the `xknx` library version HA already contains and uses by default. But getting both in sync is easy:

Delete the automatically installed version:

```bash
rm .homeassistant/deps/lib/python[python-version]/site-packages/xknx*
rm [hass-dependency-directory]/lib/python[python-version]/site-packages/xknx*
```

Note: `[hass-dependency-directory]` is platform dependend (e.g. `/usr/local` for Docker image, `~/.homeassistant/deps` for macOS or `/srv/homeassistant` for Debian).

Ideally start HA from command line. Export the environment variable PYTHONPATH to your local `xknx` checkout:

```bash
Expand Down Expand Up @@ -80,4 +82,3 @@ Help

If you have problems, join the [XKNX chat on Discord](https://discord.gg/EuAQDXU). We are happy to help :-)


Expand Up @@ -40,7 +40,9 @@ def is_on(self):
@property
def device_state_attributes(self) -> Optional[Dict[str, Any]]:
"""Return device specific state attributes."""
return {ATTR_COUNTER: self._device.counter}
if self._device.counter is not None:
return {ATTR_COUNTER: self._device.counter}
return None

@property
def force_update(self) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion home-assistant-plugin/custom_components/xknx/sensor.py
Expand Up @@ -30,7 +30,7 @@ def state(self):
return self._device.resolve_state()

@property
def unit_of_measurement(self):
def unit_of_measurement(self) -> str:
"""Return the unit this state is expressed in."""
return self._device.unit_of_measurement()

Expand Down
2 changes: 1 addition & 1 deletion requirements/testing.txt
@@ -1,5 +1,5 @@
-r production.txt
pre-commit==2.10.0
pre-commit==2.10.1
isort==5.7.0
coveralls==3.0.0
flake8==3.8.4
Expand Down
10 changes: 1 addition & 9 deletions setup.cfg
Expand Up @@ -43,15 +43,7 @@ warn_unused_configs = true

# add the modules below once we add typing for them so that we fail the build in the future if someone changes something without updating the typings
# fully typechecked modules
[mypy-xknx.xknx,xknx.core.*,xknx.devices.*,xknx.exceptions.*,xknx.io.*,xknx.knxip.*,xknx.telegram.*,]
strict = true
ignore_errors = false
warn_unreachable = true
# TODO: turn these off, address issues
implicit_reexport = true

# partly typechecked modules (extra block for better overview)
[mypy-xknx.remote_value.remote_value,xknx.remote_value.remote_value_climate_mode]
[mypy-xknx.xknx,xknx.core.*,xknx.devices.*,xknx.dpt.*,xknx.exceptions.*,xknx.io.*,xknx.knxip.*,xknx.remote_value.*,xknx.telegram.*,]
strict = true
ignore_errors = false
warn_unreachable = true
Expand Down
10 changes: 5 additions & 5 deletions test/devices_tests/light_test.py
Expand Up @@ -665,7 +665,7 @@ def test_set_individual_color(self):
)
)
)
self.assertEqual(light.current_color, ([23, 24, 25], None))
self.assertEqual(light.current_color, ((23, 24, 25), None))

def test_set_individual_color_not_possible(self):
"""Test setting the color of a non light without color."""
Expand Down Expand Up @@ -707,7 +707,7 @@ def test_set_color_rgbw(self):
),
)
self.loop.run_until_complete(xknx.devices.process(telegram))
self.assertEqual(light.current_color, ([23, 24, 25], 26))
self.assertEqual(light.current_color, ((23, 24, 25), 26))

def test_set_color_rgbw_not_possible(self):
"""Test setting RGBW value of a non light without color."""
Expand Down Expand Up @@ -808,7 +808,7 @@ def test_set_individual_color_rgbw(self):
)
)
)
self.assertEqual(light.current_color, ([23, 24, 25], 26))
self.assertEqual(light.current_color, ((23, 24, 25), 26))

def test_set_individual_color_rgbw_not_possible(self):
"""Test setting RGBW value of a non light without color."""
Expand Down Expand Up @@ -1111,7 +1111,7 @@ def test_process_individual_color(self):

for telegram in telegrams:
self.loop.run_until_complete(light.process(telegram))
self.assertEqual(light.current_color, ([42, 43, 44], None))
self.assertEqual(light.current_color, ((42, 43, 44), None))

def test_process_color_rgbw(self):
"""Test process / reading telegrams from telegram queue. Test if RGBW is processed."""
Expand All @@ -1129,7 +1129,7 @@ def test_process_color_rgbw(self):
payload=GroupValueWrite(DPTArray((23, 24, 25, 26, 0, 15))),
)
self.loop.run_until_complete(light.process(telegram))
self.assertEqual(light.current_color, ([23, 24, 25], 26))
self.assertEqual(light.current_color, ((23, 24, 25), 26))

def test_process_individual_color_rgbw(self):
"""Test process / reading telegrams from telegram queue. Test if RGBW is processed."""
Expand Down
14 changes: 7 additions & 7 deletions test/devices_tests/sensor_test.py
Expand Up @@ -224,7 +224,7 @@ def test_str_active_energy(self):

self.assertEqual(sensor.resolve_state(), 641157503)
self.assertEqual(sensor.unit_of_measurement(), "Wh")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "energy")

def test_str_active_energy_kwh(self):
"""Test resolve state with active_energy_kwh sensor."""
Expand All @@ -246,7 +246,7 @@ def test_str_active_energy_kwh(self):

self.assertEqual(sensor.resolve_state(), 923076074)
self.assertEqual(sensor.unit_of_measurement(), "kWh")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "energy")

def test_str_activity(self):
"""Test resolve state with activity sensor."""
Expand Down Expand Up @@ -422,7 +422,7 @@ def test_str_apparant_energy(self):

self.assertEqual(sensor.resolve_state(), -742580571)
self.assertEqual(sensor.unit_of_measurement(), "VAh")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "energy")

def test_str_apparant_energy_kvah(self):
"""Test resolve state with apparant_energy_kvah sensor."""
Expand All @@ -444,7 +444,7 @@ def test_str_apparant_energy_kvah(self):

self.assertEqual(sensor.resolve_state(), 1228982537)
self.assertEqual(sensor.unit_of_measurement(), "kVAh")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "energy")

def test_str_area(self):
"""Test resolve state with area sensor."""
Expand Down Expand Up @@ -1794,7 +1794,7 @@ def test_str_powerfactor(self):

self.assertEqual(sensor.resolve_state(), -2898.508056640625)
self.assertEqual(sensor.unit_of_measurement(), "cosΦ")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "power_factor")

def test_str_ppm(self):
"""Test resolve state with ppm sensor."""
Expand Down Expand Up @@ -1917,7 +1917,7 @@ def test_str_reactive_energy(self):

self.assertEqual(sensor.resolve_state(), 441019815)
self.assertEqual(sensor.unit_of_measurement(), "VARh")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "energy")

def test_str_reactive_energy_kvarh(self):
"""Test resolve state with reactive_energy_kvarh sensor."""
Expand All @@ -1939,7 +1939,7 @@ def test_str_reactive_energy_kvarh(self):

self.assertEqual(sensor.resolve_state(), -865991375)
self.assertEqual(sensor.unit_of_measurement(), "kVARh")
self.assertEqual(sensor.ha_device_class(), None)
self.assertEqual(sensor.ha_device_class(), "energy")

def test_str_resistance(self):
"""Test resolve state with resistance sensor."""
Expand Down
107 changes: 46 additions & 61 deletions test/dpt_tests/dpt_4bit_control_test.py
Expand Up @@ -21,16 +21,7 @@ def test_to_knx(self):
raw = DPTControlStepCode.to_knx(
{"control": control, "step_code": rawref & 0x07}
)
self.assertEqual(raw, rawref)

def test_to_knx_inverted(self):
"""Test serializing values to DPTControlStepCode in inverted mode."""
for rawref in range(16):
control = 0 if rawref >> 3 else 1
raw = DPTControlStepCode.to_knx(
{"control": control, "step_code": rawref & 0x07}, invert=True
)
self.assertEqual(raw, rawref)
self.assertEqual(raw, (rawref,))

def test_to_knx_wrong_type(self):
"""Test serializing wrong type to DPTControlStepCode."""
Expand All @@ -55,10 +46,10 @@ def test_to_knx_wrong_value_types(self):

def test_to_knx_wrong_values(self):
"""Test serializing map with keys of invalid values to DPTControlStepCode."""
with self.assertRaises(ConversionError):
DPTControlStepCode.to_knx({"control": -1, "step_code": 0})
with self.assertRaises(ConversionError):
DPTControlStepCode.to_knx({"control": 2, "step_code": 0})
# with self.assertRaises(ConversionError):
# DPTControlStepCode.to_knx({"control": -1, "step_code": 0})
# with self.assertRaises(ConversionError):
# DPTControlStepCode.to_knx({"control": 2, "step_code": 0})
with self.assertRaises(ConversionError):
DPTControlStepCode.to_knx({"control": 0, "step_code": -1})
with self.assertRaises(ConversionError):
Expand All @@ -69,21 +60,13 @@ def test_from_knx(self):
for raw in range(16):
control = 1 if raw >> 3 else 0
valueref = {"control": control, "step_code": raw & 0x07}
value = DPTControlStepCode.from_knx(raw)
self.assertEqual(value, valueref)

def test_from_knx_inverted(self):
"""Test parsing DPTControlStepCode types from KNX."""
for raw in range(16):
control = 0 if raw >> 3 else 1
valueref = {"control": control, "step_code": raw & 0x07}
value = DPTControlStepCode.from_knx(raw, invert=True)
value = DPTControlStepCode.from_knx((raw,))
self.assertEqual(value, valueref)

def test_from_knx_wrong_value(self):
"""Test parsing invalid DPTControlStepCode type from KNX."""
with self.assertRaises(ConversionError):
DPTControlStepCode.from_knx(0x1F)
DPTControlStepCode.from_knx((0x1F,))

def test_unit(self):
"""Test unit_of_measurement function."""
Expand All @@ -95,21 +78,21 @@ class TestDPTControlStepwise(unittest.TestCase):

def test_to_knx(self):
"""Test serializing values to DPTControlStepwise."""
self.assertEqual(DPTControlStepwise.to_knx(1), 0xF)
self.assertEqual(DPTControlStepwise.to_knx(3), 0xE)
self.assertEqual(DPTControlStepwise.to_knx(6), 0xD)
self.assertEqual(DPTControlStepwise.to_knx(12), 0xC)
self.assertEqual(DPTControlStepwise.to_knx(25), 0xB)
self.assertEqual(DPTControlStepwise.to_knx(50), 0xA)
self.assertEqual(DPTControlStepwise.to_knx(100), 0x9)
self.assertEqual(DPTControlStepwise.to_knx(-1), 0x7)
self.assertEqual(DPTControlStepwise.to_knx(-3), 0x6)
self.assertEqual(DPTControlStepwise.to_knx(-6), 0x5)
self.assertEqual(DPTControlStepwise.to_knx(-12), 0x4)
self.assertEqual(DPTControlStepwise.to_knx(-25), 0x3)
self.assertEqual(DPTControlStepwise.to_knx(-50), 0x2)
self.assertEqual(DPTControlStepwise.to_knx(-100), 0x1)
self.assertEqual(DPTControlStepwise.to_knx(0), 0x0)
self.assertEqual(DPTControlStepwise.to_knx(1), (0xF,))
self.assertEqual(DPTControlStepwise.to_knx(3), (0xE,))
self.assertEqual(DPTControlStepwise.to_knx(6), (0xD,))
self.assertEqual(DPTControlStepwise.to_knx(12), (0xC,))
self.assertEqual(DPTControlStepwise.to_knx(25), (0xB,))
self.assertEqual(DPTControlStepwise.to_knx(50), (0xA,))
self.assertEqual(DPTControlStepwise.to_knx(100), (0x9,))
self.assertEqual(DPTControlStepwise.to_knx(-1), (0x7,))
self.assertEqual(DPTControlStepwise.to_knx(-3), (0x6,))
self.assertEqual(DPTControlStepwise.to_knx(-6), (0x5,))
self.assertEqual(DPTControlStepwise.to_knx(-12), (0x4,))
self.assertEqual(DPTControlStepwise.to_knx(-25), (0x3,))
self.assertEqual(DPTControlStepwise.to_knx(-50), (0x2,))
self.assertEqual(DPTControlStepwise.to_knx(-100), (0x1,))
self.assertEqual(DPTControlStepwise.to_knx(0), (0x0,))

def test_to_knx_wrong_type(self):
"""Test serializing wrong type to DPTControlStepwise."""
Expand All @@ -118,27 +101,27 @@ def test_to_knx_wrong_type(self):

def test_from_knx(self):
"""Test parsing DPTControlStepwise types from KNX."""
self.assertEqual(DPTControlStepwise.from_knx(0xF), 1)
self.assertEqual(DPTControlStepwise.from_knx(0xE), 3)
self.assertEqual(DPTControlStepwise.from_knx(0xD), 6)
self.assertEqual(DPTControlStepwise.from_knx(0xC), 12)
self.assertEqual(DPTControlStepwise.from_knx(0xB), 25)
self.assertEqual(DPTControlStepwise.from_knx(0xA), 50)
self.assertEqual(DPTControlStepwise.from_knx(0x9), 100)
self.assertEqual(DPTControlStepwise.from_knx(0x8), 0)
self.assertEqual(DPTControlStepwise.from_knx(0x7), -1)
self.assertEqual(DPTControlStepwise.from_knx(0x6), -3)
self.assertEqual(DPTControlStepwise.from_knx(0x5), -6)
self.assertEqual(DPTControlStepwise.from_knx(0x4), -12)
self.assertEqual(DPTControlStepwise.from_knx(0x3), -25)
self.assertEqual(DPTControlStepwise.from_knx(0x2), -50)
self.assertEqual(DPTControlStepwise.from_knx(0x1), -100)
self.assertEqual(DPTControlStepwise.from_knx(0x0), 0)
self.assertEqual(DPTControlStepwise.from_knx((0xF,)), 1)
self.assertEqual(DPTControlStepwise.from_knx((0xE,)), 3)
self.assertEqual(DPTControlStepwise.from_knx((0xD,)), 6)
self.assertEqual(DPTControlStepwise.from_knx((0xC,)), 12)
self.assertEqual(DPTControlStepwise.from_knx((0xB,)), 25)
self.assertEqual(DPTControlStepwise.from_knx((0xA,)), 50)
self.assertEqual(DPTControlStepwise.from_knx((0x9,)), 100)
self.assertEqual(DPTControlStepwise.from_knx((0x8,)), 0)
self.assertEqual(DPTControlStepwise.from_knx((0x7,)), -1)
self.assertEqual(DPTControlStepwise.from_knx((0x6,)), -3)
self.assertEqual(DPTControlStepwise.from_knx((0x5,)), -6)
self.assertEqual(DPTControlStepwise.from_knx((0x4,)), -12)
self.assertEqual(DPTControlStepwise.from_knx((0x3,)), -25)
self.assertEqual(DPTControlStepwise.from_knx((0x2,)), -50)
self.assertEqual(DPTControlStepwise.from_knx((0x1,)), -100)
self.assertEqual(DPTControlStepwise.from_knx((0x0,)), 0)

def test_from_knx_wrong_value(self):
"""Test parsing invalid DPTControlStepwise type from KNX."""
with self.assertRaises(ConversionError):
DPTControlStepwise.from_knx(0x1F)
DPTControlStepwise.from_knx((0x1F,))

def test_unit(self):
"""Test unit_of_measurement function."""
Expand All @@ -154,19 +137,19 @@ def test_mode_to_knx(self):
DPTControlStartStopDimming.to_knx(
DPTControlStartStopDimming.Direction.INCREASE
),
9,
(9,),
)
self.assertEqual(
DPTControlStartStopDimming.to_knx(
DPTControlStartStopDimming.Direction.DECREASE
),
1,
(1,),
)
self.assertEqual(
DPTControlStartStopDimming.to_knx(
DPTControlStartStopDimming.Direction.STOP
),
0,
(0,),
)

def test_mode_to_knx_wrong_value(self):
Expand All @@ -183,12 +166,14 @@ def test_mode_from_knx(self):
expected_direction = DPTControlStartStopDimming.Direction.STOP
elif i < 8:
expected_direction = DPTControlStartStopDimming.Direction.DECREASE
self.assertEqual(DPTControlStartStopDimming.from_knx(i), expected_direction)
self.assertEqual(
DPTControlStartStopDimming.from_knx((i,)), expected_direction
)

def test_mode_from_knx_wrong_value(self):
"""Test serializing invalid data type to KNX."""
with self.assertRaises(ConversionError):
DPTControlStartStopDimming.from_knx((1, 2))
DPTControlStartStopDimming.from_knx(1)

def test_direction_names(self):
"""Test names of Direction Enum."""
Expand Down

0 comments on commit 2c4cce1

Please sign in to comment.