Support in service descriptions for input sections (#116100)
This commit is contained in:
parent
c4b277b6ab
commit
75c7ae7c69
6 changed files with 198 additions and 95 deletions
|
@ -199,45 +199,6 @@ turn_on:
|
||||||
example: "[255, 100, 100]"
|
example: "[255, 100, 100]"
|
||||||
selector:
|
selector:
|
||||||
color_rgb:
|
color_rgb:
|
||||||
rgbw_color: &rgbw_color
|
|
||||||
filter: *color_support
|
|
||||||
advanced: true
|
|
||||||
example: "[255, 100, 100, 50]"
|
|
||||||
selector:
|
|
||||||
object:
|
|
||||||
rgbww_color: &rgbww_color
|
|
||||||
filter: *color_support
|
|
||||||
advanced: true
|
|
||||||
example: "[255, 100, 100, 50, 70]"
|
|
||||||
selector:
|
|
||||||
object:
|
|
||||||
color_name: &color_name
|
|
||||||
filter: *color_support
|
|
||||||
advanced: true
|
|
||||||
selector:
|
|
||||||
select:
|
|
||||||
translation_key: color_name
|
|
||||||
options: *named_colors
|
|
||||||
hs_color: &hs_color
|
|
||||||
filter: *color_support
|
|
||||||
advanced: true
|
|
||||||
example: "[300, 70]"
|
|
||||||
selector:
|
|
||||||
object:
|
|
||||||
xy_color: &xy_color
|
|
||||||
filter: *color_support
|
|
||||||
advanced: true
|
|
||||||
example: "[0.52, 0.43]"
|
|
||||||
selector:
|
|
||||||
object:
|
|
||||||
color_temp: &color_temp
|
|
||||||
filter: *color_temp_support
|
|
||||||
advanced: true
|
|
||||||
selector:
|
|
||||||
color_temp:
|
|
||||||
unit: "mired"
|
|
||||||
min: 153
|
|
||||||
max: 500
|
|
||||||
kelvin: &kelvin
|
kelvin: &kelvin
|
||||||
filter: *color_temp_support
|
filter: *color_temp_support
|
||||||
selector:
|
selector:
|
||||||
|
@ -245,13 +206,6 @@ turn_on:
|
||||||
unit: "kelvin"
|
unit: "kelvin"
|
||||||
min: 2000
|
min: 2000
|
||||||
max: 6500
|
max: 6500
|
||||||
brightness: &brightness
|
|
||||||
filter: *brightness_support
|
|
||||||
advanced: true
|
|
||||||
selector:
|
|
||||||
number:
|
|
||||||
min: 0
|
|
||||||
max: 255
|
|
||||||
brightness_pct: &brightness_pct
|
brightness_pct: &brightness_pct
|
||||||
filter: *brightness_support
|
filter: *brightness_support
|
||||||
selector:
|
selector:
|
||||||
|
@ -259,13 +213,6 @@ turn_on:
|
||||||
min: 0
|
min: 0
|
||||||
max: 100
|
max: 100
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
brightness_step:
|
|
||||||
filter: *brightness_support
|
|
||||||
advanced: true
|
|
||||||
selector:
|
|
||||||
number:
|
|
||||||
min: -225
|
|
||||||
max: 255
|
|
||||||
brightness_step_pct:
|
brightness_step_pct:
|
||||||
filter: *brightness_support
|
filter: *brightness_support
|
||||||
selector:
|
selector:
|
||||||
|
@ -273,39 +220,84 @@ turn_on:
|
||||||
min: -100
|
min: -100
|
||||||
max: 100
|
max: 100
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
white: &white
|
|
||||||
filter:
|
|
||||||
attribute:
|
|
||||||
supported_color_modes:
|
|
||||||
- light.ColorMode.WHITE
|
|
||||||
advanced: true
|
|
||||||
selector:
|
|
||||||
constant:
|
|
||||||
value: true
|
|
||||||
label: Enabled
|
|
||||||
profile: &profile
|
|
||||||
advanced: true
|
|
||||||
example: relax
|
|
||||||
selector:
|
|
||||||
text:
|
|
||||||
flash: &flash
|
|
||||||
filter:
|
|
||||||
supported_features:
|
|
||||||
- light.LightEntityFeature.FLASH
|
|
||||||
advanced: true
|
|
||||||
selector:
|
|
||||||
select:
|
|
||||||
options:
|
|
||||||
- label: "Long"
|
|
||||||
value: "long"
|
|
||||||
- label: "Short"
|
|
||||||
value: "short"
|
|
||||||
effect: &effect
|
effect: &effect
|
||||||
filter:
|
filter:
|
||||||
supported_features:
|
supported_features:
|
||||||
- light.LightEntityFeature.EFFECT
|
- light.LightEntityFeature.EFFECT
|
||||||
selector:
|
selector:
|
||||||
text:
|
text:
|
||||||
|
advanced_fields:
|
||||||
|
collapsed: true
|
||||||
|
fields:
|
||||||
|
rgbw_color: &rgbw_color
|
||||||
|
filter: *color_support
|
||||||
|
example: "[255, 100, 100, 50]"
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
rgbww_color: &rgbww_color
|
||||||
|
filter: *color_support
|
||||||
|
example: "[255, 100, 100, 50, 70]"
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
color_name: &color_name
|
||||||
|
filter: *color_support
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
translation_key: color_name
|
||||||
|
options: *named_colors
|
||||||
|
hs_color: &hs_color
|
||||||
|
filter: *color_support
|
||||||
|
example: "[300, 70]"
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
xy_color: &xy_color
|
||||||
|
filter: *color_support
|
||||||
|
example: "[0.52, 0.43]"
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
color_temp: &color_temp
|
||||||
|
filter: *color_temp_support
|
||||||
|
selector:
|
||||||
|
color_temp:
|
||||||
|
unit: "mired"
|
||||||
|
min: 153
|
||||||
|
max: 500
|
||||||
|
brightness: &brightness
|
||||||
|
filter: *brightness_support
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
max: 255
|
||||||
|
brightness_step:
|
||||||
|
filter: *brightness_support
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: -225
|
||||||
|
max: 255
|
||||||
|
white: &white
|
||||||
|
filter:
|
||||||
|
attribute:
|
||||||
|
supported_color_modes:
|
||||||
|
- light.ColorMode.WHITE
|
||||||
|
selector:
|
||||||
|
constant:
|
||||||
|
value: true
|
||||||
|
label: Enabled
|
||||||
|
profile: &profile
|
||||||
|
example: relax
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
flash: &flash
|
||||||
|
filter:
|
||||||
|
supported_features:
|
||||||
|
- light.LightEntityFeature.FLASH
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- label: "Long"
|
||||||
|
value: "long"
|
||||||
|
- label: "Short"
|
||||||
|
value: "short"
|
||||||
|
|
||||||
turn_off:
|
turn_off:
|
||||||
target:
|
target:
|
||||||
|
@ -313,7 +305,10 @@ turn_off:
|
||||||
domain: light
|
domain: light
|
||||||
fields:
|
fields:
|
||||||
transition: *transition
|
transition: *transition
|
||||||
flash: *flash
|
advanced_fields:
|
||||||
|
collapsed: true
|
||||||
|
fields:
|
||||||
|
flash: *flash
|
||||||
|
|
||||||
toggle:
|
toggle:
|
||||||
target:
|
target:
|
||||||
|
@ -322,16 +317,19 @@ toggle:
|
||||||
fields:
|
fields:
|
||||||
transition: *transition
|
transition: *transition
|
||||||
rgb_color: *rgb_color
|
rgb_color: *rgb_color
|
||||||
rgbw_color: *rgbw_color
|
|
||||||
rgbww_color: *rgbww_color
|
|
||||||
color_name: *color_name
|
|
||||||
hs_color: *hs_color
|
|
||||||
xy_color: *xy_color
|
|
||||||
color_temp: *color_temp
|
|
||||||
kelvin: *kelvin
|
kelvin: *kelvin
|
||||||
brightness: *brightness
|
|
||||||
brightness_pct: *brightness_pct
|
brightness_pct: *brightness_pct
|
||||||
white: *white
|
|
||||||
profile: *profile
|
|
||||||
flash: *flash
|
|
||||||
effect: *effect
|
effect: *effect
|
||||||
|
advanced_fields:
|
||||||
|
collapsed: true
|
||||||
|
fields:
|
||||||
|
rgbw_color: *rgbw_color
|
||||||
|
rgbww_color: *rgbww_color
|
||||||
|
color_name: *color_name
|
||||||
|
hs_color: *hs_color
|
||||||
|
xy_color: *xy_color
|
||||||
|
color_temp: *color_temp
|
||||||
|
brightness: *brightness
|
||||||
|
white: *white
|
||||||
|
profile: *profile
|
||||||
|
flash: *flash
|
||||||
|
|
|
@ -34,7 +34,8 @@
|
||||||
"field_white_description": "Set the light to white mode.",
|
"field_white_description": "Set the light to white mode.",
|
||||||
"field_white_name": "White",
|
"field_white_name": "White",
|
||||||
"field_xy_color_description": "Color in XY-format. A list of two decimal numbers between 0 and 1.",
|
"field_xy_color_description": "Color in XY-format. A list of two decimal numbers between 0 and 1.",
|
||||||
"field_xy_color_name": "XY-color"
|
"field_xy_color_name": "XY-color",
|
||||||
|
"section_advanced_fields_name": "Advanced options"
|
||||||
},
|
},
|
||||||
"device_automation": {
|
"device_automation": {
|
||||||
"action_type": {
|
"action_type": {
|
||||||
|
@ -354,6 +355,11 @@
|
||||||
"name": "[%key:component::light::common::field_effect_name%]",
|
"name": "[%key:component::light::common::field_effect_name%]",
|
||||||
"description": "[%key:component::light::common::field_effect_description%]"
|
"description": "[%key:component::light::common::field_effect_description%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"advanced_fields": {
|
||||||
|
"name": "[%key:component::light::common::section_advanced_fields_name%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"turn_off": {
|
"turn_off": {
|
||||||
|
@ -368,6 +374,11 @@
|
||||||
"name": "[%key:component::light::common::field_flash_name%]",
|
"name": "[%key:component::light::common::field_flash_name%]",
|
||||||
"description": "[%key:component::light::common::field_flash_description%]"
|
"description": "[%key:component::light::common::field_flash_description%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"advanced_fields": {
|
||||||
|
"name": "[%key:component::light::common::section_advanced_fields_name%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"toggle": {
|
"toggle": {
|
||||||
|
@ -434,6 +445,11 @@
|
||||||
"name": "[%key:component::light::common::field_effect_name%]",
|
"name": "[%key:component::light::common::field_effect_name%]",
|
||||||
"description": "[%key:component::light::common::field_effect_description%]"
|
"description": "[%key:component::light::common::field_effect_description%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"advanced_fields": {
|
||||||
|
"name": "[%key:component::light::common::section_advanced_fields_name%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,10 +179,19 @@ _FIELD_SCHEMA = vol.Schema(
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_SECTION_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("fields"): vol.Schema({str: _FIELD_SCHEMA}),
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
_SERVICE_SCHEMA = vol.Schema(
|
_SERVICE_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional("target"): vol.Any(TargetSelector.CONFIG_SCHEMA, None),
|
vol.Optional("target"): vol.Any(TargetSelector.CONFIG_SCHEMA, None),
|
||||||
vol.Optional("fields"): vol.Schema({str: _FIELD_SCHEMA}),
|
vol.Optional("fields"): vol.Schema(
|
||||||
|
{str: vol.Any(_SECTION_SCHEMA, _FIELD_SCHEMA)}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
|
@ -26,6 +26,23 @@ def exists(value: Any) -> Any:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def unique_field_validator(fields: Any) -> Any:
|
||||||
|
"""Validate the inputs don't have duplicate keys under different sections."""
|
||||||
|
all_fields = set()
|
||||||
|
for key, value in fields.items():
|
||||||
|
if value and "fields" in value:
|
||||||
|
for key in value["fields"]:
|
||||||
|
if key in all_fields:
|
||||||
|
raise vol.Invalid(f"Duplicate use of field {key} in service.")
|
||||||
|
all_fields.add(key)
|
||||||
|
else:
|
||||||
|
if key in all_fields:
|
||||||
|
raise vol.Invalid(f"Duplicate use of field {key} in service.")
|
||||||
|
all_fields.add(key)
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
CORE_INTEGRATION_FIELD_SCHEMA = vol.Schema(
|
CORE_INTEGRATION_FIELD_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional("example"): exists,
|
vol.Optional("example"): exists,
|
||||||
|
@ -44,6 +61,13 @@ CORE_INTEGRATION_FIELD_SCHEMA = vol.Schema(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CORE_INTEGRATION_SECTION_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional("collapsed"): bool,
|
||||||
|
vol.Required("fields"): vol.Schema({str: CORE_INTEGRATION_FIELD_SCHEMA}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
CUSTOM_INTEGRATION_FIELD_SCHEMA = CORE_INTEGRATION_FIELD_SCHEMA.extend(
|
CUSTOM_INTEGRATION_FIELD_SCHEMA = CORE_INTEGRATION_FIELD_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional("description"): str,
|
vol.Optional("description"): str,
|
||||||
|
@ -57,7 +81,17 @@ CORE_INTEGRATION_SERVICE_SCHEMA = vol.Any(
|
||||||
vol.Optional("target"): vol.Any(
|
vol.Optional("target"): vol.Any(
|
||||||
selector.TargetSelector.CONFIG_SCHEMA, None
|
selector.TargetSelector.CONFIG_SCHEMA, None
|
||||||
),
|
),
|
||||||
vol.Optional("fields"): vol.Schema({str: CORE_INTEGRATION_FIELD_SCHEMA}),
|
vol.Optional("fields"): vol.All(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
str: vol.Any(
|
||||||
|
CORE_INTEGRATION_FIELD_SCHEMA,
|
||||||
|
CORE_INTEGRATION_SECTION_SCHEMA,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
unique_field_validator,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
|
@ -107,7 +141,7 @@ def grep_dir(path: pathlib.Path, glob_pattern: str, search_pattern: str) -> bool
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def validate_services(config: Config, integration: Integration) -> None:
|
def validate_services(config: Config, integration: Integration) -> None: # noqa: C901
|
||||||
"""Validate services."""
|
"""Validate services."""
|
||||||
try:
|
try:
|
||||||
data = load_yaml_dict(str(integration.path / "services.yaml"))
|
data = load_yaml_dict(str(integration.path / "services.yaml"))
|
||||||
|
@ -200,6 +234,9 @@ def validate_services(config: Config, integration: Integration) -> None:
|
||||||
# The same check is done for the description in each of the fields of the
|
# The same check is done for the description in each of the fields of the
|
||||||
# service schema.
|
# service schema.
|
||||||
for field_name, field_schema in service_schema.get("fields", {}).items():
|
for field_name, field_schema in service_schema.get("fields", {}).items():
|
||||||
|
if "fields" in field_schema:
|
||||||
|
# This is a section
|
||||||
|
continue
|
||||||
if "name" not in field_schema:
|
if "name" not in field_schema:
|
||||||
try:
|
try:
|
||||||
strings["services"][service_name]["fields"][field_name]["name"]
|
strings["services"][service_name]["fields"][field_name]["name"]
|
||||||
|
@ -233,6 +270,20 @@ def validate_services(config: Config, integration: Integration) -> None:
|
||||||
f"Service {service_name} has a field {field_name} with a selector with a translation key {translation_key} that is not in the translations file",
|
f"Service {service_name} has a field {field_name} with a selector with a translation key {translation_key} that is not in the translations file",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The same check is done for the description in each of the sections of the
|
||||||
|
# service schema.
|
||||||
|
for section_name, section_schema in service_schema.get("fields", {}).items():
|
||||||
|
if "fields" not in section_schema:
|
||||||
|
# This is not a section
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
strings["services"][service_name]["sections"][section_name]["name"]
|
||||||
|
except KeyError:
|
||||||
|
integration.add_error(
|
||||||
|
"services",
|
||||||
|
f"Service {service_name} has a section {section_name} with no name {error_msg_suffix}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
def validate(integrations: dict[str, Integration], config: Config) -> None:
|
||||||
"""Handle dependencies for integrations."""
|
"""Handle dependencies for integrations."""
|
||||||
|
|
|
@ -383,6 +383,13 @@ def gen_strings_schema(config: Config, integration: Integration) -> vol.Schema:
|
||||||
},
|
},
|
||||||
slug_validator=translation_key_validator,
|
slug_validator=translation_key_validator,
|
||||||
),
|
),
|
||||||
|
vol.Optional("sections"): cv.schema_with_slug_keys(
|
||||||
|
{
|
||||||
|
vol.Required("name"): str,
|
||||||
|
vol.Optional("description"): translation_value_validator,
|
||||||
|
},
|
||||||
|
slug_validator=translation_key_validator,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
slug_validator=translation_key_validator,
|
slug_validator=translation_key_validator,
|
||||||
),
|
),
|
||||||
|
|
|
@ -990,6 +990,17 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
|
||||||
- light.ColorMode.COLOR_TEMP
|
- light.ColorMode.COLOR_TEMP
|
||||||
selector:
|
selector:
|
||||||
number:
|
number:
|
||||||
|
advanced_stuff:
|
||||||
|
fields:
|
||||||
|
temperature:
|
||||||
|
filter:
|
||||||
|
supported_features:
|
||||||
|
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_HOME
|
||||||
|
attribute:
|
||||||
|
supported_color_modes:
|
||||||
|
- light.ColorMode.COLOR_TEMP
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
domain = "test_domain"
|
domain = "test_domain"
|
||||||
|
@ -1024,6 +1035,17 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
|
||||||
test_service_schema = {
|
test_service_schema = {
|
||||||
"description": "",
|
"description": "",
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"advanced_stuff": {
|
||||||
|
"fields": {
|
||||||
|
"temperature": {
|
||||||
|
"filter": {
|
||||||
|
"attribute": {"supported_color_modes": ["color_temp"]},
|
||||||
|
"supported_features": [1],
|
||||||
|
},
|
||||||
|
"selector": {"number": None},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"temperature": {
|
"temperature": {
|
||||||
"filter": {
|
"filter": {
|
||||||
"attribute": {"supported_color_modes": ["color_temp"]},
|
"attribute": {"supported_color_modes": ["color_temp"]},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue