Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modify TelegramQueue.Callback filter from HA service #535

Merged
merged 2 commits into from
Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

### HA integration

- knx_event: renamed `fire_event_filter` to `event_filter` and deprecated `fire_event` config option. A callback is now always registered for HA to be able to modify its `group_addresses` filter from a service.
- knx_event: renamed `address` to `destination` and added `source`, `telegramtype`, `direction` attributes.
- added `knx.event_register` service allowing to add and remove group addresses to trigger knx_event without having to change configuration.

### Internals

Expand All @@ -26,6 +28,7 @@
- Farewell Travis CI; Welcome Github Actions!
- StateUpdater allow float values for `register_remote_value(tracker_options)` attribute.
- Handle exceptions from received unsupported or not implemented KNXIP Service Type identifiers
- TelegramQueue.Callback: add `group_addresses` attribute to store a list of GroupAddress triggering the callback (additionally to `address_filters`).

## 0.15.6 Bugfix for StateUpater 2020-11-26

Expand Down
171 changes: 102 additions & 69 deletions home-assistant-plugin/custom_components/xknx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import voluptuous as vol
from xknx import XKNX
from xknx.core.telegram_queue import TelegramQueue
from xknx.devices import DateTime, ExposeSensor
from xknx.dpt import DPTArray, DPTBase, DPTBinary
from xknx.exceptions import XKNXException
Expand Down Expand Up @@ -59,7 +60,7 @@
CONF_XKNX_ROUTING = "routing"
CONF_XKNX_TUNNELING = "tunneling"
CONF_XKNX_FIRE_EVENT = "fire_event"
CONF_XKNX_FIRE_EVENT_FILTER = "fire_event_filter"
CONF_XKNX_EVENT_FILTER = "event_filter"
CONF_XKNX_INDIVIDUAL_ADDRESS = "individual_address"
CONF_XKNX_MCAST_GRP = "multicast_group"
CONF_XKNX_MCAST_PORT = "multicast_port"
Expand All @@ -71,62 +72,72 @@
SERVICE_XKNX_ATTR_ADDRESS = "address"
SERVICE_XKNX_ATTR_PAYLOAD = "payload"
SERVICE_XKNX_ATTR_TYPE = "type"
SERVICE_XKNX_ATTR_REMOVE = "remove"
SERVICE_XKNX_EVENT_REGISTER = "event_register"

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(CONF_XKNX_CONFIG): cv.string,
vol.Exclusive(
CONF_XKNX_ROUTING, "connection_type"
): ConnectionSchema.ROUTING_SCHEMA,
vol.Exclusive(
CONF_XKNX_TUNNELING, "connection_type"
): ConnectionSchema.TUNNELING_SCHEMA,
vol.Inclusive(CONF_XKNX_FIRE_EVENT, "fire_ev"): cv.boolean,
vol.Inclusive(CONF_XKNX_FIRE_EVENT_FILTER, "fire_ev"): vol.All(
cv.ensure_list, [cv.string]
),
vol.Optional(
CONF_XKNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS
): cv.string,
vol.Optional(CONF_XKNX_MCAST_GRP, default=DEFAULT_MCAST_GRP): cv.string,
vol.Optional(CONF_XKNX_MCAST_PORT, default=DEFAULT_MCAST_PORT): cv.port,
vol.Optional(CONF_XKNX_STATE_UPDATER, default=True): cv.boolean,
vol.Optional(CONF_XKNX_RATE_LIMIT, default=20): vol.All(
vol.Coerce(int), vol.Range(min=1, max=100)
),
vol.Optional(CONF_XKNX_EXPOSE): vol.All(
cv.ensure_list, [ExposeSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.cover.value): vol.All(
cv.ensure_list, [CoverSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.binary_sensor.value): vol.All(
cv.ensure_list, [BinarySensorSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.light.value): vol.All(
cv.ensure_list, [LightSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.climate.value): vol.All(
cv.ensure_list, [ClimateSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.notify.value): vol.All(
cv.ensure_list, [NotifySchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.switch.value): vol.All(
cv.ensure_list, [SwitchSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.sensor.value): vol.All(
cv.ensure_list, [SensorSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.scene.value): vol.All(
cv.ensure_list, [SceneSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.weather.value): vol.All(
cv.ensure_list, [WeatherSchema.SCHEMA]
),
}
DOMAIN: vol.All(
cv.deprecated(CONF_XKNX_FIRE_EVENT, invalidation_version="2021.12"),
cv.deprecated("fire_event_filter", replacement_key=CONF_XKNX_EVENT_FILTER),
vol.Schema(
{
vol.Optional(CONF_XKNX_CONFIG): cv.string,
vol.Exclusive(
CONF_XKNX_ROUTING, "connection_type"
): ConnectionSchema.ROUTING_SCHEMA,
vol.Exclusive(
CONF_XKNX_TUNNELING, "connection_type"
): ConnectionSchema.TUNNELING_SCHEMA,
vol.Optional(CONF_XKNX_FIRE_EVENT): cv.boolean,
vol.Optional(CONF_XKNX_EVENT_FILTER, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
vol.Optional(
CONF_XKNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS
): cv.string,
vol.Optional(
CONF_XKNX_MCAST_GRP, default=DEFAULT_MCAST_GRP
): cv.string,
vol.Optional(
CONF_XKNX_MCAST_PORT, default=DEFAULT_MCAST_PORT
): cv.port,
vol.Optional(CONF_XKNX_STATE_UPDATER, default=True): cv.boolean,
vol.Optional(CONF_XKNX_RATE_LIMIT, default=20): vol.All(
vol.Coerce(int), vol.Range(min=1, max=100)
),
vol.Optional(CONF_XKNX_EXPOSE): vol.All(
cv.ensure_list, [ExposeSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.cover.value): vol.All(
cv.ensure_list, [CoverSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.binary_sensor.value): vol.All(
cv.ensure_list, [BinarySensorSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.light.value): vol.All(
cv.ensure_list, [LightSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.climate.value): vol.All(
cv.ensure_list, [ClimateSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.notify.value): vol.All(
cv.ensure_list, [NotifySchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.switch.value): vol.All(
cv.ensure_list, [SwitchSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.sensor.value): vol.All(
cv.ensure_list, [SensorSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.scene.value): vol.All(
cv.ensure_list, [SceneSchema.SCHEMA]
),
vol.Optional(SupportedPlatforms.weather.value): vol.All(
cv.ensure_list, [WeatherSchema.SCHEMA]
),
}
),
)
},
extra=vol.ALLOW_EXTRA,
Expand All @@ -151,6 +162,13 @@
),
)

SERVICE_XKNX_EVENT_REGISTER_SCHEMA = vol.Schema(
{
vol.Required(SERVICE_XKNX_ATTR_ADDRESS): cv.string,
vol.Optional(SERVICE_XKNX_ATTR_REMOVE, default=False): cv.boolean,
}
)


async def async_setup(hass, config):
"""Set up the KNX component."""
Expand Down Expand Up @@ -188,6 +206,14 @@ async def async_setup(hass, config):
schema=SERVICE_XKNX_SEND_SCHEMA,
)

async_register_admin_service(
hass,
DOMAIN,
SERVICE_XKNX_EVENT_REGISTER,
hass.data[DOMAIN].service_event_register_modify,
schema=SERVICE_XKNX_EVENT_REGISTER_SCHEMA,
)

async def reload_service_handler(service_call: ServiceCallType) -> None:
"""Remove all KNX components and load new ones from config."""

Expand Down Expand Up @@ -221,10 +247,11 @@ def __init__(self, hass, config):
self.hass = hass
self.config = config
self.connected = False
self.init_xknx()
self.register_callbacks()
self.exposures = []

self.init_xknx()
self._knx_event_callback: TelegramQueue.Callback = self.register_callback()

def init_xknx(self):
"""Initialize of KNX object."""
self.xknx = XKNX(
Expand Down Expand Up @@ -289,19 +316,6 @@ def connection_config_tunneling(self):
auto_reconnect=True,
)

def register_callbacks(self):
"""Register callbacks within XKNX object."""
if (
CONF_XKNX_FIRE_EVENT in self.config[DOMAIN]
and self.config[DOMAIN][CONF_XKNX_FIRE_EVENT]
):
address_filters = list(
map(AddressFilter, self.config[DOMAIN][CONF_XKNX_FIRE_EVENT_FILTER])
)
self.xknx.telegram_queue.register_telegram_received_cb(
self.telegram_received_cb, address_filters
)

@callback
def async_create_exposures(self):
"""Create exposures."""
Expand Down Expand Up @@ -349,6 +363,25 @@ async def telegram_received_cb(self, telegram):
},
)

def register_callback(self) -> TelegramQueue.Callback:
"""Register callback within XKNX TelegramQueue."""
address_filters = list(
map(AddressFilter, self.config[DOMAIN][CONF_XKNX_EVENT_FILTER])
)
return self.xknx.telegram_queue.register_telegram_received_cb(
self.telegram_received_cb,
address_filters=address_filters,
group_addresses=[],
)

async def service_event_register_modify(self, call):
"""Service for adding or removing a GroupAddress to the knx_event filter."""
group_address = GroupAddress(call.data.get(SERVICE_XKNX_ATTR_ADDRESS))
if call.data.get(SERVICE_XKNX_ATTR_REMOVE):
self._knx_event_callback.group_addresses.remove(group_address)
elif group_address not in self._knx_event_callback.group_addresses:
self._knx_event_callback.group_addresses.append(group_address)

async def service_send_to_knx_bus(self, call):
"""Service for sending an arbitrary KNX message to the KNX bus."""
attr_payload = call.data.get(SERVICE_XKNX_ATTR_PAYLOAD)
Expand Down
8 changes: 8 additions & 0 deletions home-assistant-plugin/custom_components/xknx/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ send:
type:
description: "Optional. If set, the payload will not be sent as raw bytes, but encoded as given DPT. Knx sensor types are valid values (see https://www.home-assistant.io/integrations/sensor.knx)."
example: "temperature"
event_register:
description: "Add or remove single group address to knx_event filter for triggering `knx_event`s. Only addresses added with this service can be removed."
fields:
address:
description: "Group address that shall be added or removed."
example: "1/1/0"
remove:
description: "Optional. If `True` the group address will be removed. Defaults to `False`."
Loading