diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index bb63ae5fef0..9ba6308e07c 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -18,6 +18,7 @@ from homeassistant.components.sensor import ( RestoreSensor, SensorDeviceClass, SensorExtraStoredData, + SensorStateClass, ) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( @@ -84,11 +85,27 @@ _PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend( } ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) + +def validate_sensor_state_class_config(config: ConfigType) -> ConfigType: + """Validate the sensor state class config.""" + if ( + CONF_LAST_RESET_VALUE_TEMPLATE in config + and (state_class := config.get(CONF_STATE_CLASS)) != SensorStateClass.TOTAL + ): + raise vol.Invalid( + f"The option `{CONF_LAST_RESET_VALUE_TEMPLATE}` cannot be used " + f"together with state class `{state_class}`" + ) + + return config + + PLATFORM_SCHEMA_MODERN = vol.All( # Deprecated in HA Core 2021.11.0 https://github.com/home-assistant/core/pull/54840 # Removed in HA Core 2023.6.0 cv.removed(CONF_LAST_RESET_TOPIC), _PLATFORM_SCHEMA_BASE, + validate_sensor_state_class_config, ) DISCOVERY_SCHEMA = vol.All( @@ -96,6 +113,7 @@ DISCOVERY_SCHEMA = vol.All( # Removed in HA Core 2023.6.0 cv.removed(CONF_LAST_RESET_TOPIC), _PLATFORM_SCHEMA_BASE.extend({}, extra=vol.REMOVE_EXTRA), + validate_sensor_state_class_config, ) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index d88bd3a544a..5ab4b660963 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -1449,6 +1449,7 @@ async def test_entity_name( DEFAULT_CONFIG, ( { + "state_class": "total", "availability_topic": "availability-topic", "json_attributes_topic": "json-attributes-topic", "value_template": "{{ value_json.state }}", @@ -1491,6 +1492,7 @@ async def test_skipped_async_ha_write_state( DEFAULT_CONFIG, ( { + "state_class": "total", "value_template": "{{ value_json.some_var * 1 }}", "last_reset_value_template": "{{ value_json.some_var * 2 }}", }, @@ -1510,3 +1512,62 @@ async def test_value_template_fails( "TypeError: unsupported operand type(s) for *: 'NoneType' and 'int' rendering template" in caplog.text ) + + +@pytest.mark.parametrize( + "hass_config", + [ + help_custom_config( + sensor.DOMAIN, + DEFAULT_CONFIG, + ( + { + "state_class": "total_increasing", + "last_reset_value_template": "{{ value_json.last_reset }}", + }, + ), + ), + help_custom_config( + sensor.DOMAIN, + DEFAULT_CONFIG, + ( + { + "state_class": "measurement", + "last_reset_value_template": "{{ value_json.last_reset }}", + }, + ), + ), + help_custom_config( + sensor.DOMAIN, + DEFAULT_CONFIG, + ( + { + "last_reset_value_template": "{{ value_json.last_reset }}", + }, + ), + ), + ], +) +async def test_value_incorrect_state_class_config( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + hass_config: ConfigType, +) -> None: + """Test a sensor config with incorrect state_class config fails from yaml or discovery.""" + await mqtt_mock_entry() + assert ( + "The option `last_reset_value_template` cannot be used together with state class" + in caplog.text + ) + caplog.clear() + + config_payload = hass_config[mqtt.DOMAIN][sensor.DOMAIN][0] + async_fire_mqtt_message( + hass, "homeassistant/sensor/bla/config", json.dumps(config_payload) + ) + await hass.async_block_till_done() + assert ( + "The option `last_reset_value_template` cannot be used together with state class" + in caplog.text + )