Centralize knx config and update xknx to 0.12.0 (#39219)

* Refactor KNX integration to centralize configuration yaml (#39189)

* Updates for xknx 0.12.0 (#38880)
This commit is contained in:
Marvin Wichmann 2020-08-26 18:03:03 +02:00 committed by GitHub
parent 79f4b6eb6b
commit a2651845f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 847 additions and 622 deletions

View File

@ -223,7 +223,7 @@ homeassistant/components/keba/* @dannerph
homeassistant/components/keenetic_ndms2/* @foxel
homeassistant/components/kef/* @basnijholt
homeassistant/components/keyboard_remote/* @bendavid
homeassistant/components/knx/* @Julius2342
homeassistant/components/knx/* @Julius2342 @farmio @marvin-w
homeassistant/components/kodi/* @armills @OnFreund
homeassistant/components/konnected/* @heythisisnate @kit-klein
homeassistant/components/lametric/* @robbiet480

View File

@ -3,8 +3,8 @@ import logging
import voluptuous as vol
from xknx import XKNX
from xknx.devices import ActionCallback, DateTime, DateTimeBroadcastType, ExposeSensor
from xknx.dpt import DPTArray, DPTBinary
from xknx.devices import DateTime, ExposeSensor
from xknx.dpt import DPTArray, DPTBase, DPTBinary
from xknx.exceptions import XKNXException
from xknx.io import DEFAULT_MCAST_PORT, ConnectionConfig, ConnectionType
from xknx.telegram import AddressFilter, GroupAddress, Telegram
@ -23,60 +23,61 @@ from homeassistant.core import callback
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.script import Script
from .const import DATA_KNX, DOMAIN, DeviceTypes
from .factory import create_knx_device
from .schema import (
BinarySensorSchema,
ClimateSchema,
ConnectionSchema,
CoverSchema,
ExposeSchema,
LightSchema,
NotifySchema,
SceneSchema,
SensorSchema,
SwitchSchema,
)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "knx"
DATA_KNX = "data_knx"
CONF_KNX_CONFIG = "config_file"
CONF_KNX_ROUTING = "routing"
CONF_KNX_TUNNELING = "tunneling"
CONF_KNX_LOCAL_IP = "local_ip"
CONF_KNX_FIRE_EVENT = "fire_event"
CONF_KNX_FIRE_EVENT_FILTER = "fire_event_filter"
CONF_KNX_STATE_UPDATER = "state_updater"
CONF_KNX_RATE_LIMIT = "rate_limit"
CONF_KNX_EXPOSE = "expose"
CONF_KNX_EXPOSE_TYPE = "type"
CONF_KNX_EXPOSE_ATTRIBUTE = "attribute"
CONF_KNX_EXPOSE_DEFAULT = "default"
CONF_KNX_EXPOSE_ADDRESS = "address"
CONF_KNX_LIGHT = "light"
CONF_KNX_COVER = "cover"
CONF_KNX_BINARY_SENSOR = "binary_sensor"
CONF_KNX_SCENE = "scene"
CONF_KNX_SENSOR = "sensor"
CONF_KNX_SWITCH = "switch"
CONF_KNX_NOTIFY = "notify"
CONF_KNX_CLIMATE = "climate"
SERVICE_KNX_SEND = "send"
SERVICE_KNX_ATTR_ADDRESS = "address"
SERVICE_KNX_ATTR_PAYLOAD = "payload"
SERVICE_KNX_ATTR_TYPE = "type"
ATTR_DISCOVER_DEVICES = "devices"
TUNNELING_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_KNX_LOCAL_IP): cv.string,
vol.Optional(CONF_PORT): cv.port,
}
)
ROUTING_SCHEMA = vol.Schema({vol.Optional(CONF_KNX_LOCAL_IP): cv.string})
EXPOSE_SCHEMA = vol.Schema(
{
vol.Required(CONF_KNX_EXPOSE_TYPE): cv.string,
vol.Optional(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string,
vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all,
vol.Required(CONF_KNX_EXPOSE_ADDRESS): cv.string,
}
)
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(CONF_KNX_CONFIG): cv.string,
vol.Exclusive(CONF_KNX_ROUTING, "connection_type"): ROUTING_SCHEMA,
vol.Exclusive(CONF_KNX_TUNNELING, "connection_type"): TUNNELING_SCHEMA,
vol.Exclusive(
CONF_KNX_ROUTING, "connection_type"
): ConnectionSchema.ROUTING_SCHEMA,
vol.Exclusive(
CONF_KNX_TUNNELING, "connection_type"
): ConnectionSchema.TUNNELING_SCHEMA,
vol.Inclusive(CONF_KNX_FIRE_EVENT, "fire_ev"): cv.boolean,
vol.Inclusive(CONF_KNX_FIRE_EVENT_FILTER, "fire_ev"): vol.All(
cv.ensure_list, [cv.string]
@ -85,7 +86,33 @@ CONFIG_SCHEMA = vol.Schema(
vol.Optional(CONF_KNX_RATE_LIMIT, default=20): vol.All(
vol.Coerce(int), vol.Range(min=1, max=100)
),
vol.Optional(CONF_KNX_EXPOSE): vol.All(cv.ensure_list, [EXPOSE_SCHEMA]),
vol.Optional(CONF_KNX_EXPOSE): vol.All(
cv.ensure_list, [ExposeSchema.SCHEMA]
),
vol.Optional(CONF_KNX_COVER): vol.All(
cv.ensure_list, [CoverSchema.SCHEMA]
),
vol.Optional(CONF_KNX_BINARY_SENSOR): vol.All(
cv.ensure_list, [BinarySensorSchema.SCHEMA]
),
vol.Optional(CONF_KNX_LIGHT): vol.All(
cv.ensure_list, [LightSchema.SCHEMA]
),
vol.Optional(CONF_KNX_CLIMATE): vol.All(
cv.ensure_list, [ClimateSchema.SCHEMA]
),
vol.Optional(CONF_KNX_NOTIFY): vol.All(
cv.ensure_list, [NotifySchema.SCHEMA]
),
vol.Optional(CONF_KNX_SWITCH): vol.All(
cv.ensure_list, [SwitchSchema.SCHEMA]
),
vol.Optional(CONF_KNX_SENSOR): vol.All(
cv.ensure_list, [SensorSchema.SCHEMA]
),
vol.Optional(CONF_KNX_SCENE): vol.All(
cv.ensure_list, [SceneSchema.SCHEMA]
),
}
)
},
@ -98,9 +125,21 @@ SERVICE_KNX_SEND_SCHEMA = vol.Schema(
vol.Required(SERVICE_KNX_ATTR_PAYLOAD): vol.Any(
cv.positive_int, [cv.positive_int]
),
vol.Optional(SERVICE_KNX_ATTR_TYPE): vol.Any(int, float, str),
}
)
KNX_CONFIG_PLATFORM_MAPPING = {
CONF_KNX_COVER: DeviceTypes.cover,
CONF_KNX_SWITCH: DeviceTypes.switch,
CONF_KNX_LIGHT: DeviceTypes.light,
CONF_KNX_SENSOR: DeviceTypes.sensor,
CONF_KNX_NOTIFY: DeviceTypes.notify,
CONF_KNX_SCENE: DeviceTypes.scene,
CONF_KNX_BINARY_SENSOR: DeviceTypes.binary_sensor,
CONF_KNX_CLIMATE: DeviceTypes.climate,
}
async def async_setup(hass, config):
"""Set up the KNX component."""
@ -114,6 +153,15 @@ async def async_setup(hass, config):
f"Can't connect to KNX interface: <br><b>{ex}</b>", title="KNX"
)
for platform_config, device_type in KNX_CONFIG_PLATFORM_MAPPING.items():
if platform_config in config[DOMAIN]:
for device_config in config[DOMAIN][platform_config]:
hass.data[DATA_KNX].xknx.devices.add(
create_knx_device(
hass, device_type, hass.data[DATA_KNX].xknx, device_config
)
)
for component, discovery_type in (
("switch", "Switch"),
("climate", "Climate"),
@ -203,11 +251,15 @@ class KNXModule:
return self.connection_config_tunneling()
if CONF_KNX_ROUTING in self.config[DOMAIN]:
return self.connection_config_routing()
return self.connection_config_auto()
# return None to let xknx use config from xknx.yaml connection block if given
# otherwise it will use default ConnectionConfig (Automatic)
return None
def connection_config_routing(self):
"""Return the connection_config if routing is configured."""
local_ip = self.config[DOMAIN][CONF_KNX_ROUTING].get(CONF_KNX_LOCAL_IP)
local_ip = self.config[DOMAIN][CONF_KNX_ROUTING].get(
ConnectionSchema.CONF_KNX_LOCAL_IP
)
return ConnectionConfig(
connection_type=ConnectionType.ROUTING, local_ip=local_ip
)
@ -216,7 +268,9 @@ class KNXModule:
"""Return the connection_config if tunneling is configured."""
gateway_ip = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_HOST]
gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_PORT)
local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_KNX_LOCAL_IP)
local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get(
ConnectionSchema.CONF_KNX_LOCAL_IP
)
if gateway_port is None:
gateway_port = DEFAULT_MCAST_PORT
return ConnectionConfig(
@ -227,11 +281,6 @@ class KNXModule:
auto_reconnect=True,
)
def connection_config_auto(self):
"""Return the connection_config if auto is configured."""
# pylint: disable=no-self-use
return ConnectionConfig()
def register_callbacks(self):
"""Register callbacks within XKNX object."""
if (
@ -251,11 +300,11 @@ class KNXModule:
if CONF_KNX_EXPOSE not in self.config[DOMAIN]:
return
for to_expose in self.config[DOMAIN][CONF_KNX_EXPOSE]:
expose_type = to_expose.get(CONF_KNX_EXPOSE_TYPE)
expose_type = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_TYPE)
entity_id = to_expose.get(CONF_ENTITY_ID)
attribute = to_expose.get(CONF_KNX_EXPOSE_ATTRIBUTE)
default = to_expose.get(CONF_KNX_EXPOSE_DEFAULT)
address = to_expose.get(CONF_KNX_EXPOSE_ADDRESS)
attribute = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE)
default = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT)
address = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS)
if expose_type in ["time", "date", "datetime"]:
exposure = KNXExposeTime(self.xknx, expose_type, address)
exposure.async_register()
@ -286,9 +335,15 @@ class KNXModule:
"""Service for sending an arbitrary KNX message to the KNX bus."""
attr_payload = call.data.get(SERVICE_KNX_ATTR_PAYLOAD)
attr_address = call.data.get(SERVICE_KNX_ATTR_ADDRESS)
attr_type = call.data.get(SERVICE_KNX_ATTR_TYPE)
def calculate_payload(attr_payload):
"""Calculate payload depending on type of attribute."""
if attr_type is not None:
transcoder = DPTBase.parse_transcoder(attr_type)
if transcoder is None:
raise ValueError(f"Invalid type for knx.send service: {attr_type}")
return DPTArray(transcoder.to_knx(attr_payload))
if isinstance(attr_payload, int):
return DPTBinary(attr_payload)
return DPTArray(attr_payload)
@ -302,22 +357,6 @@ class KNXModule:
await self.xknx.telegrams.put(telegram)
class KNXAutomation:
"""Wrapper around xknx.devices.ActionCallback object.."""
def __init__(self, hass, device, hook, action, counter=1):
"""Initialize Automation class."""
self.hass = hass
self.device = device
script_name = f"{device.get_name()} turn ON script"
self.script = Script(hass, action, script_name, DOMAIN)
self.action = ActionCallback(
hass.data[DATA_KNX].xknx, self.script.async_run, hook=hook, counter=counter
)
device.actions.append(self.action)
class KNXExposeTime:
"""Object to Expose Time/Date object to KNX bus."""
@ -332,7 +371,7 @@ class KNXExposeTime:
def async_register(self):
"""Register listener."""
broadcast_type_string = self.type.upper()
broadcast_type = DateTimeBroadcastType[broadcast_type_string]
broadcast_type = broadcast_type_string
self.device = DateTime(
self.xknx, "Time", broadcast_type=broadcast_type, group_address=self.address
)

View File

@ -1,60 +1,16 @@
"""Support for KNX/IP binary sensors."""
import voluptuous as vol
from xknx.devices import BinarySensor
from xknx.devices import BinarySensor as XknxBinarySensor
from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from . import ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation
CONF_STATE_ADDRESS = "state_address"
CONF_SIGNIFICANT_BIT = "significant_bit"
CONF_DEFAULT_SIGNIFICANT_BIT = 1
CONF_SYNC_STATE = "sync_state"
CONF_AUTOMATION = "automation"
CONF_HOOK = "hook"
CONF_DEFAULT_HOOK = "on"
CONF_COUNTER = "counter"
CONF_DEFAULT_COUNTER = 1
CONF_ACTION = "action"
CONF_RESET_AFTER = "reset_after"
CONF__ACTION = "turn_off_action"
DEFAULT_NAME = "KNX Binary Sensor"
AUTOMATION_SCHEMA = vol.Schema(
{
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
}
)
AUTOMATIONS_SCHEMA = vol.All(cv.ensure_list, [AUTOMATION_SCHEMA])
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(
CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT
): cv.positive_int,
vol.Optional(CONF_SYNC_STATE, default=True): cv.boolean,
vol.Required(CONF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_RESET_AFTER): cv.positive_int,
vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
}
)
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up binary sensor(s) for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -67,48 +23,12 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up binary senor for KNX platform configured within platform."""
name = config[CONF_NAME]
binary_sensor = BinarySensor(
hass.data[DATA_KNX].xknx,
name=name,
group_address_state=config[CONF_STATE_ADDRESS],
sync_state=config[CONF_SYNC_STATE],
device_class=config.get(CONF_DEVICE_CLASS),
significant_bit=config[CONF_SIGNIFICANT_BIT],
reset_after=config.get(CONF_RESET_AFTER),
)
hass.data[DATA_KNX].xknx.devices.add(binary_sensor)
entity = KNXBinarySensor(binary_sensor)
automations = config.get(CONF_AUTOMATION)
if automations is not None:
for automation in automations:
counter = automation[CONF_COUNTER]
hook = automation[CONF_HOOK]
action = automation[CONF_ACTION]
entity.automations.append(
KNXAutomation(
hass=hass,
device=binary_sensor,
hook=hook,
action=action,
counter=counter,
)
)
async_add_entities([entity])
class KNXBinarySensor(BinarySensorEntity):
"""Representation of a KNX binary sensor."""
def __init__(self, device):
def __init__(self, device: XknxBinarySensor):
"""Initialize of KNX binary sensor."""
self.device = device
self.automations = []
@callback
def async_register_callbacks(self):

View File

@ -1,127 +1,31 @@
"""Support for KNX/IP climate devices."""
from typing import List, Optional
import voluptuous as vol
from xknx.devices import Climate as XknxClimate, ClimateMode as XknxClimateMode
from xknx.devices import Climate as XknxClimate
from xknx.dpt import HVACOperationMode
from homeassistant.components.climate import PLATFORM_SCHEMA, ClimateEntity
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_COMFORT,
PRESET_ECO,
PRESET_SLEEP,
SUPPORT_PRESET_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_SETPOINT_SHIFT_ADDRESS = "setpoint_shift_address"
CONF_SETPOINT_SHIFT_STATE_ADDRESS = "setpoint_shift_state_address"
CONF_SETPOINT_SHIFT_STEP = "setpoint_shift_step"
CONF_SETPOINT_SHIFT_MAX = "setpoint_shift_max"
CONF_SETPOINT_SHIFT_MIN = "setpoint_shift_min"
CONF_TEMPERATURE_ADDRESS = "temperature_address"
CONF_TARGET_TEMPERATURE_ADDRESS = "target_temperature_address"
CONF_TARGET_TEMPERATURE_STATE_ADDRESS = "target_temperature_state_address"
CONF_OPERATION_MODE_ADDRESS = "operation_mode_address"
CONF_OPERATION_MODE_STATE_ADDRESS = "operation_mode_state_address"
CONF_CONTROLLER_STATUS_ADDRESS = "controller_status_address"
CONF_CONTROLLER_STATUS_STATE_ADDRESS = "controller_status_state_address"
CONF_CONTROLLER_MODE_ADDRESS = "controller_mode_address"
CONF_CONTROLLER_MODE_STATE_ADDRESS = "controller_mode_state_address"
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = "operation_mode_frost_protection_address"
CONF_OPERATION_MODE_NIGHT_ADDRESS = "operation_mode_night_address"
CONF_OPERATION_MODE_COMFORT_ADDRESS = "operation_mode_comfort_address"
CONF_OPERATION_MODES = "operation_modes"
CONF_ON_OFF_ADDRESS = "on_off_address"
CONF_ON_OFF_STATE_ADDRESS = "on_off_state_address"
CONF_ON_OFF_INVERT = "on_off_invert"
CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp"
DEFAULT_NAME = "KNX Climate"
DEFAULT_SETPOINT_SHIFT_STEP = 0.5
DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEFAULT_ON_OFF_INVERT = False
# Map KNX operation modes to HA modes. This list might not be full.
OPERATION_MODES = {
# Map DPT 201.105 HVAC control modes
"Auto": HVAC_MODE_AUTO,
"Heat": HVAC_MODE_HEAT,
"Cool": HVAC_MODE_COOL,
"Off": HVAC_MODE_OFF,
"Fan only": HVAC_MODE_FAN_ONLY,
"Dry": HVAC_MODE_DRY,
}
from .const import OPERATION_MODES, PRESET_MODES
OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items())
PRESET_MODES = {
# Map DPT 201.100 HVAC operating modes to HA presets
"Frost Protection": PRESET_ECO,
"Night": PRESET_SLEEP,
"Standby": PRESET_AWAY,
"Comfort": PRESET_COMFORT,
}
PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items())
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(
CONF_SETPOINT_SHIFT_STEP, default=DEFAULT_SETPOINT_SHIFT_STEP
): vol.All(float, vol.Range(min=0, max=2)),
vol.Optional(
CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX
): vol.All(int, vol.Range(min=0, max=32)),
vol.Optional(
CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN
): vol.All(int, vol.Range(min=-32, max=0)),
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT): cv.boolean,
vol.Optional(CONF_OPERATION_MODES): vol.All(
cv.ensure_list, [vol.In({**OPERATION_MODES, **PRESET_MODES})]
),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up climate(s) for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -134,68 +38,10 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up climate for KNX platform configured within platform."""
climate_mode = XknxClimateMode(
hass.data[DATA_KNX].xknx,
name=f"{config[CONF_NAME]} Mode",
group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS),
group_address_operation_mode_state=config.get(
CONF_OPERATION_MODE_STATE_ADDRESS
),
group_address_controller_status=config.get(CONF_CONTROLLER_STATUS_ADDRESS),
group_address_controller_status_state=config.get(
CONF_CONTROLLER_STATUS_STATE_ADDRESS
),
group_address_controller_mode=config.get(CONF_CONTROLLER_MODE_ADDRESS),
group_address_controller_mode_state=config.get(
CONF_CONTROLLER_MODE_STATE_ADDRESS
),
group_address_operation_mode_protection=config.get(
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS
),
group_address_operation_mode_night=config.get(
CONF_OPERATION_MODE_NIGHT_ADDRESS
),
group_address_operation_mode_comfort=config.get(
CONF_OPERATION_MODE_COMFORT_ADDRESS
),
operation_modes=config.get(CONF_OPERATION_MODES),
)
hass.data[DATA_KNX].xknx.devices.add(climate_mode)
climate = XknxClimate(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address_temperature=config[CONF_TEMPERATURE_ADDRESS],
group_address_target_temperature=config.get(CONF_TARGET_TEMPERATURE_ADDRESS),
group_address_target_temperature_state=config[
CONF_TARGET_TEMPERATURE_STATE_ADDRESS
],
group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS),
group_address_setpoint_shift_state=config.get(
CONF_SETPOINT_SHIFT_STATE_ADDRESS
),
setpoint_shift_step=config[CONF_SETPOINT_SHIFT_STEP],
setpoint_shift_max=config[CONF_SETPOINT_SHIFT_MAX],
setpoint_shift_min=config[CONF_SETPOINT_SHIFT_MIN],
group_address_on_off=config.get(CONF_ON_OFF_ADDRESS),
group_address_on_off_state=config.get(CONF_ON_OFF_STATE_ADDRESS),
min_temp=config.get(CONF_MIN_TEMP),
max_temp=config.get(CONF_MAX_TEMP),
mode=climate_mode,
on_off_invert=config[CONF_ON_OFF_INVERT],
)
hass.data[DATA_KNX].xknx.devices.add(climate)
async_add_entities([KNXClimate(climate)])
class KNXClimate(ClimateEntity):
"""Representation of a KNX climate device."""
def __init__(self, device):
def __init__(self, device: XknxClimate):
"""Initialize of a KNX climate device."""
self.device = device
self._unit_of_measurement = TEMP_CELSIUS
@ -278,8 +124,6 @@ class KNXClimate(ClimateEntity):
"""Return current operation ie. heat, cool, idle."""
if self.device.supports_on_off and not self.device.is_on:
return HVAC_MODE_OFF
if self.device.supports_on_off and self.device.is_on:
return HVAC_MODE_HEAT
if self.device.mode.supports_operation_mode:
return OPERATION_MODES.get(
self.device.mode.operation_mode.value, HVAC_MODE_HEAT
@ -296,10 +140,11 @@ class KNXClimate(ClimateEntity):
]
if self.device.supports_on_off:
_operations.append(HVAC_MODE_HEAT)
if not _operations:
_operations.append(HVAC_MODE_HEAT)
_operations.append(HVAC_MODE_OFF)
_modes = list(filter(None, _operations))
_modes = list(set(filter(None, _operations)))
# default to ["heat"]
return _modes if _modes else [HVAC_MODE_HEAT]
@ -307,12 +152,15 @@ class KNXClimate(ClimateEntity):
"""Set operation mode."""
if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF:
await self.device.turn_off()
elif self.device.supports_on_off and hvac_mode == HVAC_MODE_HEAT:
await self.device.turn_on()
elif self.device.mode.supports_operation_mode:
knx_operation_mode = HVACOperationMode(OPERATION_MODES_INV.get(hvac_mode))
await self.device.mode.set_operation_mode(knx_operation_mode)
self.async_write_ha_state()
else:
if self.device.supports_on_off and not self.device.is_on:
await self.device.turn_on()
if self.device.mode.supports_operation_mode:
knx_operation_mode = HVACOperationMode(
OPERATION_MODES_INV.get(hvac_mode)
)
await self.device.mode.set_operation_mode(knx_operation_mode)
self.async_write_ha_state()
@property
def preset_mode(self) -> Optional[str]:

View File

@ -0,0 +1,61 @@
"""Constants for the KNX integration."""
from enum import Enum
from homeassistant.components.climate.const import (
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
PRESET_AWAY,
PRESET_COMFORT,
PRESET_ECO,
PRESET_SLEEP,
)
DOMAIN = "knx"
DATA_KNX = "data_knx"
CONF_STATE_ADDRESS = "state_address"
CONF_SYNC_STATE = "sync_state"
class ColorTempModes(Enum):
"""Color temperature modes for config validation."""
absolute = "DPT-7.600"
relative = "DPT-5.001"
class DeviceTypes(Enum):
"""KNX device types."""
cover = "cover"
light = "light"
binary_sensor = "binary_sensor"
climate = "climate"
switch = "switch"
notify = "notify"
scene = "scene"
sensor = "sensor"
# Map KNX operation modes to HA modes. This list might not be complete.
OPERATION_MODES = {
# Map DPT 20.105 HVAC control modes
"Auto": HVAC_MODE_AUTO,
"Heat": HVAC_MODE_HEAT,
"Cool": HVAC_MODE_COOL,
"Off": HVAC_MODE_OFF,
"Fan only": HVAC_MODE_FAN_ONLY,
"Dry": HVAC_MODE_DRY,
}
PRESET_MODES = {
# Map DPT 20.102 HVAC operating modes to HA presets
"Frost Protection": PRESET_ECO,
"Night": PRESET_SLEEP,
"Standby": PRESET_AWAY,
"Comfort": PRESET_COMFORT,
}

View File

@ -1,11 +1,10 @@
"""Support for KNX/IP covers."""
import voluptuous as vol
from xknx.devices import Cover as XknxCover
from homeassistant.components.cover import (
ATTR_POSITION,
ATTR_TILT_POSITION,
PLATFORM_SCHEMA,
DEVICE_CLASS_BLIND,
SUPPORT_CLOSE,
SUPPORT_OPEN,
SUPPORT_SET_POSITION,
@ -13,53 +12,16 @@ from homeassistant.components.cover import (
SUPPORT_STOP,
CoverEntity,
)
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_utc_time_change
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_MOVE_LONG_ADDRESS = "move_long_address"
CONF_MOVE_SHORT_ADDRESS = "move_short_address"
CONF_POSITION_ADDRESS = "position_address"
CONF_POSITION_STATE_ADDRESS = "position_state_address"
CONF_ANGLE_ADDRESS = "angle_address"
CONF_ANGLE_STATE_ADDRESS = "angle_state_address"
CONF_TRAVELLING_TIME_DOWN = "travelling_time_down"
CONF_TRAVELLING_TIME_UP = "travelling_time_up"
CONF_INVERT_POSITION = "invert_position"
CONF_INVERT_ANGLE = "invert_angle"
DEFAULT_TRAVEL_TIME = 25
DEFAULT_NAME = "KNX Cover"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string,
vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string,
vol.Optional(CONF_POSITION_ADDRESS): cv.string,
vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string,
vol.Optional(CONF_ANGLE_ADDRESS): cv.string,
vol.Optional(CONF_ANGLE_STATE_ADDRESS): cv.string,
vol.Optional(
CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME
): cv.positive_int,
vol.Optional(
CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME
): cv.positive_int,
vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean,
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up cover(s) for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -72,32 +34,10 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up cover for KNX platform configured within platform."""
cover = XknxCover(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address_long=config.get(CONF_MOVE_LONG_ADDRESS),
group_address_short=config.get(CONF_MOVE_SHORT_ADDRESS),
group_address_position_state=config.get(CONF_POSITION_STATE_ADDRESS),
group_address_angle=config.get(CONF_ANGLE_ADDRESS),
group_address_angle_state=config.get(CONF_ANGLE_STATE_ADDRESS),
group_address_position=config.get(CONF_POSITION_ADDRESS),
travel_time_down=config[CONF_TRAVELLING_TIME_DOWN],
travel_time_up=config[CONF_TRAVELLING_TIME_UP],
invert_position=config[CONF_INVERT_POSITION],
invert_angle=config[CONF_INVERT_ANGLE],
)
hass.data[DATA_KNX].xknx.devices.add(cover)
async_add_entities([KNXCover(cover)])
class KNXCover(CoverEntity):
"""Representation of a KNX cover."""
def __init__(self, device):
def __init__(self, device: XknxCover):
"""Initialize the cover."""
self.device = device
self._unsubscribe_auto_updater = None
@ -109,6 +49,8 @@ class KNXCover(CoverEntity):
async def after_update_callback(device):
"""Call after device was updated."""
self.async_write_ha_state()
if self.device.is_traveling():
self.start_auto_updater()
self.device.register_device_updated_cb(after_update_callback)
@ -135,44 +77,62 @@ class KNXCover(CoverEntity):
"""No polling needed within KNX."""
return False
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
if self.device.supports_angle:
return DEVICE_CLASS_BLIND
return None
@property
def supported_features(self):
"""Flag supported features."""
supported_features = (
SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP
)
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION
if self.device.supports_stop:
supported_features |= SUPPORT_STOP
if self.device.supports_angle:
supported_features |= SUPPORT_SET_TILT_POSITION
return supported_features
@property
def current_cover_position(self):
"""Return the current position of the cover."""
return self.device.current_position()
"""Return the current position of the cover.
None is unknown, 0 is closed, 100 is fully open.
"""
# In KNX 0 is open, 100 is closed.
try:
return 100 - self.device.current_position()
except TypeError:
return None
@property
def is_closed(self):
"""Return if the cover is closed."""
return self.device.is_closed()
@property
def is_opening(self):
"""Return if the cover is opening or not."""
return self.device.is_opening()
@property
def is_closing(self):
"""Return if the cover is closing or not."""
return self.device.is_closing()
async def async_close_cover(self, **kwargs):
"""Close the cover."""
if not self.device.is_closed():
await self.device.set_down()
self.start_auto_updater()
await self.device.set_down()
async def async_open_cover(self, **kwargs):
"""Open the cover."""
if not self.device.is_open():
await self.device.set_up()
self.start_auto_updater()
await self.device.set_up()
async def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
await self.device.set_position(position)
self.start_auto_updater()
knx_position = 100 - kwargs[ATTR_POSITION]
await self.device.set_position(knx_position)
async def async_stop_cover(self, **kwargs):
"""Stop the cover."""
@ -184,13 +144,15 @@ class KNXCover(CoverEntity):
"""Return current tilt position of cover."""
if not self.device.supports_angle:
return None
return self.device.current_angle()
try:
return 100 - self.device.current_angle()
except TypeError:
return None
async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
if ATTR_TILT_POSITION in kwargs:
tilt_position = kwargs[ATTR_TILT_POSITION]
await self.device.set_angle(tilt_position)
knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION]
await self.device.set_angle(knx_tilt_position)
def start_auto_updater(self):
"""Start the autoupdater to update Home Assistant while cover is moving."""

View File

@ -0,0 +1,263 @@
"""Factory function to initialize KNX devices from config."""
from xknx import XKNX
from xknx.devices import (
ActionCallback as XknxActionCallback,
BinarySensor as XknxBinarySensor,
Climate as XknxClimate,
ClimateMode as XknxClimateMode,
Cover as XknxCover,
Device as XknxDevice,
Light as XknxLight,
Notification as XknxNotification,
Scene as XknxScene,
Sensor as XknxSensor,
Switch as XknxSwitch,
)
from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.script import Script
from homeassistant.helpers.typing import ConfigType
from .const import DATA_KNX, DOMAIN, ColorTempModes, DeviceTypes
from .schema import (
BinarySensorSchema,
ClimateSchema,
CoverSchema,
LightSchema,
SceneSchema,
SensorSchema,
SwitchSchema,
)
def create_knx_device(
hass: HomeAssistant, device_type: DeviceTypes, knx_module: XKNX, config: ConfigType
) -> XknxDevice:
"""Return the requested XKNX device."""
if device_type is DeviceTypes.light:
return _create_light(knx_module, config)
if device_type is DeviceTypes.cover:
return _create_cover(knx_module, config)
if device_type is DeviceTypes.climate:
return _create_climate(hass, knx_module, config)
if device_type is DeviceTypes.switch:
return _create_switch(knx_module, config)
if device_type is DeviceTypes.sensor:
return _create_sensor(knx_module, config)
if device_type is DeviceTypes.notify:
return _create_notify(knx_module, config)
if device_type is DeviceTypes.scene:
return _create_scene(knx_module, config)
if device_type is DeviceTypes.binary_sensor:
return _create_binary_sensor(hass, knx_module, config)
def _create_cover(knx_module: XKNX, config: ConfigType) -> XknxCover:
"""Return a KNX Cover device to be used within XKNX."""
return XknxCover(
knx_module,
name=config[CONF_NAME],
group_address_long=config.get(CoverSchema.CONF_MOVE_LONG_ADDRESS),
group_address_short=config.get(CoverSchema.CONF_MOVE_SHORT_ADDRESS),
group_address_stop=config.get(CoverSchema.CONF_STOP_ADDRESS),
group_address_position_state=config.get(
CoverSchema.CONF_POSITION_STATE_ADDRESS
),
group_address_angle=config.get(CoverSchema.CONF_ANGLE_ADDRESS),
group_address_angle_state=config.get(CoverSchema.CONF_ANGLE_STATE_ADDRESS),
group_address_position=config.get(CoverSchema.CONF_POSITION_ADDRESS),
travel_time_down=config[CoverSchema.CONF_TRAVELLING_TIME_DOWN],
travel_time_up=config[CoverSchema.CONF_TRAVELLING_TIME_UP],
invert_position=config[CoverSchema.CONF_INVERT_POSITION],
invert_angle=config[CoverSchema.CONF_INVERT_ANGLE],
)
def _create_light(knx_module: XKNX, config: ConfigType) -> XknxLight:
"""Return a KNX Light device to be used within XKNX."""
group_address_tunable_white = None
group_address_tunable_white_state = None
group_address_color_temp = None
group_address_color_temp_state = None
if config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.absolute:
group_address_color_temp = config.get(LightSchema.CONF_COLOR_TEMP_ADDRESS)
group_address_color_temp_state = config.get(
LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS
)
elif config[LightSchema.CONF_COLOR_TEMP_MODE] == ColorTempModes.relative:
group_address_tunable_white = config.get(LightSchema.CONF_COLOR_TEMP_ADDRESS)
group_address_tunable_white_state = config.get(
LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS
)
return XknxLight(
knx_module,
name=config[CONF_NAME],
group_address_switch=config[CONF_ADDRESS],
group_address_switch_state=config.get(LightSchema.CONF_STATE_ADDRESS),
group_address_brightness=config.get(LightSchema.CONF_BRIGHTNESS_ADDRESS),
group_address_brightness_state=config.get(
LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS
),
group_address_color=config.get(LightSchema.CONF_COLOR_ADDRESS),
group_address_color_state=config.get(LightSchema.CONF_COLOR_STATE_ADDRESS),
group_address_rgbw=config.get(LightSchema.CONF_RGBW_ADDRESS),
group_address_rgbw_state=config.get(LightSchema.CONF_RGBW_STATE_ADDRESS),
group_address_tunable_white=group_address_tunable_white,
group_address_tunable_white_state=group_address_tunable_white_state,
group_address_color_temperature=group_address_color_temp,
group_address_color_temperature_state=group_address_color_temp_state,
min_kelvin=config[LightSchema.CONF_MIN_KELVIN],
max_kelvin=config[LightSchema.CONF_MAX_KELVIN],
)
def _create_climate(
hass: HomeAssistant, knx_module: XKNX, config: ConfigType
) -> XknxClimate:
"""Return a KNX Climate device to be used within XKNX."""
climate_mode = XknxClimateMode(
knx_module,
name=f"{config[CONF_NAME]} Mode",
group_address_operation_mode=config.get(
ClimateSchema.CONF_OPERATION_MODE_ADDRESS
),
group_address_operation_mode_state=config.get(
ClimateSchema.CONF_OPERATION_MODE_STATE_ADDRESS
),
group_address_controller_status=config.get(
ClimateSchema.CONF_CONTROLLER_STATUS_ADDRESS
),
group_address_controller_status_state=config.get(
ClimateSchema.CONF_CONTROLLER_STATUS_STATE_ADDRESS
),
group_address_controller_mode=config.get(
ClimateSchema.CONF_CONTROLLER_MODE_ADDRESS
),
group_address_controller_mode_state=config.get(
ClimateSchema.CONF_CONTROLLER_MODE_STATE_ADDRESS
),
group_address_operation_mode_protection=config.get(
ClimateSchema.CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS
),
group_address_operation_mode_night=config.get(
ClimateSchema.CONF_OPERATION_MODE_NIGHT_ADDRESS
),
group_address_operation_mode_comfort=config.get(
ClimateSchema.CONF_OPERATION_MODE_COMFORT_ADDRESS
),
group_address_operation_mode_standby=config.get(
ClimateSchema.CONF_OPERATION_MODE_STANDBY_ADDRESS
),
group_address_heat_cool=config.get(ClimateSchema.CONF_HEAT_COOL_ADDRESS),
group_address_heat_cool_state=config.get(
ClimateSchema.CONF_HEAT_COOL_STATE_ADDRESS
),
operation_modes=config.get(ClimateSchema.CONF_OPERATION_MODES),
)
hass.data[DATA_KNX].xknx.devices.add(climate_mode)
return XknxClimate(
knx_module,
name=config[CONF_NAME],
group_address_temperature=config[ClimateSchema.CONF_TEMPERATURE_ADDRESS],
group_address_target_temperature=config.get(
ClimateSchema.CONF_TARGET_TEMPERATURE_ADDRESS
),
group_address_target_temperature_state=config[
ClimateSchema.CONF_TARGET_TEMPERATURE_STATE_ADDRESS
],
group_address_setpoint_shift=config.get(
ClimateSchema.CONF_SETPOINT_SHIFT_ADDRESS
),
group_address_setpoint_shift_state=config.get(
ClimateSchema.CONF_SETPOINT_SHIFT_STATE_ADDRESS
),
setpoint_shift_mode=config[ClimateSchema.CONF_SETPOINT_SHIFT_MODE],
setpoint_shift_max=config[ClimateSchema.CONF_SETPOINT_SHIFT_MAX],
setpoint_shift_min=config[ClimateSchema.CONF_SETPOINT_SHIFT_MIN],
temperature_step=config[ClimateSchema.CONF_TEMPERATURE_STEP],
group_address_on_off=config.get(ClimateSchema.CONF_ON_OFF_ADDRESS),
group_address_on_off_state=config.get(ClimateSchema.CONF_ON_OFF_STATE_ADDRESS),
min_temp=config.get(ClimateSchema.CONF_MIN_TEMP),
max_temp=config.get(ClimateSchema.CONF_MAX_TEMP),
mode=climate_mode,
on_off_invert=config[ClimateSchema.CONF_ON_OFF_INVERT],
)
def _create_switch(knx_module: XKNX, config: ConfigType) -> XknxSwitch:
"""Return a KNX switch to be used within XKNX."""
return XknxSwitch(
knx_module,
name=config[CONF_NAME],
group_address=config[CONF_ADDRESS],
group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS),
)
def _create_sensor(knx_module: XKNX, config: ConfigType) -> XknxSensor:
"""Return a KNX sensor to be used within XKNX."""
return XknxSensor(
knx_module,
name=config[CONF_NAME],
group_address_state=config[SensorSchema.CONF_STATE_ADDRESS],
sync_state=config[SensorSchema.CONF_SYNC_STATE],
value_type=config[CONF_TYPE],
)
def _create_notify(knx_module: XKNX, config: ConfigType) -> XknxNotification:
"""Return a KNX notification to be used within XKNX."""
return XknxNotification(
knx_module, name=config[CONF_NAME], group_address=config[CONF_ADDRESS],
)
def _create_scene(knx_module: XKNX, config: ConfigType) -> XknxScene:
"""Return a KNX scene to be used within XKNX."""
return XknxScene(
knx_module,
name=config[CONF_NAME],
group_address=config[CONF_ADDRESS],
scene_number=config[SceneSchema.CONF_SCENE_NUMBER],
)
def _create_binary_sensor(
hass: HomeAssistant, knx_module: XKNX, config: ConfigType
) -> XknxBinarySensor:
"""Return a KNX binary sensor to be used within XKNX."""
device_name = config[CONF_NAME]
actions = []
automations = config.get(BinarySensorSchema.CONF_AUTOMATION)
if automations is not None:
for automation in automations:
counter = automation[BinarySensorSchema.CONF_COUNTER]
hook = automation[BinarySensorSchema.CONF_HOOK]
action = automation[BinarySensorSchema.CONF_ACTION]
script_name = f"{device_name} turn ON script"
script = Script(hass, action, script_name, DOMAIN)
action = XknxActionCallback(
knx_module, script.async_run, hook=hook, counter=counter
)
actions.append(action)
return XknxBinarySensor(
knx_module,
name=device_name,
group_address_state=config[BinarySensorSchema.CONF_STATE_ADDRESS],
sync_state=config[BinarySensorSchema.CONF_SYNC_STATE],
device_class=config.get(CONF_DEVICE_CLASS),
ignore_internal_state=config[BinarySensorSchema.CONF_IGNORE_INTERNAL_STATE],
reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER),
actions=actions,
)

View File

@ -1,7 +1,4 @@
"""Support for KNX/IP lights."""
from enum import Enum
import voluptuous as vol
from xknx.devices import Light as XknxLight
from homeassistant.components.light import (
@ -9,81 +6,26 @@ from homeassistant.components.light import (
ATTR_COLOR_TEMP,
ATTR_HS_COLOR,
ATTR_WHITE_VALUE,
PLATFORM_SCHEMA,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_WHITE_VALUE,
LightEntity,
)
from homeassistant.const import CONF_ADDRESS, CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_STATE_ADDRESS = "state_address"
CONF_BRIGHTNESS_ADDRESS = "brightness_address"
CONF_BRIGHTNESS_STATE_ADDRESS = "brightness_state_address"
CONF_COLOR_ADDRESS = "color_address"
CONF_COLOR_STATE_ADDRESS = "color_state_address"
CONF_COLOR_TEMP_ADDRESS = "color_temperature_address"
CONF_COLOR_TEMP_STATE_ADDRESS = "color_temperature_state_address"
CONF_COLOR_TEMP_MODE = "color_temperature_mode"
CONF_RGBW_ADDRESS = "rgbw_address"
CONF_RGBW_STATE_ADDRESS = "rgbw_state_address"
CONF_MIN_KELVIN = "min_kelvin"
CONF_MAX_KELVIN = "max_kelvin"
DEFAULT_NAME = "KNX Light"
DEFAULT_COLOR = (0.0, 0.0)
DEFAULT_BRIGHTNESS = 255
DEFAULT_COLOR_TEMP_MODE = "absolute"
DEFAULT_WHITE_VALUE = 255
DEFAULT_MIN_KELVIN = 2700 # 370 mireds
DEFAULT_MAX_KELVIN = 6000 # 166 mireds
class ColorTempModes(Enum):
"""Color temperature modes for config validation."""
absolute = "DPT-7.600"
relative = "DPT-5.001"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_BRIGHTNESS_ADDRESS): cv.string,
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE): cv.enum(
ColorTempModes
),
vol.Optional(CONF_RGBW_ADDRESS): cv.string,
vol.Optional(CONF_RGBW_STATE_ADDRESS): cv.string,
vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up lights for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -96,46 +38,10 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up light for KNX platform configured within platform."""
group_address_tunable_white = None
group_address_tunable_white_state = None
group_address_color_temp = None
group_address_color_temp_state = None
if config[CONF_COLOR_TEMP_MODE] == ColorTempModes.absolute:
group_address_color_temp = config.get(CONF_COLOR_TEMP_ADDRESS)
group_address_color_temp_state = config.get(CONF_COLOR_TEMP_STATE_ADDRESS)
elif config[CONF_COLOR_TEMP_MODE] == ColorTempModes.relative:
group_address_tunable_white = config.get(CONF_COLOR_TEMP_ADDRESS)
group_address_tunable_white_state = config.get(CONF_COLOR_TEMP_STATE_ADDRESS)
light = XknxLight(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address_switch=config[CONF_ADDRESS],
group_address_switch_state=config.get(CONF_STATE_ADDRESS),
group_address_brightness=config.get(CONF_BRIGHTNESS_ADDRESS),
group_address_brightness_state=config.get(CONF_BRIGHTNESS_STATE_ADDRESS),
group_address_color=config.get(CONF_COLOR_ADDRESS),
group_address_color_state=config.get(CONF_COLOR_STATE_ADDRESS),
group_address_rgbw=config.get(CONF_RGBW_ADDRESS),
group_address_rgbw_state=config.get(CONF_RGBW_STATE_ADDRESS),
group_address_tunable_white=group_address_tunable_white,
group_address_tunable_white_state=group_address_tunable_white_state,
group_address_color_temperature=group_address_color_temp,
group_address_color_temperature_state=group_address_color_temp_state,
min_kelvin=config[CONF_MIN_KELVIN],
max_kelvin=config[CONF_MAX_KELVIN],
)
hass.data[DATA_KNX].xknx.devices.add(light)
async_add_entities([KNXLight(light)])
class KNXLight(LightEntity):
"""Representation of a KNX light."""
def __init__(self, device):
def __init__(self, device: XknxLight):
"""Initialize of KNX light."""
self.device = device

View File

@ -2,6 +2,6 @@
"domain": "knx",
"name": "KNX",
"documentation": "https://www.home-assistant.io/integrations/knx",
"requirements": ["xknx==0.11.3"],
"codeowners": ["@Julius2342"]
"requirements": ["xknx==0.12.0"],
"codeowners": ["@Julius2342", "@farmio", "@marvin-w"]
}

View File

@ -1,31 +1,16 @@
"""Support for KNX/IP notification services."""
import voluptuous as vol
from xknx.devices import Notification as XknxNotification
from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService
from homeassistant.const import CONF_ADDRESS, CONF_NAME
from homeassistant.components.notify import BaseNotificationService
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
DEFAULT_NAME = "KNX Notify"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
async def async_get_service(hass, config, discovery_info=None):
"""Get the KNX notification service."""
return (
if discovery_info is not None:
async_get_service_discovery(hass, discovery_info)
if discovery_info is not None
else async_get_service_config(hass, config)
)
@callback
@ -40,22 +25,10 @@ def async_get_service_discovery(hass, discovery_info):
)
@callback
def async_get_service_config(hass, config):
"""Set up notification for KNX platform configured within platform."""
notification = XknxNotification(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address=config[CONF_ADDRESS],
)
hass.data[DATA_KNX].xknx.devices.add(notification)
return KNXNotificationService([notification])
class KNXNotificationService(BaseNotificationService):
"""Implement demo notification service."""
def __init__(self, devices):
def __init__(self, devices: XknxNotification):
"""Initialize the service."""
self.devices = devices

View File

@ -1,35 +1,18 @@
"""Support for KNX scenes."""
from typing import Any
import voluptuous as vol
from xknx.devices import Scene as XknxScene
from homeassistant.components.scene import CONF_PLATFORM, Scene
from homeassistant.const import CONF_ADDRESS, CONF_NAME
from homeassistant.components.scene import Scene
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_SCENE_NUMBER = "scene_number"
DEFAULT_NAME = "KNX SCENE"
PLATFORM_SCHEMA = vol.Schema(
{
vol.Required(CONF_PLATFORM): "knx",
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_ADDRESS): cv.string,
vol.Required(CONF_SCENE_NUMBER): cv.positive_int,
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up the scenes for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -42,23 +25,10 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up scene for KNX platform configured within platform."""
scene = XknxScene(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address=config[CONF_ADDRESS],
scene_number=config[CONF_SCENE_NUMBER],
)
hass.data[DATA_KNX].xknx.devices.add(scene)
async_add_entities([KNXScene(scene)])
class KNXScene(Scene):
"""Representation of a KNX scene."""
def __init__(self, scene):
def __init__(self, scene: XknxScene):
"""Init KNX scene."""
self.scene = scene

View File

@ -0,0 +1,342 @@
"""Voluptuous schemas for the KNX integration."""
import voluptuous as vol
from xknx.devices.climate import SetpointShiftMode
from homeassistant.const import (
CONF_ADDRESS,
CONF_DEVICE_CLASS,
CONF_ENTITY_ID,
CONF_HOST,
CONF_NAME,
CONF_PORT,
CONF_TYPE,
)
import homeassistant.helpers.config_validation as cv
from .const import (
CONF_STATE_ADDRESS,
CONF_SYNC_STATE,
OPERATION_MODES,
PRESET_MODES,
ColorTempModes,
)
class ConnectionSchema:
"""Voluptuous schema for KNX connection."""
CONF_KNX_LOCAL_IP = "local_ip"
TUNNELING_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_KNX_LOCAL_IP): cv.string,
vol.Optional(CONF_PORT): cv.port,
}
)
ROUTING_SCHEMA = vol.Schema({vol.Optional(CONF_KNX_LOCAL_IP): cv.string})
class CoverSchema:
"""Voluptuous schema for KNX covers."""
CONF_MOVE_LONG_ADDRESS = "move_long_address"
CONF_MOVE_SHORT_ADDRESS = "move_short_address"
CONF_STOP_ADDRESS = "stop_address"
CONF_POSITION_ADDRESS = "position_address"
CONF_POSITION_STATE_ADDRESS = "position_state_address"
CONF_ANGLE_ADDRESS = "angle_address"
CONF_ANGLE_STATE_ADDRESS = "angle_state_address"
CONF_TRAVELLING_TIME_DOWN = "travelling_time_down"
CONF_TRAVELLING_TIME_UP = "travelling_time_up"
CONF_INVERT_POSITION = "invert_position"
CONF_INVERT_ANGLE = "invert_angle"
DEFAULT_TRAVEL_TIME = 25
DEFAULT_NAME = "KNX Cover"
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string,
vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string,
vol.Optional(CONF_STOP_ADDRESS): cv.string,
vol.Optional(CONF_POSITION_ADDRESS): cv.string,
vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string,
vol.Optional(CONF_ANGLE_ADDRESS): cv.string,
vol.Optional(CONF_ANGLE_STATE_ADDRESS): cv.string,
vol.Optional(
CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME
): cv.positive_int,
vol.Optional(
CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME
): cv.positive_int,
vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean,
vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean,
}
)
class BinarySensorSchema:
"""Voluptuous schema for KNX binary sensors."""
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
CONF_SYNC_STATE = CONF_SYNC_STATE
CONF_IGNORE_INTERNAL_STATE = "ignore_internal_state"
CONF_AUTOMATION = "automation"
CONF_HOOK = "hook"
CONF_DEFAULT_HOOK = "on"
CONF_COUNTER = "counter"
CONF_DEFAULT_COUNTER = 1
CONF_ACTION = "action"
CONF_RESET_AFTER = "reset_after"
DEFAULT_NAME = "KNX Binary Sensor"
AUTOMATION_SCHEMA = vol.Schema(
{
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA,
}
)
AUTOMATIONS_SCHEMA = vol.All(cv.ensure_list, [AUTOMATION_SCHEMA])
SCHEMA = vol.All(
cv.deprecated("significant_bit"),
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SYNC_STATE, default=True): vol.Any(
vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)),
cv.boolean,
cv.string,
),
vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean,
vol.Required(CONF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_RESET_AFTER): cv.positive_int,
vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
}
),
)
class LightSchema:
"""Voluptuous schema for KNX lights."""
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
CONF_BRIGHTNESS_ADDRESS = "brightness_address"
CONF_BRIGHTNESS_STATE_ADDRESS = "brightness_state_address"
CONF_COLOR_ADDRESS = "color_address"
CONF_COLOR_STATE_ADDRESS = "color_state_address"
CONF_COLOR_TEMP_ADDRESS = "color_temperature_address"
CONF_COLOR_TEMP_STATE_ADDRESS = "color_temperature_state_address"
CONF_COLOR_TEMP_MODE = "color_temperature_mode"
CONF_RGBW_ADDRESS = "rgbw_address"
CONF_RGBW_STATE_ADDRESS = "rgbw_state_address"
CONF_MIN_KELVIN = "min_kelvin"
CONF_MAX_KELVIN = "max_kelvin"
DEFAULT_NAME = "KNX Light"
DEFAULT_COLOR_TEMP_MODE = "absolute"
DEFAULT_MIN_KELVIN = 2700 # 370 mireds
DEFAULT_MAX_KELVIN = 6000 # 166 mireds
SCHEMA = vol.Schema(
{
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_ADDRESS): cv.string,
vol.Optional(CONF_BRIGHTNESS_ADDRESS): cv.string,
vol.Optional(CONF_BRIGHTNESS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_STATE_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_ADDRESS): cv.string,
vol.Optional(CONF_COLOR_TEMP_STATE_ADDRESS): cv.string,
vol.Optional(
CONF_COLOR_TEMP_MODE, default=DEFAULT_COLOR_TEMP_MODE
): cv.enum(ColorTempModes),
vol.Optional(CONF_RGBW_ADDRESS): cv.string,
vol.Optional(CONF_RGBW_STATE_ADDRESS): cv.string,
vol.Optional(CONF_MIN_KELVIN, default=DEFAULT_MIN_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
vol.Coerce(int), vol.Range(min=1)
),
}
)
class ClimateSchema:
"""Voluptuous schema for KNX climate devices."""
CONF_SETPOINT_SHIFT_ADDRESS = "setpoint_shift_address"
CONF_SETPOINT_SHIFT_STATE_ADDRESS = "setpoint_shift_state_address"
CONF_SETPOINT_SHIFT_MODE = "setpoint_shift_mode"
CONF_SETPOINT_SHIFT_MAX = "setpoint_shift_max"
CONF_SETPOINT_SHIFT_MIN = "setpoint_shift_min"
CONF_TEMPERATURE_ADDRESS = "temperature_address"
CONF_TEMPERATURE_STEP = "temperature_step"
CONF_TARGET_TEMPERATURE_ADDRESS = "target_temperature_address"
CONF_TARGET_TEMPERATURE_STATE_ADDRESS = "target_temperature_state_address"
CONF_OPERATION_MODE_ADDRESS = "operation_mode_address"
CONF_OPERATION_MODE_STATE_ADDRESS = "operation_mode_state_address"
CONF_CONTROLLER_STATUS_ADDRESS = "controller_status_address"
CONF_CONTROLLER_STATUS_STATE_ADDRESS = "controller_status_state_address"
CONF_CONTROLLER_MODE_ADDRESS = "controller_mode_address"
CONF_CONTROLLER_MODE_STATE_ADDRESS = "controller_mode_state_address"
CONF_HEAT_COOL_ADDRESS = "heat_cool_address"
CONF_HEAT_COOL_STATE_ADDRESS = "heat_cool_state_address"
CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = (
"operation_mode_frost_protection_address"
)
CONF_OPERATION_MODE_NIGHT_ADDRESS = "operation_mode_night_address"
CONF_OPERATION_MODE_COMFORT_ADDRESS = "operation_mode_comfort_address"
CONF_OPERATION_MODE_STANDBY_ADDRESS = "operation_mode_standby_address"
CONF_OPERATION_MODES = "operation_modes"
CONF_ON_OFF_ADDRESS = "on_off_address"
CONF_ON_OFF_STATE_ADDRESS = "on_off_state_address"
CONF_ON_OFF_INVERT = "on_off_invert"
CONF_MIN_TEMP = "min_temp"
CONF_MAX_TEMP = "max_temp"
DEFAULT_NAME = "KNX Climate"
DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010"
DEFAULT_SETPOINT_SHIFT_MAX = 6
DEFAULT_SETPOINT_SHIFT_MIN = -6
DEFAULT_TEMPERATURE_STEP = 0.1
DEFAULT_ON_OFF_INVERT = False
SCHEMA = vol.All(
cv.deprecated("setpoint_shift_step", replacement_key=CONF_TEMPERATURE_STEP),
vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(
CONF_SETPOINT_SHIFT_MODE, default=DEFAULT_SETPOINT_SHIFT_MODE
): cv.enum(SetpointShiftMode),
vol.Optional(
CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX
): vol.All(int, vol.Range(min=0, max=32)),
vol.Optional(
CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN
): vol.All(int, vol.Range(min=-32, max=0)),
vol.Optional(
CONF_TEMPERATURE_STEP, default=DEFAULT_TEMPERATURE_STEP
): vol.All(float, vol.Range(min=0, max=2)),
vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string,
vol.Required(CONF_TARGET_TEMPERATURE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string,
vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_HEAT_COOL_ADDRESS): cv.string,
vol.Optional(CONF_HEAT_COOL_STATE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STANDBY_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_ADDRESS): cv.string,
vol.Optional(CONF_ON_OFF_STATE_ADDRESS): cv.string,
vol.Optional(
CONF_ON_OFF_INVERT, default=DEFAULT_ON_OFF_INVERT
): cv.boolean,
vol.Optional(CONF_OPERATION_MODES): vol.All(
cv.ensure_list, [vol.In({**OPERATION_MODES, **PRESET_MODES})]
),
vol.Optional(CONF_MIN_TEMP): vol.Coerce(float),
vol.Optional(CONF_MAX_TEMP): vol.Coerce(float),
}
),
)
class SwitchSchema:
"""Voluptuous schema for KNX switches."""
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
DEFAULT_NAME = "KNX Switch"
SCHEMA = vol.Schema(
{
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_ADDRESS): cv.string,
}
)
class ExposeSchema:
"""Voluptuous schema for KNX exposures."""
CONF_KNX_EXPOSE_TYPE = CONF_TYPE
CONF_KNX_EXPOSE_ATTRIBUTE = "attribute"
CONF_KNX_EXPOSE_DEFAULT = "default"
CONF_KNX_EXPOSE_ADDRESS = CONF_ADDRESS
SCHEMA = vol.Schema(
{
vol.Required(CONF_KNX_EXPOSE_TYPE): vol.Any(int, float, str),
vol.Optional(CONF_ENTITY_ID): cv.entity_id,
vol.Optional(CONF_KNX_EXPOSE_ATTRIBUTE): cv.string,
vol.Optional(CONF_KNX_EXPOSE_DEFAULT): cv.match_all,
vol.Required(CONF_KNX_EXPOSE_ADDRESS): cv.string,
}
)
class NotifySchema:
"""Voluptuous schema for KNX notifications."""
DEFAULT_NAME = "KNX Notify"
SCHEMA = vol.Schema(
{
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}
)
class SensorSchema:
"""Voluptuous schema for KNX sensors."""
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
CONF_SYNC_STATE = CONF_SYNC_STATE
DEFAULT_NAME = "KNX Sensor"
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SYNC_STATE, default=True): vol.Any(
vol.All(vol.Coerce(int), vol.Range(min=2, max=1440)),
cv.boolean,
cv.string,
),
vol.Required(CONF_STATE_ADDRESS): cv.string,
vol.Required(CONF_TYPE): vol.Any(int, float, str),
}
)
class SceneSchema:
"""Voluptuous schema for KNX scenes."""
CONF_SCENE_NUMBER = "scene_number"
DEFAULT_NAME = "KNX SCENE"
SCHEMA = vol.Schema(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_ADDRESS): cv.string,
vol.Required(CONF_SCENE_NUMBER): cv.positive_int,
}
)

View File

@ -1,35 +1,16 @@
"""Support for KNX/IP sensors."""
import voluptuous as vol
from xknx.devices import Sensor as XknxSensor
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, CONF_TYPE
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_STATE_ADDRESS = "state_address"
CONF_SYNC_STATE = "sync_state"
DEFAULT_NAME = "KNX Sensor"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SYNC_STATE, default=True): cv.boolean,
vol.Required(CONF_STATE_ADDRESS): cv.string,
vol.Required(CONF_TYPE): cv.string,
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up sensor(s) for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -42,24 +23,10 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up sensor for KNX platform configured within platform."""
sensor = XknxSensor(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address_state=config[CONF_STATE_ADDRESS],
sync_state=config[CONF_SYNC_STATE],
value_type=config[CONF_TYPE],
)
hass.data[DATA_KNX].xknx.devices.add(sensor)
async_add_entities([KNXSensor(sensor)])
class KNXSensor(Entity):
"""Representation of a KNX sensor."""
def __init__(self, device):
def __init__(self, device: XknxSensor):
"""Initialize of a KNX sensor."""
self.device = device

View File

@ -7,3 +7,6 @@ send:
payload:
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."
example: "[0, 4]"
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"

View File

@ -1,32 +1,16 @@
"""Support for KNX/IP switches."""
import voluptuous as vol
from xknx.devices import Switch as XknxSwitch
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity
from homeassistant.const import CONF_ADDRESS, CONF_NAME
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from . import ATTR_DISCOVER_DEVICES, DATA_KNX
CONF_STATE_ADDRESS = "state_address"
DEFAULT_NAME = "KNX Switch"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ADDRESS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_STATE_ADDRESS): cv.string,
}
)
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
"""Set up switch(es) for KNX platform."""
if discovery_info is not None:
async_add_entities_discovery(hass, discovery_info, async_add_entities)
else:
async_add_entities_config(hass, config, async_add_entities)
@callback
@ -39,23 +23,10 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities):
async_add_entities(entities)
@callback
def async_add_entities_config(hass, config, async_add_entities):
"""Set up switch for KNX platform configured within platform."""
switch = XknxSwitch(
hass.data[DATA_KNX].xknx,
name=config[CONF_NAME],
group_address=config[CONF_ADDRESS],
group_address_state=config.get(CONF_STATE_ADDRESS),
)
hass.data[DATA_KNX].xknx.devices.add(switch)
async_add_entities([KNXSwitch(switch)])
class KNXSwitch(SwitchEntity):
"""Representation of a KNX switch."""
def __init__(self, device):
def __init__(self, device: XknxSwitch):
"""Initialize of KNX switch."""
self.device = device

View File

@ -2252,7 +2252,7 @@ xboxapi==2.0.1
xfinity-gateway==0.0.4
# homeassistant.components.knx
xknx==0.11.3
xknx==0.12.0
# homeassistant.components.bluesound
# homeassistant.components.rest