Fix KNX services.yaml (#46897)

This commit is contained in:
Matthias Alphart 2021-02-23 22:59:16 +01:00 committed by GitHub
parent d68a51ddce
commit b583ded8b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 31 deletions

View file

@ -17,6 +17,7 @@ from xknx.telegram import AddressFilter, GroupAddress, Telegram
from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite from xknx.telegram.apci import GroupValueRead, GroupValueResponse, GroupValueWrite
from homeassistant.const import ( from homeassistant.const import (
CONF_ADDRESS,
CONF_HOST, CONF_HOST,
CONF_PORT, CONF_PORT,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
@ -64,7 +65,6 @@ CONF_KNX_RATE_LIMIT = "rate_limit"
CONF_KNX_EXPOSE = "expose" CONF_KNX_EXPOSE = "expose"
SERVICE_KNX_SEND = "send" SERVICE_KNX_SEND = "send"
SERVICE_KNX_ATTR_ADDRESS = "address"
SERVICE_KNX_ATTR_PAYLOAD = "payload" SERVICE_KNX_ATTR_PAYLOAD = "payload"
SERVICE_KNX_ATTR_TYPE = "type" SERVICE_KNX_ATTR_TYPE = "type"
SERVICE_KNX_ATTR_REMOVE = "remove" SERVICE_KNX_ATTR_REMOVE = "remove"
@ -146,7 +146,7 @@ CONFIG_SCHEMA = vol.Schema(
SERVICE_KNX_SEND_SCHEMA = vol.Any( SERVICE_KNX_SEND_SCHEMA = vol.Any(
vol.Schema( vol.Schema(
{ {
vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all, vol.Required(SERVICE_KNX_ATTR_PAYLOAD): cv.match_all,
vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str), vol.Required(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str),
} }
@ -154,7 +154,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any(
vol.Schema( vol.Schema(
# without type given payload is treated as raw bytes # without type given payload is treated as raw bytes
{ {
vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any( vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any(
cv.positive_int, [cv.positive_int] cv.positive_int, [cv.positive_int]
), ),
@ -164,7 +164,7 @@ SERVICE_KNX_SEND_SCHEMA = vol.Any(
SERVICE_KNX_READ_SCHEMA = vol.Schema( SERVICE_KNX_READ_SCHEMA = vol.Schema(
{ {
vol.Required(SERVICE_KNX_ATTR_ADDRESS): vol.All( vol.Required(CONF_ADDRESS): vol.All(
cv.ensure_list, cv.ensure_list,
[cv.string], [cv.string],
) )
@ -173,7 +173,7 @@ SERVICE_KNX_READ_SCHEMA = vol.Schema(
SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema( SERVICE_KNX_EVENT_REGISTER_SCHEMA = vol.Schema(
{ {
vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean, vol.Optional(SERVICE_KNX_ATTR_REMOVE, default=False): cv.boolean,
} }
) )
@ -187,7 +187,7 @@ SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any(
vol.Schema( vol.Schema(
# for removing only `address` is required # for removing only `address` is required
{ {
vol.Required(SERVICE_KNX_ATTR_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True), vol.Required(SERVICE_KNX_ATTR_REMOVE): vol.All(cv.boolean, True),
}, },
extra=vol.ALLOW_EXTRA, extra=vol.ALLOW_EXTRA,
@ -198,8 +198,9 @@ SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA = vol.Any(
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the KNX component.""" """Set up the KNX component."""
try: try:
hass.data[DOMAIN] = KNXModule(hass, config) knx_module = KNXModule(hass, config)
await hass.data[DOMAIN].start() hass.data[DOMAIN] = knx_module
await knx_module.start()
except XKNXException as ex: except XKNXException as ex:
_LOGGER.warning("Could not connect to KNX interface: %s", ex) _LOGGER.warning("Could not connect to KNX interface: %s", ex)
hass.components.persistent_notification.async_create( hass.components.persistent_notification.async_create(
@ -208,14 +209,14 @@ async def async_setup(hass, config):
if CONF_KNX_EXPOSE in config[DOMAIN]: if CONF_KNX_EXPOSE in config[DOMAIN]:
for expose_config in config[DOMAIN][CONF_KNX_EXPOSE]: for expose_config in config[DOMAIN][CONF_KNX_EXPOSE]:
hass.data[DOMAIN].exposures.append( knx_module.exposures.append(
create_knx_exposure(hass, hass.data[DOMAIN].xknx, expose_config) create_knx_exposure(hass, knx_module.xknx, expose_config)
) )
for platform in SupportedPlatforms: for platform in SupportedPlatforms:
if platform.value in config[DOMAIN]: if platform.value in config[DOMAIN]:
for device_config in config[DOMAIN][platform.value]: for device_config in config[DOMAIN][platform.value]:
create_knx_device(platform, hass.data[DOMAIN].xknx, device_config) create_knx_device(platform, knx_module.xknx, device_config)
# We need to wait until all entities are loaded into the device list since they could also be created from other platforms # We need to wait until all entities are loaded into the device list since they could also be created from other platforms
for platform in SupportedPlatforms: for platform in SupportedPlatforms:
@ -223,7 +224,7 @@ async def async_setup(hass, config):
discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config)
) )
if not hass.data[DOMAIN].xknx.devices: if not knx_module.xknx.devices:
_LOGGER.warning( _LOGGER.warning(
"No KNX devices are configured. Please read " "No KNX devices are configured. Please read "
"https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes"
@ -232,14 +233,14 @@ async def async_setup(hass, config):
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_KNX_SEND, SERVICE_KNX_SEND,
hass.data[DOMAIN].service_send_to_knx_bus, knx_module.service_send_to_knx_bus,
schema=SERVICE_KNX_SEND_SCHEMA, schema=SERVICE_KNX_SEND_SCHEMA,
) )
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_KNX_READ, SERVICE_KNX_READ,
hass.data[DOMAIN].service_read_to_knx_bus, knx_module.service_read_to_knx_bus,
schema=SERVICE_KNX_READ_SCHEMA, schema=SERVICE_KNX_READ_SCHEMA,
) )
@ -247,7 +248,7 @@ async def async_setup(hass, config):
hass, hass,
DOMAIN, DOMAIN,
SERVICE_KNX_EVENT_REGISTER, SERVICE_KNX_EVENT_REGISTER,
hass.data[DOMAIN].service_event_register_modify, knx_module.service_event_register_modify,
schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA, schema=SERVICE_KNX_EVENT_REGISTER_SCHEMA,
) )
@ -255,7 +256,7 @@ async def async_setup(hass, config):
hass, hass,
DOMAIN, DOMAIN,
SERVICE_KNX_EXPOSURE_REGISTER, SERVICE_KNX_EXPOSURE_REGISTER,
hass.data[DOMAIN].service_exposure_register_modify, knx_module.service_exposure_register_modify,
schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA, schema=SERVICE_KNX_EXPOSURE_REGISTER_SCHEMA,
) )
@ -269,7 +270,7 @@ async def async_setup(hass, config):
if not config or DOMAIN not in config: if not config or DOMAIN not in config:
return return
await hass.data[DOMAIN].xknx.stop() await knx_module.xknx.stop()
await asyncio.gather( await asyncio.gather(
*[platform.async_reset() for platform in async_get_platforms(hass, DOMAIN)] *[platform.async_reset() for platform in async_get_platforms(hass, DOMAIN)]
@ -398,7 +399,7 @@ class KNXModule:
async def service_event_register_modify(self, call): async def service_event_register_modify(self, call):
"""Service for adding or removing a GroupAddress to the knx_event filter.""" """Service for adding or removing a GroupAddress to the knx_event filter."""
group_address = GroupAddress(call.data.get(SERVICE_KNX_ATTR_ADDRESS)) group_address = GroupAddress(call.data[CONF_ADDRESS])
if call.data.get(SERVICE_KNX_ATTR_REMOVE): if call.data.get(SERVICE_KNX_ATTR_REMOVE):
try: try:
self._knx_event_callback.group_addresses.remove(group_address) self._knx_event_callback.group_addresses.remove(group_address)
@ -416,7 +417,7 @@ class KNXModule:
async def service_exposure_register_modify(self, call): async def service_exposure_register_modify(self, call):
"""Service for adding or removing an exposure to KNX bus.""" """Service for adding or removing an exposure to KNX bus."""
group_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS) group_address = call.data.get(CONF_ADDRESS)
if call.data.get(SERVICE_KNX_ATTR_REMOVE): if call.data.get(SERVICE_KNX_ATTR_REMOVE):
try: try:
@ -448,7 +449,7 @@ class KNXModule:
async def service_send_to_knx_bus(self, call): async def service_send_to_knx_bus(self, call):
"""Service for sending an arbitrary KNX message to the KNX bus.""" """Service for sending an arbitrary KNX message to the KNX bus."""
attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD) attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD)
attr_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS) attr_address = call.data.get(CONF_ADDRESS)
attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE) attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE)
def calculate_payload(attr_payload): def calculate_payload(attr_payload):
@ -470,7 +471,7 @@ class KNXModule:
async def service_read_to_knx_bus(self, call): async def service_read_to_knx_bus(self, call):
"""Service for sending a GroupValueRead telegram to the KNX bus.""" """Service for sending a GroupValueRead telegram to the KNX bus."""
for address in call.data.get(SERVICE_KNX_ATTR_ADDRESS): for address in call.data.get(CONF_ADDRESS):
telegram = Telegram( telegram = Telegram(
destination_address=GroupAddress(address), destination_address=GroupAddress(address),
payload=GroupValueRead(), payload=GroupValueRead(),

View file

@ -5,6 +5,7 @@ from xknx import XKNX
from xknx.devices import DateTime, ExposeSensor from xknx.devices import DateTime, ExposeSensor
from homeassistant.const import ( from homeassistant.const import (
CONF_ADDRESS,
CONF_ENTITY_ID, CONF_ENTITY_ID,
STATE_OFF, STATE_OFF,
STATE_ON, STATE_ON,
@ -23,11 +24,11 @@ def create_knx_exposure(
hass: HomeAssistant, xknx: XKNX, config: ConfigType hass: HomeAssistant, xknx: XKNX, config: ConfigType
) -> Union["KNXExposeSensor", "KNXExposeTime"]: ) -> Union["KNXExposeSensor", "KNXExposeTime"]:
"""Create exposures from config.""" """Create exposures from config."""
expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE) address = config[CONF_ADDRESS]
entity_id = config.get(CONF_ENTITY_ID)
attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) attribute = config.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE)
entity_id = config.get(CONF_ENTITY_ID)
expose_type = config.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE)
default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) default = config.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT)
address = config.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS)
exposure: Union["KNXExposeSensor", "KNXExposeTime"] exposure: Union["KNXExposeSensor", "KNXExposeTime"]
if expose_type.lower() in ["time", "date", "datetime"]: if expose_type.lower() in ["time", "date", "datetime"]:
@ -83,6 +84,7 @@ class KNXExposeSensor:
"""Prepare for deletion.""" """Prepare for deletion."""
if self._remove_listener is not None: if self._remove_listener is not None:
self._remove_listener() self._remove_listener()
self._remove_listener = None
if self.device is not None: if self.device is not None:
self.device.shutdown() self.device.shutdown()

View file

@ -327,7 +327,6 @@ class ExposeSchema:
CONF_KNX_EXPOSE_TYPE = CONF_TYPE CONF_KNX_EXPOSE_TYPE = CONF_TYPE
CONF_KNX_EXPOSE_ATTRIBUTE = "attribute" CONF_KNX_EXPOSE_ATTRIBUTE = "attribute"
CONF_KNX_EXPOSE_DEFAULT = "default" CONF_KNX_EXPOSE_DEFAULT = "default"
CONF_KNX_EXPOSE_ADDRESS = CONF_ADDRESS
SCHEMA = vol.Schema( SCHEMA = vol.Schema(
{ {
@ -335,7 +334,7 @@ class ExposeSchema:
vol.Optional(CONF_ENTITY_ID): cv.entity_id, vol.Optional(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string, vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string,
vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all, vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all,
vol.Required(CONF_KNX_EXPOSE_ADDRESS): cv.string, vol.Required(CONF_ADDRESS): cv.string,
} }
) )

View file

@ -1,4 +1,5 @@
send: send:
name: "Send to KNX bus"
description: "Send arbitrary data directly to the KNX bus." description: "Send arbitrary data directly to the KNX bus."
fields: fields:
address: address:
@ -13,6 +14,8 @@ send:
description: "Payload to send to the bus. Integers are treated as DPT 1/2/3 payloads. For DPTs > 6 bits send a list. Each value represents 1 octet (0-255). Pad with 0 to DPT byte length." description: "Payload to send to the bus. Integers are treated as DPT 1/2/3 payloads. For DPTs > 6 bits send a list. Each value represents 1 octet (0-255). Pad with 0 to DPT byte length."
required: true required: true
example: "[0, 4]" example: "[0, 4]"
selector:
object:
type: type:
name: "Value type" name: "Value 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)." 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)."
@ -21,6 +24,7 @@ send:
selector: selector:
text: text:
read: read:
name: "Read from KNX bus"
description: "Send GroupValueRead requests to the KNX bus. Response can be used from `knx_event` and will be processed in KNX entities." description: "Send GroupValueRead requests to the KNX bus. Response can be used from `knx_event` and will be processed in KNX entities."
fields: fields:
address: address:
@ -31,6 +35,7 @@ read:
selector: selector:
text: text:
event_register: event_register:
name: "Register knx_event"
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." 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: fields:
address: address:
@ -38,14 +43,16 @@ event_register:
description: "Group address that shall be added or removed." description: "Group address that shall be added or removed."
required: true required: true
example: "1/1/0" example: "1/1/0"
selector:
text:
remove: remove:
name: "Remove event registration" name: "Remove event registration"
description: "Optional. If `True` the group address will be removed." description: "Optional. If `True` the group address will be removed."
required: false
default: false default: false
selector: selector:
boolean: boolean:
exposure_register: exposure_register:
name: "Expose to KNX bus"
description: "Add or remove exposures to KNX bus. Only exposures added with this service can be removed." description: "Add or remove exposures to KNX bus. Only exposures added with this service can be removed."
fields: fields:
address: address:
@ -72,19 +79,21 @@ exposure_register:
attribute: attribute:
name: "Entity attribute" name: "Entity 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”." 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”."
required: false
example: "brightness" example: "brightness"
selector:
text:
default: default:
name: "Default value" name: "Default value"
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." 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."
required: false
example: "0" example: "0"
selector:
object:
remove: remove:
name: "Remove exposure" name: "Remove exposure"
description: "Optional. If `True` the exposure will be removed. Only `address` is required for removal." description: "Optional. If `True` the exposure will be removed. Only `address` is required for removal."
required: false
default: false default: false
selector: selector:
boolean: boolean:
reload: reload:
description: "Reload KNX configuration." name: "Reload KNX configuration"
description: "Reload the KNX configuration from YAML."