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(
CONF_DEVICE_CLASS, default=HumidifierDeviceClass.HUMIDIFIER
): vol.In(
[HumidifierDeviceClass.HUMIDIFIER, HumidifierDeviceClass.DEHUMIDIFIER]
[HumidifierDeviceClass.HUMIDIFIER, HumidifierDeviceClass.DEHUMIDIFIER, None]
),
vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
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.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,
},
).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_SUGGESTED_DISPLAY_PRECISION): cv.positive_int,
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)

View file

@ -505,27 +505,44 @@ async def test_setting_sensor_value_via_mqtt_message_empty_template(
@pytest.mark.parametrize(
"hass_config",
("hass_config", "device_class"),
[
{
mqtt.DOMAIN: {
binary_sensor.DOMAIN: {
"name": "test",
"device_class": "motion",
"state_topic": "test-topic",
(
{
mqtt.DOMAIN: {
binary_sensor.DOMAIN: {
"name": "test",
"device_class": "motion",
"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(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | 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()
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(

View file

@ -477,6 +477,11 @@ async def test_invalid_device_class(
"name": "Test 3",
"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,
),
(
{
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: {

View file

@ -18,7 +18,6 @@ from homeassistant.components.number import (
ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE,
NumberDeviceClass,
)
from homeassistant.const import (
ATTR_ASSUMED_STATE,
@ -76,45 +75,80 @@ def number_platform_only():
@pytest.mark.parametrize(
"hass_config",
("hass_config", "device_class", "unit_of_measurement", "values"),
[
{
mqtt.DOMAIN: {
number.DOMAIN: {
"state_topic": "test/state_number",
"command_topic": "test/cmd_number",
"name": "Test Number",
"device_class": "temperature",
"unit_of_measurement": UnitOfTemperature.FAHRENHEIT.value,
"payload_reset": "reset!",
(
{
mqtt.DOMAIN: {
number.DOMAIN: {
"state_topic": "test/state_number",
"command_topic": "test/cmd_number",
"name": "Test Number",
"device_class": "temperature",
"unit_of_measurement": UnitOfTemperature.FAHRENHEIT.value,
"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(
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:
"""Test that it fetches the given payload."""
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")
assert state.state == "-12.0" # 10 °F -> -12 °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", "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"
state = hass.states.get("number.test_number")
assert state.state == value
assert state.attributes.get(ATTR_DEVICE_CLASS) == device_class
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == unit_of_measurement
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")
assert state.state == "unknown"
assert state.attributes.get(ATTR_DEVICE_CLASS) == NumberDeviceClass.TEMPERATURE
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == "°C"
assert state.attributes.get(ATTR_DEVICE_CLASS) == device_class
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == unit_of_measurement
@pytest.mark.parametrize(

View file

@ -851,16 +851,17 @@ async def test_invalid_device_class(
"name": "Test 3",
"state_topic": "test-topic",
"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
) -> 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()
state = hass.states.get("sensor.test_1")

View file

@ -62,31 +62,51 @@ def switch_platform_only():
@pytest.mark.parametrize(
"hass_config",
("hass_config", "device_class"),
[
{
mqtt.DOMAIN: {
switch.DOMAIN: {
"name": "test",
"state_topic": "state-topic",
"command_topic": "command-topic",
"payload_on": 1,
"payload_off": 0,
"device_class": "switch",
(
{
mqtt.DOMAIN: {
switch.DOMAIN: {
"name": "test",
"state_topic": "state-topic",
"command_topic": "command-topic",
"payload_on": 1,
"payload_off": 0,
"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(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | None,
) -> None:
"""Test the controlling state via topic."""
await mqtt_mock_entry()
state = hass.states.get("switch.test")
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)
async_fire_mqtt_message(hass, "state-topic", "1")

View file

@ -63,25 +63,48 @@ def update_platform_only():
@pytest.mark.parametrize(
"hass_config",
("hass_config", "device_class"),
[
{
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",
(
{
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": "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(
hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator
hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator,
device_class: str | None,
) -> None:
"""Test that it fetches the given payload."""
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("title") == "Test Update Title"
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")