mirror of
https://github.com/home-assistant/core.git
synced 2025-07-10 06:47:09 +00:00
Move QoS setting to shared device properties in MQTT device subentries configuration (#141369)
* Move QoS setting to shared device properties in MQTT device subentries configuration * Use kwargs for validate_user_input helper
This commit is contained in:
parent
63d4efda2e
commit
0de3549e6e
@ -134,6 +134,7 @@ from .const import (
|
|||||||
DEFAULT_PORT,
|
DEFAULT_PORT,
|
||||||
DEFAULT_PREFIX,
|
DEFAULT_PREFIX,
|
||||||
DEFAULT_PROTOCOL,
|
DEFAULT_PROTOCOL,
|
||||||
|
DEFAULT_QOS,
|
||||||
DEFAULT_TRANSPORT,
|
DEFAULT_TRANSPORT,
|
||||||
DEFAULT_WILL,
|
DEFAULT_WILL,
|
||||||
DEFAULT_WS_PATH,
|
DEFAULT_WS_PATH,
|
||||||
@ -368,10 +369,6 @@ COMMON_ENTITY_FIELDS = {
|
|||||||
CONF_ENTITY_PICTURE: PlatformField(TEXT_SELECTOR, False, cv.url, "invalid_url"),
|
CONF_ENTITY_PICTURE: PlatformField(TEXT_SELECTOR, False, cv.url, "invalid_url"),
|
||||||
}
|
}
|
||||||
|
|
||||||
COMMON_MQTT_FIELDS = {
|
|
||||||
CONF_QOS: PlatformField(QOS_SELECTOR, False, valid_qos_schema, default=0),
|
|
||||||
}
|
|
||||||
|
|
||||||
PLATFORM_ENTITY_FIELDS = {
|
PLATFORM_ENTITY_FIELDS = {
|
||||||
Platform.NOTIFY.value: {},
|
Platform.NOTIFY.value: {},
|
||||||
Platform.SENSOR.value: {
|
Platform.SENSOR.value: {
|
||||||
@ -431,16 +428,17 @@ ENTITY_CONFIG_VALIDATOR: dict[
|
|||||||
Platform.SENSOR.value: validate_sensor_platform_config,
|
Platform.SENSOR.value: validate_sensor_platform_config,
|
||||||
}
|
}
|
||||||
|
|
||||||
MQTT_DEVICE_SCHEMA = vol.Schema(
|
MQTT_DEVICE_PLATFORM_FIELDS = {
|
||||||
{
|
ATTR_NAME: PlatformField(TEXT_SELECTOR, False, str),
|
||||||
vol.Required(ATTR_NAME): TEXT_SELECTOR,
|
ATTR_SW_VERSION: PlatformField(TEXT_SELECTOR, False, str),
|
||||||
vol.Optional(ATTR_SW_VERSION): TEXT_SELECTOR,
|
ATTR_HW_VERSION: PlatformField(TEXT_SELECTOR, False, str),
|
||||||
vol.Optional(ATTR_HW_VERSION): TEXT_SELECTOR,
|
ATTR_MODEL: PlatformField(TEXT_SELECTOR, False, str),
|
||||||
vol.Optional(ATTR_MODEL): TEXT_SELECTOR,
|
ATTR_MODEL_ID: PlatformField(TEXT_SELECTOR, False, str),
|
||||||
vol.Optional(ATTR_MODEL_ID): TEXT_SELECTOR,
|
ATTR_CONFIGURATION_URL: PlatformField(TEXT_SELECTOR, False, cv.url, "invalid_url"),
|
||||||
vol.Optional(ATTR_CONFIGURATION_URL): TEXT_SELECTOR,
|
CONF_QOS: PlatformField(
|
||||||
}
|
QOS_SELECTOR, False, int, default=DEFAULT_QOS, section="mqtt_settings"
|
||||||
)
|
),
|
||||||
|
}
|
||||||
|
|
||||||
REAUTH_SCHEMA = vol.Schema(
|
REAUTH_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -527,7 +525,8 @@ def calculate_merged_config(
|
|||||||
def validate_user_input(
|
def validate_user_input(
|
||||||
user_input: dict[str, Any],
|
user_input: dict[str, Any],
|
||||||
data_schema_fields: dict[str, PlatformField],
|
data_schema_fields: dict[str, PlatformField],
|
||||||
component_data: dict[str, Any] | None,
|
*,
|
||||||
|
component_data: dict[str, Any] | None = None,
|
||||||
config_validator: Callable[[dict[str, Any]], dict[str, str]] | None = None,
|
config_validator: Callable[[dict[str, Any]], dict[str, str]] | None = None,
|
||||||
) -> tuple[dict[str, Any], dict[str, str]]:
|
) -> tuple[dict[str, Any], dict[str, str]]:
|
||||||
"""Validate user input."""
|
"""Validate user input."""
|
||||||
@ -566,11 +565,21 @@ def data_schema_from_fields(
|
|||||||
reconfig: bool,
|
reconfig: bool,
|
||||||
component_data: dict[str, Any] | None = None,
|
component_data: dict[str, Any] | None = None,
|
||||||
user_input: dict[str, Any] | None = None,
|
user_input: dict[str, Any] | None = None,
|
||||||
|
device_data: MqttDeviceData | None = None,
|
||||||
) -> vol.Schema:
|
) -> vol.Schema:
|
||||||
"""Generate custom data schema from platform fields."""
|
"""Generate custom data schema from platform fields or device data."""
|
||||||
component_data_with_user_input = deepcopy(component_data)
|
if device_data is not None:
|
||||||
|
component_data_with_user_input: dict[str, Any] | None = dict(device_data)
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert component_data_with_user_input is not None
|
||||||
|
component_data_with_user_input.update(
|
||||||
|
component_data_with_user_input.pop("mqtt_settings", {})
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
component_data_with_user_input = deepcopy(component_data)
|
||||||
if component_data_with_user_input is not None and user_input is not None:
|
if component_data_with_user_input is not None and user_input is not None:
|
||||||
component_data_with_user_input |= user_input
|
component_data_with_user_input |= user_input
|
||||||
|
|
||||||
sections: dict[str | None, None] = {
|
sections: dict[str | None, None] = {
|
||||||
field_details.section: None for field_details in data_schema_fields.values()
|
field_details.section: None for field_details in data_schema_fields.values()
|
||||||
}
|
}
|
||||||
@ -1221,17 +1230,26 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> SubentryFlowResult:
|
) -> SubentryFlowResult:
|
||||||
"""Add a new MQTT device."""
|
"""Add a new MQTT device."""
|
||||||
errors: dict[str, str] = {}
|
errors: dict[str, Any] = {}
|
||||||
validate_field("configuration_url", cv.url, user_input, errors, "invalid_url")
|
device_data = self._subentry_data[CONF_DEVICE]
|
||||||
if not errors and user_input is not None:
|
data_schema = data_schema_from_fields(
|
||||||
self._subentry_data[CONF_DEVICE] = cast(MqttDeviceData, user_input)
|
MQTT_DEVICE_PLATFORM_FIELDS,
|
||||||
if self.source == SOURCE_RECONFIGURE:
|
device_data=device_data,
|
||||||
return await self.async_step_summary_menu()
|
reconfig=True,
|
||||||
return await self.async_step_entity()
|
)
|
||||||
|
if user_input is not None:
|
||||||
|
merged_user_input, errors = validate_user_input(
|
||||||
|
user_input, MQTT_DEVICE_PLATFORM_FIELDS
|
||||||
|
)
|
||||||
|
if not errors:
|
||||||
|
self._subentry_data[CONF_DEVICE] = cast(
|
||||||
|
MqttDeviceData, merged_user_input
|
||||||
|
)
|
||||||
|
if self.source == SOURCE_RECONFIGURE:
|
||||||
|
return await self.async_step_summary_menu()
|
||||||
|
return await self.async_step_entity()
|
||||||
data_schema = self.add_suggested_values_to_schema(
|
data_schema = self.add_suggested_values_to_schema(
|
||||||
MQTT_DEVICE_SCHEMA,
|
data_schema, device_data if user_input is None else user_input
|
||||||
self._subentry_data[CONF_DEVICE] if user_input is None else user_input,
|
|
||||||
)
|
)
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id=CONF_DEVICE,
|
step_id=CONF_DEVICE,
|
||||||
@ -1257,7 +1275,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
data_schema = data_schema_from_fields(data_schema_fields, reconfig=reconfig)
|
data_schema = data_schema_from_fields(data_schema_fields, reconfig=reconfig)
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
merged_user_input, errors = validate_user_input(
|
merged_user_input, errors = validate_user_input(
|
||||||
user_input, data_schema_fields, component_data
|
user_input, data_schema_fields, component_data=component_data
|
||||||
)
|
)
|
||||||
if not errors:
|
if not errors:
|
||||||
if self._component_id is None:
|
if self._component_id is None:
|
||||||
@ -1357,8 +1375,8 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
merged_user_input, errors = validate_user_input(
|
merged_user_input, errors = validate_user_input(
|
||||||
user_input,
|
user_input,
|
||||||
data_schema_fields,
|
data_schema_fields,
|
||||||
component_data,
|
component_data=component_data,
|
||||||
ENTITY_CONFIG_VALIDATOR[platform],
|
config_validator=ENTITY_CONFIG_VALIDATOR[platform],
|
||||||
)
|
)
|
||||||
if not errors:
|
if not errors:
|
||||||
self.update_component_fields(data_schema_fields, merged_user_input)
|
self.update_component_fields(data_schema_fields, merged_user_input)
|
||||||
@ -1395,7 +1413,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
assert self._component_id is not None
|
assert self._component_id is not None
|
||||||
component_data = self._subentry_data["components"][self._component_id]
|
component_data = self._subentry_data["components"][self._component_id]
|
||||||
platform = component_data[CONF_PLATFORM]
|
platform = component_data[CONF_PLATFORM]
|
||||||
data_schema_fields = PLATFORM_MQTT_FIELDS[platform] | COMMON_MQTT_FIELDS
|
data_schema_fields = PLATFORM_MQTT_FIELDS[platform]
|
||||||
data_schema = data_schema_from_fields(
|
data_schema = data_schema_from_fields(
|
||||||
data_schema_fields,
|
data_schema_fields,
|
||||||
reconfig=bool(
|
reconfig=bool(
|
||||||
@ -1408,8 +1426,8 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
merged_user_input, errors = validate_user_input(
|
merged_user_input, errors = validate_user_input(
|
||||||
user_input,
|
user_input,
|
||||||
data_schema_fields,
|
data_schema_fields,
|
||||||
component_data,
|
component_data=component_data,
|
||||||
ENTITY_CONFIG_VALIDATOR[platform],
|
config_validator=ENTITY_CONFIG_VALIDATOR[platform],
|
||||||
)
|
)
|
||||||
if not errors:
|
if not errors:
|
||||||
self.update_component_fields(data_schema_fields, merged_user_input)
|
self.update_component_fields(data_schema_fields, merged_user_input)
|
||||||
|
@ -300,6 +300,7 @@ def async_setup_entity_entry_helper(
|
|||||||
availability_config = subentry_data.get("availability", {})
|
availability_config = subentry_data.get("availability", {})
|
||||||
subentry_entities: list[Entity] = []
|
subentry_entities: list[Entity] = []
|
||||||
device_config = subentry_data["device"].copy()
|
device_config = subentry_data["device"].copy()
|
||||||
|
device_mqtt_options = device_config.pop("mqtt_settings", {})
|
||||||
device_config["identifiers"] = config_subentry_id
|
device_config["identifiers"] = config_subentry_id
|
||||||
for component_id, component_data in subentry_data["components"].items():
|
for component_id, component_data in subentry_data["components"].items():
|
||||||
if component_data["platform"] != domain:
|
if component_data["platform"] != domain:
|
||||||
@ -311,6 +312,7 @@ def async_setup_entity_entry_helper(
|
|||||||
component_config[CONF_DEVICE] = device_config
|
component_config[CONF_DEVICE] = device_config
|
||||||
component_config.pop("platform")
|
component_config.pop("platform")
|
||||||
component_config.update(availability_config)
|
component_config.update(availability_config)
|
||||||
|
component_config.update(device_mqtt_options)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
config = platform_schema_modern(component_config)
|
config = platform_schema_modern(component_config)
|
||||||
|
@ -420,6 +420,12 @@ class MqttComponentConfig:
|
|||||||
discovery_payload: MQTTDiscoveryPayload
|
discovery_payload: MQTTDiscoveryPayload
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceMqttOptions(TypedDict, total=False):
|
||||||
|
"""Hold the shared MQTT specific options for an MQTT device."""
|
||||||
|
|
||||||
|
qos: int
|
||||||
|
|
||||||
|
|
||||||
class MqttDeviceData(TypedDict, total=False):
|
class MqttDeviceData(TypedDict, total=False):
|
||||||
"""Hold the data for an MQTT device."""
|
"""Hold the data for an MQTT device."""
|
||||||
|
|
||||||
@ -430,6 +436,7 @@ class MqttDeviceData(TypedDict, total=False):
|
|||||||
hw_version: str
|
hw_version: str
|
||||||
model: str
|
model: str
|
||||||
model_id: str
|
model_id: str
|
||||||
|
mqtt_settings: DeviceMqttOptions
|
||||||
|
|
||||||
|
|
||||||
class MqttAvailabilityData(TypedDict, total=False):
|
class MqttAvailabilityData(TypedDict, total=False):
|
||||||
|
@ -150,6 +150,17 @@
|
|||||||
"hw_version": "The hardware version of the device. E.g. 'v1.0 rev a'.",
|
"hw_version": "The hardware version of the device. E.g. 'v1.0 rev a'.",
|
||||||
"model": "E.g. 'Cleanmaster Pro'.",
|
"model": "E.g. 'Cleanmaster Pro'.",
|
||||||
"model_id": "E.g. '123NK2PRO'."
|
"model_id": "E.g. '123NK2PRO'."
|
||||||
|
},
|
||||||
|
"sections": {
|
||||||
|
"mqtt_settings": {
|
||||||
|
"name": "MQTT Settings",
|
||||||
|
"data": {
|
||||||
|
"qos": "QoS"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"qos": "The QoS value the device's entities should use."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"summary_menu": {
|
"summary_menu": {
|
||||||
@ -235,8 +246,7 @@
|
|||||||
"value_template": "Value template",
|
"value_template": "Value template",
|
||||||
"last_reset_value_template": "Last reset value template",
|
"last_reset_value_template": "Last reset value template",
|
||||||
"force_update": "Force update",
|
"force_update": "Force update",
|
||||||
"retain": "Retain",
|
"retain": "Retain"
|
||||||
"qos": "QoS"
|
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
|
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
|
||||||
@ -245,8 +255,7 @@
|
|||||||
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value.",
|
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value.",
|
||||||
"last_reset_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the last reset. When Last reset template is set, the State class option must be Total. [Learn more.]({url}#last_reset_value_template)",
|
"last_reset_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the last reset. When Last reset template is set, the State class option must be Total. [Learn more.]({url}#last_reset_value_template)",
|
||||||
"force_update": "Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history. [Learn more.]({url}#force_update)",
|
"force_update": "Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history. [Learn more.]({url}#force_update)",
|
||||||
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker.",
|
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker."
|
||||||
"qos": "The QoS value {platform} entity should use."
|
|
||||||
},
|
},
|
||||||
"sections": {
|
"sections": {
|
||||||
"advanced_settings": {
|
"advanced_settings": {
|
||||||
|
@ -70,7 +70,6 @@ MOCK_SUBENTRY_NOTIFY_COMPONENT1 = {
|
|||||||
"363a7ecad6be4a19b939a016ea93e994": {
|
"363a7ecad6be4a19b939a016ea93e994": {
|
||||||
"platform": "notify",
|
"platform": "notify",
|
||||||
"name": "Milkman alert",
|
"name": "Milkman alert",
|
||||||
"qos": 0,
|
|
||||||
"command_topic": "test-topic",
|
"command_topic": "test-topic",
|
||||||
"command_template": "{{ value }}",
|
"command_template": "{{ value }}",
|
||||||
"entity_picture": "https://example.com/363a7ecad6be4a19b939a016ea93e994",
|
"entity_picture": "https://example.com/363a7ecad6be4a19b939a016ea93e994",
|
||||||
@ -81,7 +80,6 @@ MOCK_SUBENTRY_NOTIFY_COMPONENT2 = {
|
|||||||
"6494827dac294fa0827c54b02459d309": {
|
"6494827dac294fa0827c54b02459d309": {
|
||||||
"platform": "notify",
|
"platform": "notify",
|
||||||
"name": "The second notifier",
|
"name": "The second notifier",
|
||||||
"qos": 0,
|
|
||||||
"command_topic": "test-topic2",
|
"command_topic": "test-topic2",
|
||||||
"entity_picture": "https://example.com/6494827dac294fa0827c54b02459d309",
|
"entity_picture": "https://example.com/6494827dac294fa0827c54b02459d309",
|
||||||
},
|
},
|
||||||
@ -89,7 +87,6 @@ MOCK_SUBENTRY_NOTIFY_COMPONENT2 = {
|
|||||||
MOCK_SUBENTRY_NOTIFY_COMPONENT_NO_NAME = {
|
MOCK_SUBENTRY_NOTIFY_COMPONENT_NO_NAME = {
|
||||||
"5269352dd9534c908d22812ea5d714cd": {
|
"5269352dd9534c908d22812ea5d714cd": {
|
||||||
"platform": "notify",
|
"platform": "notify",
|
||||||
"qos": 0,
|
|
||||||
"command_topic": "test-topic",
|
"command_topic": "test-topic",
|
||||||
"command_template": "{{ value }}",
|
"command_template": "{{ value }}",
|
||||||
"entity_picture": "https://example.com/5269352dd9534c908d22812ea5d714cd",
|
"entity_picture": "https://example.com/5269352dd9534c908d22812ea5d714cd",
|
||||||
@ -102,7 +99,6 @@ MOCK_SUBENTRY_SENSOR_COMPONENT = {
|
|||||||
"platform": "sensor",
|
"platform": "sensor",
|
||||||
"name": "Energy",
|
"name": "Energy",
|
||||||
"device_class": "enum",
|
"device_class": "enum",
|
||||||
"qos": 1,
|
|
||||||
"state_topic": "test-topic",
|
"state_topic": "test-topic",
|
||||||
"options": ["low", "medium", "high"],
|
"options": ["low", "medium", "high"],
|
||||||
"expire_after": 30,
|
"expire_after": 30,
|
||||||
@ -117,7 +113,6 @@ MOCK_SUBENTRY_SENSOR_COMPONENT_STATE_CLASS = {
|
|||||||
"state_class": "measurement",
|
"state_class": "measurement",
|
||||||
"state_topic": "test-topic",
|
"state_topic": "test-topic",
|
||||||
"entity_picture": "https://example.com/a0f85790a95d4889924602effff06b6e",
|
"entity_picture": "https://example.com/a0f85790a95d4889924602effff06b6e",
|
||||||
"qos": 0,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET = {
|
MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET = {
|
||||||
@ -128,7 +123,6 @@ MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET = {
|
|||||||
"last_reset_value_template": "{{ value_json.value }}",
|
"last_reset_value_template": "{{ value_json.value }}",
|
||||||
"state_topic": "test-topic",
|
"state_topic": "test-topic",
|
||||||
"entity_picture": "https://example.com/e9261f6feed443e7b7d5f3fbe2a47412",
|
"entity_picture": "https://example.com/e9261f6feed443e7b7d5f3fbe2a47412",
|
||||||
"qos": 0,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +133,6 @@ MOCK_SUBENTRY_LIGHT_COMPONENT = {
|
|||||||
"8131babc5e8d4f44b82e0761d39091a2": {
|
"8131babc5e8d4f44b82e0761d39091a2": {
|
||||||
"platform": "light",
|
"platform": "light",
|
||||||
"name": "Test light",
|
"name": "Test light",
|
||||||
"qos": 1,
|
|
||||||
"command_topic": "test-topic4",
|
"command_topic": "test-topic4",
|
||||||
"schema": "basic",
|
"schema": "basic",
|
||||||
"entity_picture": "https://example.com/8131babc5e8d4f44b82e0761d39091a2",
|
"entity_picture": "https://example.com/8131babc5e8d4f44b82e0761d39091a2",
|
||||||
@ -149,7 +142,6 @@ MOCK_SUBENTRY_NOTIFY_BAD_SCHEMA = {
|
|||||||
"b10b531e15244425a74bb0abb1e9d2c6": {
|
"b10b531e15244425a74bb0abb1e9d2c6": {
|
||||||
"platform": "notify",
|
"platform": "notify",
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"qos": 1,
|
|
||||||
"command_topic": "bad#topic",
|
"command_topic": "bad#topic",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -183,6 +175,7 @@ MOCK_NOTIFY_SUBENTRY_DATA_SINGLE = {
|
|||||||
"model": "Model XL",
|
"model": "Model XL",
|
||||||
"model_id": "mn002",
|
"model_id": "mn002",
|
||||||
"configuration_url": "https://example.com",
|
"configuration_url": "https://example.com",
|
||||||
|
"mqtt_settings": {"qos": 1},
|
||||||
},
|
},
|
||||||
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1,
|
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1,
|
||||||
}
|
}
|
||||||
|
@ -2616,6 +2616,7 @@ async def test_migrate_of_incompatible_config_entry(
|
|||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
(
|
(
|
||||||
"config_subentries_data",
|
"config_subentries_data",
|
||||||
|
"mock_device_user_input",
|
||||||
"mock_entity_user_input",
|
"mock_entity_user_input",
|
||||||
"mock_entity_details_user_input",
|
"mock_entity_details_user_input",
|
||||||
"mock_entity_details_failed_user_input",
|
"mock_entity_details_failed_user_input",
|
||||||
@ -2626,13 +2627,13 @@ async def test_migrate_of_incompatible_config_entry(
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
|
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
|
||||||
|
{"name": "Milk notifier", "mqtt_settings": {"qos": 1}},
|
||||||
{"name": "Milkman alert"},
|
{"name": "Milkman alert"},
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"command_topic": "test-topic",
|
"command_topic": "test-topic",
|
||||||
"command_template": "{{ value }}",
|
"command_template": "{{ value }}",
|
||||||
"qos": 0,
|
|
||||||
"retain": False,
|
"retain": False,
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
@ -2645,13 +2646,13 @@ async def test_migrate_of_incompatible_config_entry(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME,
|
MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME,
|
||||||
|
{"name": "Milk notifier", "mqtt_settings": {"qos": 0}},
|
||||||
{},
|
{},
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
{
|
{
|
||||||
"command_topic": "test-topic",
|
"command_topic": "test-topic",
|
||||||
"command_template": "{{ value }}",
|
"command_template": "{{ value }}",
|
||||||
"qos": 0,
|
|
||||||
"retain": False,
|
"retain": False,
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
@ -2664,6 +2665,7 @@ async def test_migrate_of_incompatible_config_entry(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
MOCK_SENSOR_SUBENTRY_DATA_SINGLE,
|
MOCK_SENSOR_SUBENTRY_DATA_SINGLE,
|
||||||
|
{"name": "Test sensor", "mqtt_settings": {"qos": 0}},
|
||||||
{"name": "Energy"},
|
{"name": "Energy"},
|
||||||
{"device_class": "enum", "options": ["low", "medium", "high"]},
|
{"device_class": "enum", "options": ["low", "medium", "high"]},
|
||||||
(
|
(
|
||||||
@ -2708,7 +2710,6 @@ async def test_migrate_of_incompatible_config_entry(
|
|||||||
"state_topic": "test-topic",
|
"state_topic": "test-topic",
|
||||||
"value_template": "{{ value_json.value }}",
|
"value_template": "{{ value_json.value }}",
|
||||||
"advanced_settings": {"expire_after": 30},
|
"advanced_settings": {"expire_after": 30},
|
||||||
"qos": 1,
|
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
@ -2720,6 +2721,7 @@ async def test_migrate_of_incompatible_config_entry(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS,
|
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS,
|
||||||
|
{"name": "Test sensor", "mqtt_settings": {"qos": 0}},
|
||||||
{"name": "Energy"},
|
{"name": "Energy"},
|
||||||
{
|
{
|
||||||
"state_class": "measurement",
|
"state_class": "measurement",
|
||||||
@ -2743,6 +2745,7 @@ async def test_subentry_configflow(
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mqtt_mock_entry: MqttMockHAClientGenerator,
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
config_subentries_data: dict[str, Any],
|
config_subentries_data: dict[str, Any],
|
||||||
|
mock_device_user_input: dict[str, Any],
|
||||||
mock_entity_user_input: dict[str, Any],
|
mock_entity_user_input: dict[str, Any],
|
||||||
mock_entity_details_user_input: dict[str, Any],
|
mock_entity_details_user_input: dict[str, Any],
|
||||||
mock_entity_details_failed_user_input: tuple[
|
mock_entity_details_failed_user_input: tuple[
|
||||||
@ -2753,7 +2756,7 @@ async def test_subentry_configflow(
|
|||||||
entity_name: str,
|
entity_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the subentry ConfigFlow."""
|
"""Test the subentry ConfigFlow."""
|
||||||
device_name = config_subentries_data["device"]["name"]
|
device_name = mock_device_user_input["name"]
|
||||||
component = next(iter(config_subentries_data["components"].values()))
|
component = next(iter(config_subentries_data["components"].values()))
|
||||||
|
|
||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
@ -2780,14 +2783,7 @@ async def test_subentry_configflow(
|
|||||||
|
|
||||||
result = await hass.config_entries.subentries.async_configure(
|
result = await hass.config_entries.subentries.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input=mock_device_user_input,
|
||||||
"name": device_name,
|
|
||||||
"sw_version": "1.0",
|
|
||||||
"hw_version": "2.1 rev a",
|
|
||||||
"model": "Model XL",
|
|
||||||
"model_id": "mn002",
|
|
||||||
"configuration_url": "https://example.com",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["step_id"] == "entity"
|
assert result["step_id"] == "entity"
|
||||||
@ -3471,7 +3467,6 @@ async def test_subentry_reconfigure_edit_entity_reset_fields(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command_topic": "test-topic2",
|
"command_topic": "test-topic2",
|
||||||
"qos": 0,
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""The tests for shared code of the MQTT platform."""
|
"""The tests for shared code of the MQTT platform."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -21,7 +21,11 @@ from homeassistant.helpers import (
|
|||||||
)
|
)
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from .common import MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA, MOCK_SUBENTRY_DATA_SET_MIX
|
from .common import (
|
||||||
|
MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
|
||||||
|
MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA,
|
||||||
|
MOCK_SUBENTRY_DATA_SET_MIX,
|
||||||
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry, async_capture_events, async_fire_mqtt_message
|
from tests.common import MockConfigEntry, async_capture_events, async_fire_mqtt_message
|
||||||
from tests.typing import MqttMockHAClientGenerator
|
from tests.typing import MqttMockHAClientGenerator
|
||||||
@ -547,3 +551,39 @@ async def test_loading_subentry_with_bad_component_schema(
|
|||||||
"Schema violation occurred when trying to set up entity from subentry"
|
"Schema violation occurred when trying to set up entity from subentry"
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"mqtt_config_subentries_data",
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ConfigSubentryData(
|
||||||
|
data=MOCK_NOTIFY_SUBENTRY_DATA_SINGLE,
|
||||||
|
subentry_type="device",
|
||||||
|
title="Mock subentry",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_qos_on_mqt_device_from_subentry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mqtt_mock_entry: MqttMockHAClientGenerator,
|
||||||
|
mqtt_config_subentries_data: tuple[dict[str, Any]],
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test QoS is set correctly on entities from MQTT device."""
|
||||||
|
mqtt_mock = await mqtt_mock_entry()
|
||||||
|
entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0]
|
||||||
|
subentry_id = next(iter(entry.subentries))
|
||||||
|
# Each subentry has one device
|
||||||
|
device = device_registry.async_get_device({("mqtt", subentry_id)})
|
||||||
|
assert device is not None
|
||||||
|
assert hass.states.get("notify.milk_notifier_milkman_alert") is not None
|
||||||
|
await hass.services.async_call(
|
||||||
|
"notify",
|
||||||
|
"send_message",
|
||||||
|
{"entity_id": "notify.milk_notifier_milkman_alert", "message": "Test message"},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(mqtt_mock.async_publish.mock_calls) == 1
|
||||||
|
mqtt_mock.async_publish.mock_calls[0] = call("test-topic", "Test message", 1, False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user