mirror of
https://github.com/home-assistant/core.git
synced 2025-12-23 16:29:33 +00:00
Compare commits
2 Commits
input_bool
...
knx-scene-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db387599a2 | ||
|
|
57facb26bb |
@@ -168,6 +168,7 @@ SUPPORTED_PLATFORMS_UI: Final = {
|
||||
Platform.FAN,
|
||||
Platform.DATETIME,
|
||||
Platform.LIGHT,
|
||||
Platform.SCENE,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.TIME,
|
||||
@@ -227,3 +228,9 @@ class FanConf:
|
||||
"""Common config keys for fan."""
|
||||
|
||||
MAX_STEP: Final = "max_step"
|
||||
|
||||
|
||||
class SceneConf:
|
||||
"""Common config keys for scene."""
|
||||
|
||||
SCENE_NUMBER: Final = "scene_number"
|
||||
|
||||
@@ -10,13 +10,23 @@ from homeassistant import config_entries
|
||||
from homeassistant.components.scene import BaseScene
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
async_get_current_platform,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .const import DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY, SceneConf
|
||||
from .entity import (
|
||||
KnxUiEntity,
|
||||
KnxUiEntityPlatformController,
|
||||
KnxYamlEntity,
|
||||
_KnxEntityBase,
|
||||
)
|
||||
from .knx_module import KNXModule
|
||||
from .schema import SceneSchema
|
||||
from .storage.const import CONF_ENTITY, CONF_GA_SCENE
|
||||
from .storage.util import ConfigExtractor
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -26,18 +36,53 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up scene(s) for KNX platform."""
|
||||
knx_module = hass.data[KNX_MODULE_KEY]
|
||||
config: list[ConfigType] = knx_module.config_yaml[Platform.SCENE]
|
||||
platform = async_get_current_platform()
|
||||
knx_module.config_store.add_platform(
|
||||
platform=Platform.SCENE,
|
||||
controller=KnxUiEntityPlatformController(
|
||||
knx_module=knx_module,
|
||||
entity_platform=platform,
|
||||
entity_class=KnxUiScene,
|
||||
),
|
||||
)
|
||||
|
||||
async_add_entities(KNXScene(knx_module, entity_config) for entity_config in config)
|
||||
entities: list[KnxYamlEntity | KnxUiEntity] = []
|
||||
if yaml_platform_config := knx_module.config_yaml.get(Platform.SCENE):
|
||||
entities.extend(
|
||||
KnxYamlScene(knx_module, entity_config)
|
||||
for entity_config in yaml_platform_config
|
||||
)
|
||||
if ui_config := knx_module.config_store.data["entities"].get(Platform.SCENE):
|
||||
entities.extend(
|
||||
KnxUiScene(knx_module, unique_id, config)
|
||||
for unique_id, config in ui_config.items()
|
||||
)
|
||||
if entities:
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class KNXScene(KnxYamlEntity, BaseScene):
|
||||
class _KnxScene(BaseScene, _KnxEntityBase):
|
||||
"""Representation of a KNX scene."""
|
||||
|
||||
_device: XknxScene
|
||||
|
||||
async def _async_activate(self, **kwargs: Any) -> None:
|
||||
"""Activate the scene."""
|
||||
await self._device.run()
|
||||
|
||||
def after_update_callback(self, device: XknxDevice) -> None:
|
||||
"""Call after device was updated."""
|
||||
self._async_record_activation()
|
||||
super().after_update_callback(device)
|
||||
|
||||
|
||||
class KnxYamlScene(_KnxScene, KnxYamlEntity):
|
||||
"""Representation of a KNX scene configured from YAML."""
|
||||
|
||||
_device: XknxScene
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Init KNX scene."""
|
||||
"""Initialize KNX scene."""
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxScene(
|
||||
@@ -52,11 +97,28 @@ class KNXScene(KnxYamlEntity, BaseScene):
|
||||
f"{self._device.scene_value.group_address}_{self._device.scene_number}"
|
||||
)
|
||||
|
||||
async def _async_activate(self, **kwargs: Any) -> None:
|
||||
"""Activate the scene."""
|
||||
await self._device.run()
|
||||
|
||||
def after_update_callback(self, device: XknxDevice) -> None:
|
||||
"""Call after device was updated."""
|
||||
self._async_record_activation()
|
||||
super().after_update_callback(device)
|
||||
class KnxUiScene(_KnxScene, KnxUiEntity):
|
||||
"""Representation of a KNX scene configured from the UI."""
|
||||
|
||||
_device: XknxScene
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
knx_module: KNXModule,
|
||||
unique_id: str,
|
||||
config: ConfigType,
|
||||
) -> None:
|
||||
"""Initialize KNX scene."""
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
unique_id=unique_id,
|
||||
entity_config=config[CONF_ENTITY],
|
||||
)
|
||||
knx_conf = ConfigExtractor(config[DOMAIN])
|
||||
self._device = XknxScene(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_ENTITY][CONF_NAME],
|
||||
group_address=knx_conf.get_write(CONF_GA_SCENE),
|
||||
scene_number=knx_conf.get(SceneConf.SCENE_NUMBER),
|
||||
)
|
||||
|
||||
@@ -61,6 +61,7 @@ from .const import (
|
||||
CoverConf,
|
||||
FanConf,
|
||||
FanZeroMode,
|
||||
SceneConf,
|
||||
)
|
||||
from .validation import (
|
||||
backwards_compatible_xknx_climate_enum_member,
|
||||
@@ -822,7 +823,7 @@ class SceneSchema(KNXPlatformSchema):
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Required(CONF_SCENE_NUMBER): vol.All(
|
||||
vol.Required(SceneConf.SCENE_NUMBER): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=1, max=64)
|
||||
),
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
|
||||
@@ -72,5 +72,8 @@ CONF_GA_WHITE_SWITCH: Final = "ga_white_switch"
|
||||
CONF_GA_HUE: Final = "ga_hue"
|
||||
CONF_GA_SATURATION: Final = "ga_saturation"
|
||||
|
||||
# Scene
|
||||
CONF_GA_SCENE: Final = "ga_scene"
|
||||
|
||||
# Sensor
|
||||
CONF_ALWAYS_CALLBACK: Final = "always_callback"
|
||||
|
||||
@@ -40,6 +40,7 @@ from ..const import (
|
||||
CoverConf,
|
||||
FanConf,
|
||||
FanZeroMode,
|
||||
SceneConf,
|
||||
)
|
||||
from ..dpt import get_supported_dpts
|
||||
from .const import (
|
||||
@@ -82,6 +83,7 @@ from .const import (
|
||||
CONF_GA_RED_BRIGHTNESS,
|
||||
CONF_GA_RED_SWITCH,
|
||||
CONF_GA_SATURATION,
|
||||
CONF_GA_SCENE,
|
||||
CONF_GA_SENSOR,
|
||||
CONF_GA_SETPOINT_SHIFT,
|
||||
CONF_GA_SPEED,
|
||||
@@ -419,6 +421,25 @@ LIGHT_KNX_SCHEMA = AllSerializeFirst(
|
||||
),
|
||||
)
|
||||
|
||||
SCENE_KNX_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_GA_SCENE): GASelector(
|
||||
state=False,
|
||||
passive=False,
|
||||
write_required=True,
|
||||
valid_dpt=["17.001", "18.001"],
|
||||
),
|
||||
vol.Required(SceneConf.SCENE_NUMBER): AllSerializeFirst(
|
||||
selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=1, max=64, step=1, mode=selector.NumberSelectorMode.BOX
|
||||
)
|
||||
),
|
||||
vol.Coerce(int),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
SWITCH_KNX_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_GA_SWITCH): GASelector(write_required=True, valid_dpt="1"),
|
||||
@@ -694,6 +715,7 @@ KNX_SCHEMA_FOR_PLATFORM = {
|
||||
Platform.DATETIME: DATETIME_KNX_SCHEMA,
|
||||
Platform.FAN: FAN_KNX_SCHEMA,
|
||||
Platform.LIGHT: LIGHT_KNX_SCHEMA,
|
||||
Platform.SCENE: SCENE_KNX_SCHEMA,
|
||||
Platform.SENSOR: SENSOR_KNX_SCHEMA,
|
||||
Platform.SWITCH: SWITCH_KNX_SCHEMA,
|
||||
Platform.TIME: TIME_KNX_SCHEMA,
|
||||
|
||||
@@ -774,6 +774,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"scene": {
|
||||
"description": "A KNX entity can activate a KNX scene and updates when the scene number is received.",
|
||||
"knx": {
|
||||
"ga_scene": {
|
||||
"description": "Group address to activate a scene.",
|
||||
"label": "Scene"
|
||||
},
|
||||
"scene_number": {
|
||||
"description": "The scene number this entity is associated with.",
|
||||
"label": "Scene number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"description": "Read-only entity for numeric or string datapoints. Temperature, percent etc.",
|
||||
"knx": {
|
||||
|
||||
24
tests/components/knx/fixtures/config_store_scene.json
Normal file
24
tests/components/knx/fixtures/config_store_scene.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"version": 2,
|
||||
"minor_version": 2,
|
||||
"key": "knx/config_store.json",
|
||||
"data": {
|
||||
"entities": {
|
||||
"scene": {
|
||||
"knx_es_01KCXJ181N1TEDNC81WXEMXRNS": {
|
||||
"entity": {
|
||||
"name": "test",
|
||||
"device_info": null,
|
||||
"entity_category": null
|
||||
},
|
||||
"knx": {
|
||||
"ga_scene": {
|
||||
"write": "1/1/1"
|
||||
},
|
||||
"scene_number": 12
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1576,6 +1576,50 @@
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_knx_get_schema[scene]
|
||||
dict({
|
||||
'id': 1,
|
||||
'result': list([
|
||||
dict({
|
||||
'name': 'ga_scene',
|
||||
'options': dict({
|
||||
'passive': False,
|
||||
'state': False,
|
||||
'validDPTs': list([
|
||||
dict({
|
||||
'main': 17,
|
||||
'sub': 1,
|
||||
}),
|
||||
dict({
|
||||
'main': 18,
|
||||
'sub': 1,
|
||||
}),
|
||||
]),
|
||||
'write': dict({
|
||||
'required': True,
|
||||
}),
|
||||
}),
|
||||
'required': True,
|
||||
'type': 'knx_group_address',
|
||||
}),
|
||||
dict({
|
||||
'name': 'scene_number',
|
||||
'required': True,
|
||||
'selector': dict({
|
||||
'number': dict({
|
||||
'max': 64.0,
|
||||
'min': 1.0,
|
||||
'mode': 'box',
|
||||
'step': 1.0,
|
||||
}),
|
||||
}),
|
||||
'type': 'ha_selector',
|
||||
}),
|
||||
]),
|
||||
'success': True,
|
||||
'type': 'result',
|
||||
})
|
||||
# ---
|
||||
# name: test_knx_get_schema[sensor]
|
||||
dict({
|
||||
'id': 1,
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
from homeassistant.components.knx.const import KNX_ADDRESS
|
||||
from homeassistant.components.knx.schema import SceneSchema
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, EntityCategory
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
EntityCategory,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import KnxEntityGenerator
|
||||
from .conftest import KNXTestKit
|
||||
|
||||
from tests.common import async_capture_events
|
||||
@@ -56,3 +62,35 @@ async def test_activate_knx_scene(
|
||||
# different scene number - should not be recorded
|
||||
await knx.receive_write("1/1/1", (0x00,))
|
||||
assert len(events) == 4
|
||||
|
||||
|
||||
async def test_scene_ui_create(
|
||||
hass: HomeAssistant,
|
||||
knx: KNXTestKit,
|
||||
create_ui_entity: KnxEntityGenerator,
|
||||
) -> None:
|
||||
"""Test creating a scene."""
|
||||
await knx.setup_integration()
|
||||
await create_ui_entity(
|
||||
platform=Platform.SCENE,
|
||||
entity_data={"name": "test"},
|
||||
knx_data={
|
||||
"ga_scene": {"write": "1/1/1"},
|
||||
"scene_number": 5,
|
||||
},
|
||||
)
|
||||
# activate scene from HA
|
||||
await hass.services.async_call(
|
||||
"scene", "turn_on", {"entity_id": "scene.test"}, blocking=True
|
||||
)
|
||||
await knx.assert_write("1/1/1", (0x04,)) # raw scene number is 0-based
|
||||
|
||||
|
||||
async def test_scene_ui_load(hass: HomeAssistant, knx: KNXTestKit) -> None:
|
||||
"""Test loading a scene from storage."""
|
||||
await knx.setup_integration(config_store_fixture="config_store_scene.json")
|
||||
# activate scene from HA
|
||||
await hass.services.async_call(
|
||||
"scene", "turn_on", {"entity_id": "scene.test"}, blocking=True
|
||||
)
|
||||
await knx.assert_write("1/1/1", (0x0B,))
|
||||
|
||||
Reference in New Issue
Block a user