Allow None device_class and UOM for mqtt entities (#91240)

* Allow None device_class and UOM for mqtt entities

* Rever not needed changes

* Revert another unwanted change
This commit is contained in:
Jan Bouwhuis 2023-04-12 19:14:16 +02:00 committed by GitHub
parent 325733158d
commit e36fd5f222
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 190 additions and 76 deletions

View file

@ -125,7 +125,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
vol.Optional( vol.Optional(
CONF_DEVICE_CLASS, default=HumidifierDeviceClass.HUMIDIFIER CONF_DEVICE_CLASS, default=HumidifierDeviceClass.HUMIDIFIER
): vol.In( ): vol.In(
[HumidifierDeviceClass.HUMIDIFIER, HumidifierDeviceClass.DEHUMIDIFIER] [HumidifierDeviceClass.HUMIDIFIER, HumidifierDeviceClass.DEHUMIDIFIER, None]
), ),
vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic,

View file

@ -97,7 +97,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All( vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All(
vol.Coerce(float), vol.Range(min=1e-3) vol.Coerce(float), vol.Range(min=1e-3)
), ),
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.Any(cv.string, None),
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}, },
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)

View file

@ -107,7 +107,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_SUGGESTED_DISPLAY_PRECISION): cv.positive_int, vol.Optional(CONF_SUGGESTED_DISPLAY_PRECISION): cv.positive_int,
vol.Optional(CONF_STATE_CLASS): vol.Any(STATE_CLASSES_SCHEMA, None), vol.Optional(CONF_STATE_CLASS): vol.Any(STATE_CLASSES_SCHEMA, None),
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.Any(cv.string, None),
} }
).extend(MQTT_ENTITY_COMMON_SCHEMA.schema) ).extend(MQTT_ENTITY_COMMON_SCHEMA.schema)

View file

@ -505,8 +505,9 @@ async def test_setting_sensor_value_via_mqtt_message_empty_template(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"hass_config", ("hass_config", "device_class"),
[ [
(
{ {
mqtt.DOMAIN: { mqtt.DOMAIN: {
binary_sensor.DOMAIN: { binary_sensor.DOMAIN: {
@ -515,17 +516,33 @@ async def test_setting_sensor_value_via_mqtt_message_empty_template(
"state_topic": "test-topic", "state_topic": "test-topic",
} }
} }
},
"motion",
),
(
{
mqtt.DOMAIN: {
binary_sensor.DOMAIN: {
"name": "test",
"device_class": None,
"state_topic": "test-topic",
} }
}
},
None,
),
], ],
) )
async def test_valid_device_class( async def test_valid_device_class(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | None,
) -> None: ) -> None:
"""Test the setting of a valid sensor class.""" """Test the setting of a valid sensor class and ignoring an empty device_class."""
await mqtt_mock_entry() await mqtt_mock_entry()
state = hass.states.get("binary_sensor.test") state = hass.states.get("binary_sensor.test")
assert state.attributes.get("device_class") == "motion" assert state.attributes.get("device_class") == device_class
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -477,6 +477,11 @@ async def test_invalid_device_class(
"name": "Test 3", "name": "Test 3",
"command_topic": "test-topic", "command_topic": "test-topic",
}, },
{
"name": "Test 4",
"command_topic": "test-topic",
"device_class": None,
},
] ]
} }
} }

View file

@ -867,6 +867,19 @@ async def test_attributes(
}, },
True, True,
), ),
(
{
mqtt.DOMAIN: {
humidifier.DOMAIN: {
"name": "test_valid_4",
"command_topic": "command-topic",
"target_humidity_command_topic": "humidity-command-topic",
"device_class": None,
}
}
},
True,
),
( (
{ {
mqtt.DOMAIN: { mqtt.DOMAIN: {

View file

@ -18,7 +18,6 @@ from homeassistant.components.number import (
ATTR_VALUE, ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN, DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
NumberDeviceClass,
) )
from homeassistant.const import ( from homeassistant.const import (
ATTR_ASSUMED_STATE, ATTR_ASSUMED_STATE,
@ -76,8 +75,9 @@ def number_platform_only():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"hass_config", ("hass_config", "device_class", "unit_of_measurement", "values"),
[ [
(
{ {
mqtt.DOMAIN: { mqtt.DOMAIN: {
number.DOMAIN: { number.DOMAIN: {
@ -89,32 +89,66 @@ def number_platform_only():
"payload_reset": "reset!", "payload_reset": "reset!",
} }
} }
},
"temperature",
UnitOfTemperature.CELSIUS.value,
[("10", "-12.0"), ("20.5", "-6.4")], # 10 °F -> -12 °C
),
(
{
mqtt.DOMAIN: {
number.DOMAIN: {
"state_topic": "test/state_number",
"command_topic": "test/cmd_number",
"name": "Test Number",
"device_class": "temperature",
"unit_of_measurement": UnitOfTemperature.CELSIUS.value,
"payload_reset": "reset!",
} }
}
},
"temperature",
UnitOfTemperature.CELSIUS.value,
[("10", "10"), ("15", "15")],
),
(
{
mqtt.DOMAIN: {
number.DOMAIN: {
"state_topic": "test/state_number",
"command_topic": "test/cmd_number",
"name": "Test Number",
"device_class": None,
"unit_of_measurement": None,
"payload_reset": "reset!",
}
}
},
None,
None,
[("10", "10"), ("20", "20")],
),
], ],
) )
async def test_run_number_setup( async def test_run_number_setup(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | None,
unit_of_measurement: UnitOfTemperature | None,
values: list[tuple[str, str]],
) -> None: ) -> None:
"""Test that it fetches the given payload.""" """Test that it fetches the given payload."""
await mqtt_mock_entry() await mqtt_mock_entry()
async_fire_mqtt_message(hass, "test/state_number", "10") for payload, value in values:
async_fire_mqtt_message(hass, "test/state_number", payload)
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("number.test_number") state = hass.states.get("number.test_number")
assert state.state == "-12.0" # 10 °F -> -12 °C assert state.state == value
assert state.attributes.get(ATTR_DEVICE_CLASS) == NumberDeviceClass.TEMPERATURE assert state.attributes.get(ATTR_DEVICE_CLASS) == device_class
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "°C" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == unit_of_measurement
async_fire_mqtt_message(hass, "test/state_number", "20.5")
await hass.async_block_till_done()
state = hass.states.get("number.test_number")
assert state.state == "-6.4" # 20.5 °F -> -6.4 °C
assert state.attributes.get(ATTR_DEVICE_CLASS) == NumberDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "°C"
async_fire_mqtt_message(hass, "test/state_number", "reset!") async_fire_mqtt_message(hass, "test/state_number", "reset!")
@ -122,8 +156,8 @@ async def test_run_number_setup(
state = hass.states.get("number.test_number") state = hass.states.get("number.test_number")
assert state.state == "unknown" assert state.state == "unknown"
assert state.attributes.get(ATTR_DEVICE_CLASS) == NumberDeviceClass.TEMPERATURE assert state.attributes.get(ATTR_DEVICE_CLASS) == device_class
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "°C" assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == unit_of_measurement
@pytest.mark.parametrize( @pytest.mark.parametrize(

View file

@ -851,16 +851,17 @@ async def test_invalid_device_class(
"name": "Test 3", "name": "Test 3",
"state_topic": "test-topic", "state_topic": "test-topic",
"device_class": None, "device_class": None,
"unit_of_measurement": None,
}, },
] ]
} }
} }
], ],
) )
async def test_valid_device_class( async def test_valid_device_class_and_uom(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
) -> None: ) -> None:
"""Test device_class option with valid values.""" """Test device_class option with valid values and test with an empty unit of measurement."""
await mqtt_mock_entry() await mqtt_mock_entry()
state = hass.states.get("sensor.test_1") state = hass.states.get("sensor.test_1")

View file

@ -62,8 +62,9 @@ def switch_platform_only():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"hass_config", ("hass_config", "device_class"),
[ [
(
{ {
mqtt.DOMAIN: { mqtt.DOMAIN: {
switch.DOMAIN: { switch.DOMAIN: {
@ -75,18 +76,37 @@ def switch_platform_only():
"device_class": "switch", "device_class": "switch",
} }
} }
},
"switch",
),
(
{
mqtt.DOMAIN: {
switch.DOMAIN: {
"name": "test",
"state_topic": "state-topic",
"command_topic": "command-topic",
"payload_on": 1,
"payload_off": 0,
"device_class": None,
} }
}
},
None,
),
], ],
) )
async def test_controlling_state_via_topic( async def test_controlling_state_via_topic(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | None,
) -> None: ) -> None:
"""Test the controlling state via topic.""" """Test the controlling state via topic."""
await mqtt_mock_entry() await mqtt_mock_entry()
state = hass.states.get("switch.test") state = hass.states.get("switch.test")
assert state.state == STATE_UNKNOWN assert state.state == STATE_UNKNOWN
assert state.attributes.get(ATTR_DEVICE_CLASS) == "switch" assert state.attributes.get(ATTR_DEVICE_CLASS) == device_class
assert not state.attributes.get(ATTR_ASSUMED_STATE) assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "state-topic", "1") async_fire_mqtt_message(hass, "state-topic", "1")

View file

@ -63,8 +63,9 @@ def update_platform_only():
@pytest.mark.parametrize( @pytest.mark.parametrize(
"hass_config", ("hass_config", "device_class"),
[ [
(
{ {
mqtt.DOMAIN: { mqtt.DOMAIN: {
update.DOMAIN: { update.DOMAIN: {
@ -75,13 +76,35 @@ def update_platform_only():
"release_url": "https://example.com/release", "release_url": "https://example.com/release",
"title": "Test Update Title", "title": "Test Update Title",
"entity_picture": "https://example.com/icon.png", "entity_picture": "https://example.com/icon.png",
"device_class": "firmware",
} }
} }
},
"firmware",
),
(
{
mqtt.DOMAIN: {
update.DOMAIN: {
"state_topic": "test/installed-version",
"latest_version_topic": "test/latest-version",
"name": "Test Update",
"release_summary": "Test release summary",
"release_url": "https://example.com/release",
"title": "Test Update Title",
"entity_picture": "https://example.com/icon.png",
"device_class": None,
} }
}
},
None,
),
], ],
) )
async def test_run_update_setup( async def test_run_update_setup(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | None,
) -> None: ) -> None:
"""Test that it fetches the given payload.""" """Test that it fetches the given payload."""
installed_version_topic = "test/installed-version" installed_version_topic = "test/installed-version"
@ -101,6 +124,7 @@ async def test_run_update_setup(
assert state.attributes.get("release_url") == "https://example.com/release" assert state.attributes.get("release_url") == "https://example.com/release"
assert state.attributes.get("title") == "Test Update Title" assert state.attributes.get("title") == "Test Update Title"
assert state.attributes.get("entity_picture") == "https://example.com/icon.png" assert state.attributes.get("entity_picture") == "https://example.com/icon.png"
assert state.attributes.get("device_class") == device_class
async_fire_mqtt_message(hass, latest_version_topic, "2.0.0") async_fire_mqtt_message(hass, latest_version_topic, "2.0.0")