Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sensor as entity platform on MQTT subentries #139899

Merged
merged 27 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f6e962b
Add sensor as entity platform on MQTT subentries
jbouwh Mar 15, 2025
4dff885
Fix typo
jbouwh Mar 17, 2025
f34271e
Improve device class data description
jbouwh Mar 17, 2025
89faadb
Tweak
jbouwh Mar 17, 2025
d74be02
Rework reconfig calculation
jbouwh Mar 17, 2025
3f0701a
Filter out last_reset_value_template if state class is not total
jbouwh Mar 17, 2025
e6e0477
Collapse expire after as advanced setting
jbouwh Mar 21, 2025
19f6cc0
Update suggested_display_precision translation strings
jbouwh Mar 21, 2025
d9e45cf
Make options and last_reset_template conditional, use sections for ad…
jbouwh Mar 22, 2025
55f027a
Merge branch 'dev' into mqtt-subentry-sensor
jbouwh Mar 22, 2025
3cb26a6
Ensure options are removed properly
jbouwh Mar 23, 2025
a00ffac
Improve sensor options label, ensure UOM is set when device class has…
jbouwh Mar 24, 2025
4b1f968
Merge branch 'dev' into mqtt-subentry-sensor
jbouwh Mar 24, 2025
de10613
Merge branch 'dev' into mqtt-subentry-sensor
jbouwh Mar 24, 2025
54f138a
Use helper to apply suggested values from component config
jbouwh Mar 24, 2025
cec98b0
Rename to `Add option`
jbouwh Mar 24, 2025
d5df517
Fix schema builder not hiding empty sections and removing fields excl…
jbouwh Mar 25, 2025
00fa76c
Do not hide advanced settings if values are available or are defaults
jbouwh Mar 25, 2025
018b87f
Merge branch 'dev' into mqtt-subentry-sensor
jbouwh Mar 25, 2025
56ccce5
Improve spelling and Learn more links
jbouwh Mar 25, 2025
4e9cf0d
Improve unit of measurement validation
jbouwh Mar 25, 2025
2a7667b
Fix UOM selector and translation strings
jbouwh Mar 25, 2025
303a599
Address comments from code review
jbouwh Mar 25, 2025
70a9561
Remove stale comment
jbouwh Mar 25, 2025
4bd9634
Rename selector constant, split validator
jbouwh Mar 26, 2025
09a2cd2
Simplify config validator
jbouwh Mar 26, 2025
cd5a865
Return tuple with config and errors for config validation
jbouwh Mar 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
455 changes: 406 additions & 49 deletions homeassistant/components/mqtt/config_flow.py

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions homeassistant/components/mqtt/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@
CONF_EFFECT_TEMPLATE = "effect_template"
CONF_EFFECT_VALUE_TEMPLATE = "effect_value_template"
CONF_ENTITY_PICTURE = "entity_picture"
CONF_EXPIRE_AFTER = "expire_after"
CONF_FLASH_TIME_LONG = "flash_time_long"
CONF_FLASH_TIME_SHORT = "flash_time_short"
CONF_GREEN_TEMPLATE = "green_template"
CONF_HS_COMMAND_TEMPLATE = "hs_command_template"
CONF_HS_COMMAND_TOPIC = "hs_command_topic"
CONF_HS_STATE_TOPIC = "hs_state_topic"
CONF_HS_VALUE_TEMPLATE = "hs_value_template"
CONF_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"
CONF_MAX_KELVIN = "max_kelvin"
CONF_MAX_MIREDS = "max_mireds"
CONF_MIN_KELVIN = "min_kelvin"
Expand Down Expand Up @@ -128,6 +130,7 @@
CONF_STATE_CLOSING = "state_closing"
CONF_STATE_OPEN = "state_open"
CONF_STATE_OPENING = "state_opening"
CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision"
CONF_SUPPORTED_COLOR_MODES = "supported_color_modes"
CONF_TEMP_COMMAND_TEMPLATE = "temperature_command_template"
CONF_TEMP_COMMAND_TOPIC = "temperature_command_topic"
Expand Down
7 changes: 2 additions & 5 deletions homeassistant/components/mqtt/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
async_subscribe_topics_internal,
async_unsubscribe_topics,
)
from .util import mqtt_config_entry_enabled
from .util import learn_more_url, mqtt_config_entry_enabled

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -346,17 +346,14 @@ def _async_setup_entities() -> None:
line = getattr(yaml_config, "__line__", "?")
issue_id = hex(hash(frozenset(yaml_config)))
yaml_config_str = yaml_dump(yaml_config)
learn_more_url = (
f"https://www.home-assistant.io/integrations/{domain}.mqtt/"
)
async_create_issue(
hass,
DOMAIN,
issue_id,
issue_domain=domain,
is_fixable=False,
severity=IssueSeverity.ERROR,
learn_more_url=learn_more_url,
learn_more_url=learn_more_url(domain),
translation_placeholders={
"domain": domain,
"config_file": config_file,
Expand Down
14 changes: 9 additions & 5 deletions homeassistant/components/mqtt/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,15 @@

from . import subscription
from .config import MQTT_RO_SCHEMA
from .const import CONF_OPTIONS, CONF_STATE_TOPIC, DOMAIN, PAYLOAD_NONE
from .const import (
CONF_EXPIRE_AFTER,
CONF_LAST_RESET_VALUE_TEMPLATE,
CONF_OPTIONS,
CONF_STATE_TOPIC,
CONF_SUGGESTED_DISPLAY_PRECISION,
DOMAIN,
PAYLOAD_NONE,
)
from .entity import MqttAvailabilityMixin, MqttEntity, async_setup_entity_entry_helper
from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage
from .schemas import MQTT_ENTITY_COMMON_SCHEMA
Expand All @@ -51,10 +59,6 @@

PARALLEL_UPDATES = 0

CONF_EXPIRE_AFTER = "expire_after"
CONF_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template"
CONF_SUGGESTED_DISPLAY_PRECISION = "suggested_display_precision"

MQTT_SENSOR_ATTRIBUTES_BLOCKED = frozenset(
{
sensor.ATTR_LAST_RESET,
Expand Down
125 changes: 122 additions & 3 deletions homeassistant/components/mqtt/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,20 +198,66 @@
"component": "Select the entity you want to update."
}
},
"entity_platform_config": {
"title": "Configure MQTT device \"{mqtt_device}\"",
"description": "Please configure specific details for {platform} entity \"{entity}\":",
"data": {
"device_class": "Device class",
"state_class": "State class",
"unit_of_measurement": "Unit of measurement",
"options": "Add option"
},
"data_description": {
"device_class": "The device class of the {platform} entity. [Learn more.]({url}#device_class)",
"state_class": "The [state_class](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes) of the sensor. [Learn more.]({url}#state_class)",
"unit_of_measurement": "Defines the unit of measurement of the sensor, if any.",
"options": "Options for allowed sensor state values. The sensor’s device_class must be set to Enumeration. The options option cannot be used together with State Class or Unit of measurement."
},
"sections": {
"advanced_settings": {
"name": "Advanced options",
"data": {
"suggested_display_precision": "Suggested display precision"
},
"data_description": {
"suggested_display_precision": "The number of decimals which should be used in the {platform} entity state after rounding. [Learn more.]({url}#suggested_display_precision)"
}
}
}
},
"mqtt_platform_config": {
"title": "Configure MQTT device \"{mqtt_device}\"",
"description": "Please configure MQTT specific details for {platform} entity \"{entity}\":",
"data": {
"command_topic": "Command topic",
"command_template": "Command template",
"state_topic": "State topic",
"value_template": "Value template",
"last_reset_value_template": "Last reset value template",
"force_update": "Force update",
"retain": "Retain",
"qos": "QoS"
},
"data_description": {
"command_topic": "The publishing topic that will be used to control the {platform} entity.",
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
"command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to render the payload to be published at the command topic.",
"state_topic": "The MQTT topic subscribed to receive {platform} state values. [Learn more.]({url}#state_topic)",
"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)",
"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.",
"qos": "The QoS value {platform} entity should use."
},
"sections": {
"advanced_settings": {
"name": "Advanced settings",
"data": {
"expire_after": "Expire after"
},
"data_description": {
"expire_after": "If set, it defines the number of seconds after the sensor’s state expires, if it’s not updated. After expiry, the sensor’s state becomes unavailable. If not set, the sensor's state never expires. [Learn more.]({url}#expire_after)"
}
}
}
}
},
Expand All @@ -225,7 +271,12 @@
"invalid_input": "Invalid value",
"invalid_subscribe_topic": "Invalid subscribe topic",
"invalid_template": "Invalid template",
"invalid_url": "Invalid URL"
"invalid_uom": "The unit of measurement \"{unit_of_measurement}\" is not supported by the selected device class, please either remove the device class, select a device class which supports \"{unit_of_measurement}\", or pick a supported unit of measurement from the list",
"invalid_url": "Invalid URL",
"options_not_allowed_with_state_class_or_uom": "The 'Options' setting is not allowed when state class or unit of measurement are used",
"options_device_class_enum": "The 'Options' setting must be used with the Enumeration device class'. If you continue, the existing options will be reset",
"options_with_enum_device_class": "Configure options for the enumeration sensor",
"uom_required_for_device_class": "The selected device device class requires a unit"
}
}
},
Expand Down Expand Up @@ -342,9 +393,70 @@
}
},
"selector": {
"device_class_sensor": {
"options": {
"apparent_power": "[%key:component::sensor::entity_component::apparent_power::name%]",
"area": "[%key:component::sensor::entity_component::area::name%]",
"aqi": "[%key:component::sensor::entity_component::aqi::name%]",
"atmospheric_pressure": "[%key:component::sensor::entity_component::atmospheric_pressure::name%]",
"battery": "[%key:component::sensor::entity_component::battery::name%]",
"blood_glucose_concentration": "[%key:component::sensor::entity_component::blood_glucose_concentration::name%]",
"carbon_dioxide": "[%key:component::sensor::entity_component::carbon_dioxide::name%]",
"carbon_monoxide": "[%key:component::sensor::entity_component::carbon_monoxide::name%]",
"conductivity": "[%key:component::sensor::entity_component::conductivity::name%]",
"current": "[%key:component::sensor::entity_component::current::name%]",
"data_rate": "[%key:component::sensor::entity_component::data_rate::name%]",
"data_size": "[%key:component::sensor::entity_component::data_size::name%]",
"date": "[%key:component::sensor::entity_component::date::name%]",
"distance": "[%key:component::sensor::entity_component::distance::name%]",
"duration": "[%key:component::sensor::entity_component::duration::name%]",
"energy": "[%key:component::sensor::entity_component::energy::name%]",
"energy_distance": "[%key:component::sensor::entity_component::energy_distance::name%]",
"energy_storage": "[%key:component::sensor::entity_component::energy_storage::name%]",
"enum": "Enumeration",
"frequency": "[%key:component::sensor::entity_component::frequency::name%]",
"gas": "[%key:component::sensor::entity_component::gas::name%]",
"humidity": "[%key:component::sensor::entity_component::humidity::name%]",
"illuminance": "[%key:component::sensor::entity_component::illuminance::name%]",
"irradiance": "[%key:component::sensor::entity_component::irradiance::name%]",
"moisture": "[%key:component::sensor::entity_component::moisture::name%]",
"monetary": "[%key:component::sensor::entity_component::monetary::name%]",
"nitrogen_dioxide": "[%key:component::sensor::entity_component::nitrogen_dioxide::name%]",
"nitrogen_monoxide": "[%key:component::sensor::entity_component::nitrogen_monoxide::name%]",
"nitrous_oxide": "[%key:component::sensor::entity_component::nitrous_oxide::name%]",
"ozone": "[%key:component::sensor::entity_component::ozone::name%]",
"ph": "[%key:component::sensor::entity_component::ph::name%]",
"pm1": "[%key:component::sensor::entity_component::pm1::name%]",
"pm10": "[%key:component::sensor::entity_component::pm10::name%]",
"pm25": "[%key:component::sensor::entity_component::pm25::name%]",
"power": "[%key:component::sensor::entity_component::power::name%]",
"power_factor": "[%key:component::sensor::entity_component::power_factor::name%]",
"precipitation": "[%key:component::sensor::entity_component::precipitation::name%]",
"precipitation_intensity": "[%key:component::sensor::entity_component::precipitation_intensity::name%]",
"pressure": "[%key:component::sensor::entity_component::pressure::name%]",
"reactive_power": "[%key:component::sensor::entity_component::reactive_power::name%]",
"signal_strength": "[%key:component::sensor::entity_component::signal_strength::name%]",
"sound_pressure": "[%key:component::sensor::entity_component::sound_pressure::name%]",
"speed": "[%key:component::sensor::entity_component::speed::name%]",
"sulphur_dioxide": "[%key:component::sensor::entity_component::sulphur_dioxide::name%]",
"temperature": "[%key:component::sensor::entity_component::temperature::name%]",
"timestamp": "[%key:component::sensor::entity_component::timestamp::name%]",
"volatile_organic_compounds": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]",
"volatile_organic_compounds_parts": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]",
"voltage": "[%key:component::sensor::entity_component::voltage::name%]",
"volume": "[%key:component::sensor::entity_component::volume::name%]",
"volume_flow_rate": "[%key:component::sensor::entity_component::volume_flow_rate::name%]",
"volume_storage": "[%key:component::sensor::entity_component::volume_storage::name%]",
"water": "[%key:component::sensor::entity_component::water::name%]",
"weight": "[%key:component::sensor::entity_component::weight::name%]",
"wind_direction": "[%key:component::sensor::entity_component::wind_direction::name%]",
"wind_speed": "[%key:component::sensor::entity_component::wind_speed::name%]"
}
},
Comment on lines +396 to +455
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a plan for how we can avoid having to add this list to integrations? Can we maybe allow a translation_domain or something on selector?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have asked @joostlek if he had an idea, else I'll pick up this up in a follow up PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it has to be done in a separate PR.

"platform": {
"options": {
"notify": "Notify"
"notify": "Notify",
"sensor": "Sensor"
}
},
"set_ca_cert": {
Expand All @@ -353,6 +465,13 @@
"auto": "Auto",
"custom": "Custom"
}
},
"state_class": {
"options": {
"measurement": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::measurement%]",
"total": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total%]",
"total_increasing": "[%key:component::sensor::entity_component::_::state_attributes::state_class::state::total_increasing%]"
}
}
},
"services": {
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/mqtt/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,3 +411,9 @@ def migrate_certificate_file_to_content(file_name_or_auto: str) -> str | None:
return certificate_file.read()
except OSError:
return None


@callback
def learn_more_url(platform: str) -> str:
"""Return the URL for the platform specific MQTT documentation."""
return f"https://www.home-assistant.io/integrations/{platform}.mqtt/"
74 changes: 71 additions & 3 deletions tests/components/mqtt/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
"name": "Milkman alert",
"qos": 0,
"command_topic": "test-topic",
"command_template": "{{ value_json.value }}",
"command_template": "{{ value }}",
"entity_picture": "https://example.com/363a7ecad6be4a19b939a016ea93e994",
"retain": False,
},
Expand All @@ -91,12 +91,47 @@
"platform": "notify",
"qos": 0,
"command_topic": "test-topic",
"command_template": "{{ value_json.value }}",
"command_template": "{{ value }}",
"entity_picture": "https://example.com/5269352dd9534c908d22812ea5d714cd",
"retain": False,
},
}

MOCK_SUBENTRY_SENSOR_COMPONENT = {
"e9261f6feed443e7b7d5f3fbe2a47412": {
"platform": "sensor",
"name": "Energy",
"device_class": "enum",
"qos": 1,
"state_topic": "test-topic",
"options": ["low", "medium", "high"],
"expire_after": 30,
"value_template": "{{ value_json.value }}",
"entity_picture": "https://example.com/e9261f6feed443e7b7d5f3fbe2a47412",
},
}
MOCK_SUBENTRY_SENSOR_COMPONENT_STATE_CLASS = {
"a0f85790a95d4889924602effff06b6e": {
"platform": "sensor",
"name": "Energy",
"state_class": "measurement",
"state_topic": "test-topic",
"entity_picture": "https://example.com/a0f85790a95d4889924602effff06b6e",
"qos": 0,
},
}
MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET = {
"e9261f6feed443e7b7d5f3fbe2a47412": {
"platform": "sensor",
"name": "Energy",
"state_class": "total",
"last_reset_value_template": "{{ value_json.value }}",
"state_topic": "test-topic",
"entity_picture": "https://example.com/e9261f6feed443e7b7d5f3fbe2a47412",
"qos": 0,
},
}

# Bogus light component just for code coverage
# Note that light cannot be setup through the UI yet
# The test is for code coverage
Expand Down Expand Up @@ -151,7 +186,7 @@
},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT1,
}
MOCK_SUBENTRY_DATA_NOTIFY_NO_NAME = {
MOCK_NOTIFY_SUBENTRY_DATA_NO_NAME = {
"device": {
"name": "Milk notifier",
"sw_version": "1.0",
Expand All @@ -162,6 +197,39 @@
},
"components": MOCK_SUBENTRY_NOTIFY_COMPONENT_NO_NAME,
}
MOCK_SENSOR_SUBENTRY_DATA_SINGLE = {
"device": {
"name": "Test sensor",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"components": MOCK_SUBENTRY_SENSOR_COMPONENT,
}
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_STATE_CLASS = {
"device": {
"name": "Test sensor",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_STATE_CLASS,
}
MOCK_SENSOR_SUBENTRY_DATA_SINGLE_LAST_RESET_TEMPLATE = {
"device": {
"name": "Test sensor",
"sw_version": "1.0",
"hw_version": "2.1 rev a",
"model": "Model XL",
"model_id": "mn002",
"configuration_url": "https://example.com",
},
"components": MOCK_SUBENTRY_SENSOR_COMPONENT_LAST_RESET,
}

MOCK_SUBENTRY_DATA_BAD_COMPONENT_SCHEMA = {
"device": {
Expand Down
Loading