Skip to content

Commit

Permalink
Merge 5bd6b98 into a50e930
Browse files Browse the repository at this point in the history
  • Loading branch information
farmio committed Mar 15, 2021
2 parents a50e930 + 5bd6b98 commit 3d5e90b
Show file tree
Hide file tree
Showing 33 changed files with 397 additions and 310 deletions.
5 changes: 3 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

## Unreleased changes

### Internals
### Devices

- Don't allow floats in DPTBase value_type parser
- Accept lists of group addresses using the heads for group_address / group_address_state and the tails for passive_group_addresses in every Device (and RemoteValue)
- Sensor: Don't allow floats in DPTBase value_type parser

## 0.17.2 Value templates 2021-03-10

Expand Down
33 changes: 32 additions & 1 deletion docs/devices.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,44 @@ An instantiated device is automatically added to `xknx.devices`.
* `xknx` is the XKNX object.
* `name` is the name of the object.
* `device_updated_cb` List of awaitable callbacks for each update.
* `group_address*` Group address for a specific function. If a list is passed the first element is used for sending / reading, the rest are passively updating state (listening group address).

* `has_group_address(group_address)` Test if device has given group address.
* `sync(wait_for_result)` Read states of device from KNX bus via GroupValueRead requests.
* `register_device_updated_cb(device_updated_cb)` Register device updated callback.
* `unregister_device_updated_cb(device_updated_cb)` Unregister device updated callback.
* `shutdown()` Remove callbacks and device form Devices vector.

## [](#header-2)Example

```python
>>> light_s = Light(
... xknx,
... name="light with state address",
... group_address_switch="0/2/2",
... group_address_switch_state="0/3/3",
... )
>>> light_s.switch.group_address # this is used to send payloads to the bus
GroupAddress("0/2/2")
>>> light_s.switch.group_address_state # group_address_*_state is used to send GroupValueRead requests to (from `sync()` or StateUpdater)
GroupAddress("0/3/3")
>>> light_s.switch.passive_group_addresses # none configured
[]
>>>
>>> light_p = Light(
... xknx,
... name="light with state and passive addresses",
... group_address_switch=["1/2/2", "4/2/10", "4/2/20"],
... group_address_switch_state=["1/3/3", "4/3/10", "4/3/20"],
... )
>>> light_p.switch.group_address # this is used to send payloads to the bus
GroupAddress("1/2/2")
>>> light_p.switch.group_address_state # group_address_*_state is used for reading state from the bus
GroupAddress("1/3/3")
>>> light_p.switch.passive_group_addresses # these are only listening
[GroupAddress("4/2/10"), GroupAddress("4/2/20"), GroupAddress("4/3/10"), GroupAddress("4/3/20")]
```

## [](#header-2)Device classes

The following pages will give you an overview over the available devices within XKNX.
The following pages will give you an overview over the available devices within XKNX.
2 changes: 1 addition & 1 deletion home-assistant-plugin/custom_components/xknx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
# deprecated since 2021.3
# deprecated since 2021.4
cv.deprecated(CONF_XKNX_CONFIG),
# deprecated since 2021.2
cv.deprecated(CONF_XKNX_FIRE_EVENT),
Expand Down
129 changes: 67 additions & 62 deletions home-assistant-plugin/custom_components/xknx/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
msg="value does not match pattern for KNX group address '<main>/<middle>/<sub>', '<main>/<sub>' or '<free>' (eg.'1/2/3', '9/234', '123')",
)
ga_or_list_validator = vol.Any(ga_validator, [ga_validator])

ia_validator = vol.Any(
cv.matches_regex(IndividualAddress.ADDRESS_RE),
Expand Down Expand Up @@ -97,7 +98,7 @@ class BinarySensorSchema:
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean,
vol.Required(CONF_STATE_ADDRESS): ga_validator,
vol.Required(CONF_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_CONTEXT_TIMEOUT): vol.All(
vol.Coerce(float), vol.Range(min=0, max=10)
),
Expand Down Expand Up @@ -168,27 +169,31 @@ class ClimateSchema:
vol.Optional(
CONF_TEMPERATURE_STEP, default=DEFAULT_TEMPERATURE_STEP
): vol.All(float, vol.Range(min=0, max=2)),
vol.Required(CONF_TEMPERATURE_ADDRESS): ga_validator,
vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): ga_validator,
vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): ga_validator,
vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_OPERATION_MODE_ADDRESS): ga_validator,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): ga_validator,
vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): ga_validator,
vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_HEAT_COOL_ADDRESS): ga_validator,
vol.Optional(CONF_HEAT_COOL_STATE_ADDRESS): ga_validator,
vol.Required(CONF_TEMPERATURE_ADDRESS): ga_or_list_validator,
vol.Required(
CONF_TARGET_TEMPERATURE_STATE_ADDRESS
): ga_or_list_validator,
vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_OPERATION_MODE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): ga_or_list_validator,
vol.Optional(
CONF_CONTROLLER_STATUS_STATE_ADDRESS
): ga_or_list_validator,
vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_HEAT_COOL_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_HEAT_COOL_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS
): ga_validator,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): ga_validator,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): ga_validator,
vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): ga_validator,
vol.Optional(CONF_ON_OFF_ADDRESS): ga_validator,
vol.Optional(CONF_ON_OFF_STATE_ADDRESS): ga_validator,
): ga_or_list_validator,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_ON_OFF_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_ON_OFF_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(
CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT
): cv.boolean,
Expand Down Expand Up @@ -229,13 +234,13 @@ class CoverSchema:
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_MOVE_LONG_ADDRESS): ga_validator,
vol.Optional(CONF_MOVE_SHORT_ADDRESS): ga_validator,
vol.Optional(CONF_STOP_ADDRESS): ga_validator,
vol.Optional(CONF_POSITION_ADDRESS): ga_validator,
vol.Optional(CONF_POSITION_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_ANGLE_ADDRESS): ga_validator,
vol.Optional(CONF_ANGLE_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_MOVE_LONG_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_MOVE_SHORT_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_STOP_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_POSITION_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_POSITION_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_ANGLE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_ANGLE_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(
CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME
): cv.positive_float,
Expand Down Expand Up @@ -280,10 +285,10 @@ class FanSchema:
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(KNX_ADDRESS): ga_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_OSCILLATION_ADDRESS): ga_validator,
vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_validator,
vol.Required(KNX_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_OSCILLATION_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_MAX_STEP): cv.byte,
}
)
Expand Down Expand Up @@ -318,36 +323,36 @@ class LightSchema:

COLOR_SCHEMA = vol.Schema(
{
vol.Optional(KNX_ADDRESS): ga_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_validator,
vol.Required(CONF_BRIGHTNESS_ADDRESS): ga_validator,
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator,
vol.Optional(KNX_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_or_list_validator,
vol.Required(CONF_BRIGHTNESS_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_or_list_validator,
}
)

SCHEMA = vol.All(
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(KNX_ADDRESS): ga_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_BRIGHTNESS_ADDRESS): ga_validator,
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_validator,
vol.Optional(KNX_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_BRIGHTNESS_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): ga_or_list_validator,
vol.Exclusive(CONF_INDIVIDUAL_COLORS, "color"): {
vol.Inclusive(CONF_RED, "colors"): COLOR_SCHEMA,
vol.Inclusive(CONF_GREEN, "colors"): COLOR_SCHEMA,
vol.Inclusive(CONF_BLUE, "colors"): COLOR_SCHEMA,
vol.Optional(CONF_WHITE): COLOR_SCHEMA,
},
vol.Exclusive(CONF_COLOR_ADDRESS, "color"): ga_validator,
vol.Optional(CONF_COLOR_STATE_ADDRESS): ga_validator,
vol.Optional(CONF_COLOR_TEMP_ADDRESS): ga_validator,
vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): ga_validator,
vol.Exclusive(CONF_COLOR_ADDRESS, "color"): ga_or_list_validator,
vol.Optional(CONF_COLOR_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_COLOR_TEMP_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(
CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE
): vol.All(vol.Upper, cv.enum(ColorTempModes)),
vol.Exclusive(CONF_RGBW_ADDRESS, "color"): ga_validator,
vol.Optional(CONF_RGBW_STATE_ADDRESS): ga_validator,
vol.Exclusive(CONF_RGBW_ADDRESS, "color"): ga_or_list_validator,
vol.Optional(CONF_RGBW_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
Expand Down Expand Up @@ -400,7 +405,7 @@ class SceneSchema:
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(KNX_ADDRESS): ga_validator,
vol.Required(KNX_ADDRESS): ga_or_list_validator,
vol.Required(CONF_SCENE_NUMBER): cv.positive_int,
}
)
Expand All @@ -420,7 +425,7 @@ class SensorSchema:
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
vol.Optional(CONF_ALWAYS_CALLBACK, default=False): cv.boolean,
vol.Required(CONF_STATE_ADDRESS): ga_validator,
vol.Required(CONF_STATE_ADDRESS): ga_or_list_validator,
vol.Required(CONF_TYPE): vol.Any(int, float, str),
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}
Expand All @@ -437,8 +442,8 @@ class SwitchSchema:
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(KNX_ADDRESS): ga_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_validator,
vol.Required(KNX_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_STATE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_INVERT): cv.boolean,
}
)
Expand Down Expand Up @@ -470,18 +475,18 @@ class WeatherSchema:
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
vol.Optional(CONF_XKNX_CREATE_SENSORS, default=False): cv.boolean,
vol.Required(CONF_XKNX_TEMPERATURE_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_SOUTH_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_EAST_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_WEST_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_NORTH_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_WIND_SPEED_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_WIND_BEARING_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_RAIN_ALARM_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_FROST_ALARM_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_WIND_ALARM_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_DAY_NIGHT_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_AIR_PRESSURE_ADDRESS): ga_validator,
vol.Optional(CONF_XKNX_HUMIDITY_ADDRESS): ga_validator,
vol.Required(CONF_XKNX_TEMPERATURE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_SOUTH_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_EAST_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_WEST_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_BRIGHTNESS_NORTH_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_WIND_SPEED_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_WIND_BEARING_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_RAIN_ALARM_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_FROST_ALARM_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_WIND_ALARM_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_DAY_NIGHT_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_AIR_PRESSURE_ADDRESS): ga_or_list_validator,
vol.Optional(CONF_XKNX_HUMIDITY_ADDRESS): ga_or_list_validator,
}
)
44 changes: 44 additions & 0 deletions test/devices_tests/switch_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,47 @@ def test_has_group_address(self):
switch = Switch(xknx, "TestOutlet", group_address="1/2/3")
self.assertTrue(switch.has_group_address(GroupAddress("1/2/3")))
self.assertFalse(switch.has_group_address(GroupAddress("2/2/2")))

#
# TEST passive group addresses
#
def test_has_group_address_passive(self):
"""Test has_group_address with passive group address."""
xknx = XKNX()
switch = Switch(xknx, "TestOutlet", group_address=["1/2/3", "4/4/4"])
self.assertTrue(switch.has_group_address(GroupAddress("1/2/3")))
self.assertTrue(switch.has_group_address(GroupAddress("4/4/4")))
self.assertFalse(switch.has_group_address(GroupAddress("2/2/2")))

def test_process_passive(self):
"""Test process / reading telegrams from telegram queue. Test if device was updated."""
xknx = XKNX()
callback_mock = AsyncMock()

switch1 = Switch(
xknx,
"TestOutlet",
group_address=["1/2/3", "4/4/4"],
group_address_state=["1/2/30", "5/5/5"],
device_updated_cb=callback_mock,
)
self.assertEqual(switch1.state, None)
callback_mock.assert_not_called()

telegram_on_passive = Telegram(
destination_address=GroupAddress("4/4/4"),
payload=GroupValueWrite(DPTBinary(1)),
)
telegram_off_passive = Telegram(
destination_address=GroupAddress("5/5/5"),
payload=GroupValueWrite(DPTBinary(0)),
)

self.loop.run_until_complete(switch1.process(telegram_on_passive))
self.assertEqual(switch1.state, True)
callback_mock.assert_called_once()
callback_mock.reset_mock()
self.loop.run_until_complete(switch1.process(telegram_off_passive))
self.assertEqual(switch1.state, False)
callback_mock.assert_called_once()
callback_mock.reset_mock()
Loading

0 comments on commit 3d5e90b

Please sign in to comment.