Skip to content

Commit

Permalink
add exposures from HA service
Browse files Browse the repository at this point in the history
  • Loading branch information
farmio committed Jan 16, 2021
1 parent fec1af7 commit b4339a9
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 2 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Expand Up @@ -2,9 +2,14 @@

## Unreleased changes

### HA integration

- added `knx.exposure_register` service allowing to add and remove ExposeSensor at runtime

### Internals

- remove DPTComparator: DPTBinary and DPTArray are not equal, even if their .value is, and are never equal to `None`.
- add Device.shutdown() method (used eg. when removing ExposeSensor)

## 0.16.0 APCI possibilities considerably increased 2021-01-01

Expand Down
70 changes: 69 additions & 1 deletion home-assistant-plugin/custom_components/xknx/__init__.py
Expand Up @@ -67,6 +67,7 @@
SERVICE_XKNX_ATTR_TYPE = "type"
SERVICE_XKNX_ATTR_REMOVE = "remove"
SERVICE_XKNX_EVENT_REGISTER = "event_register"
SERVICE_XKNX_EXPOSURE_REGISTER = "exposure_register"

CONFIG_SCHEMA = vol.Schema(
{
Expand Down Expand Up @@ -162,6 +163,22 @@
}
)

SERVICE_XKNX_EXPOSURE_REGISTER_SCHEMA = vol.Any(
ExposeSchema.SCHEMA.extend(
{
vol.Optional(SERVICE_XKNX_ATTR_REMOVE, default=False): cv.boolean,
}
),
vol.Schema(
# for removing only `address` is required
{
vol.Required(SERVICE_XKNX_ATTR_ADDRESS): cv.string,
vol.Required(SERVICE_XKNX_ATTR_REMOVE): vol.All(cv.boolean, True),
},
extra=vol.ALLOW_EXTRA,
),
)


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

async_register_admin_service(
hass,
DOMAIN,
SERVICE_XKNX_EXPOSURE_REGISTER,
hass.data[DOMAIN].service_exposure_register_modify,
schema=SERVICE_XKNX_EXPOSURE_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 @@ -246,6 +271,7 @@ def __init__(self, hass, config):
self.config = config
self.connected = False
self.exposures = []
self.service_exposures = {}

self.init_xknx()
self._knx_event_callback: TelegramQueue.Callback = self.register_callback()
Expand Down Expand Up @@ -348,9 +374,51 @@ 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)
try:
self._knx_event_callback.group_addresses.remove(group_address)
except ValueError:
_LOGGER.warning(
"Service event_register could not remove event for '%s'",
group_address,
)
elif group_address not in self._knx_event_callback.group_addresses:
self._knx_event_callback.group_addresses.append(group_address)
_LOGGER.debug(
"Service event_register registered event for '%s'",
group_address,
)

async def service_exposure_register_modify(self, call):
"""Service for adding or removing an exposure to KNX bus."""
group_address = call.data.get(SERVICE_XKNX_ATTR_ADDRESS)

if call.data.get(SERVICE_XKNX_ATTR_REMOVE):
try:
removed_exposure = self.service_exposures.pop(group_address)
except KeyError:
_LOGGER.warning(
"Service exposure_register could not remove exposure for '%s'",
group_address,
)
else:
removed_exposure.shutdown()
return

if group_address in self.service_exposures:
replaced_exposure = self.service_exposures.pop(group_address)
_LOGGER.warning(
"Service exposure_register replacing already registered exposure for '%s' - %s",
group_address,
replaced_exposure.device.name,
)
replaced_exposure.shutdown()
exposure = create_knx_exposure(self.hass, self.xknx, call.data)
self.service_exposures[group_address] = exposure
_LOGGER.debug(
"Service exposure_register registered exposure for '%s' - %s",
group_address,
exposure.device.name,
)

async def service_send_to_knx_bus(self, call):
"""Service for sending an arbitrary KNX message to the KNX bus."""
Expand Down
15 changes: 14 additions & 1 deletion home-assistant-plugin/custom_components/xknx/expose.py
Expand Up @@ -57,6 +57,7 @@ def __init__(self, hass, xknx, expose_type, entity_id, attribute, default, addre
self.expose_default = default
self.address = address
self.device = None
self._remove_listener = None

@callback
def async_register(self):
Expand All @@ -71,10 +72,17 @@ def async_register(self):
group_address=self.address,
value_type=self.type,
)
async_track_state_change_event(
self._remove_listener = async_track_state_change_event(
self.hass, [self.entity_id], self._async_entity_changed
)

def shutdown(self) -> None:
"""Prepare for deletion."""
if self._remove_listener is not None:
self._remove_listener()
if self.device is not None:
self.device.shutdown()

async def _async_entity_changed(self, event):
"""Handle entity change."""
new_state = event.data.get("new_state")
Expand Down Expand Up @@ -132,3 +140,8 @@ def async_register(self):
localtime=True,
group_address=self.address,
)

def shutdown(self):
"""Prepare for deletion."""
if self.device is not None:
self.device.shutdown()
22 changes: 22 additions & 0 deletions home-assistant-plugin/custom_components/xknx/services.yaml
Expand Up @@ -18,3 +18,25 @@ event_register:
example: "1/1/0"
remove:
description: "Optional. If `True` the group address will be removed. Defaults to `False`."
exposure_register:
description: "Add or remove exposures to KNX bus. Only exposures added with this service can be removed."
fields:
address:
description: "Required. Group address state or attribute updates will be sent to. GroupValueRead requests will be answered. Per address only one exposure can be registered."
example: "1/1/0"
type:
description: "Required. Telegrams will be encoded as given DPT. 'binary' and all Knx sensor types are valid values (see https://www.home-assistant.io/integrations/sensor.knx)"
example: "percentU8"
entity_id:
description: "Required. Entity id to be exposed."
example: "light.kitchen"
required: true
attribute:
description: "Optional. Attribute of the entity that shall be sent to the KNX bus. If not set the state will be sent. Eg. for a light the state is eigther “on” or “off” - with attribute you can expose its “brightness”."
example: "brightness"
required: false
default:
description: "Optional. Default value to send to the bus if the state or attribute value is None. Eg. a light with state “off” has no brightness attribute so a default value of 0 could be used. If not set (or None) no value would be sent to the bus and a GroupReadRequest to the address would return the last known value."
example: "0"
remove:
description: "Optional. If `True` the exposure will be removed. Only `address` is required for removal."

0 comments on commit b4339a9

Please sign in to comment.