From b77de2abafc61d65511022c4f58c0ed1a2c0d821 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 14 Jul 2023 12:14:32 -1000 Subject: [PATCH] Handle empty strings for ESPHome UOMs (#96556) --- homeassistant/components/esphome/number.py | 5 +++- homeassistant/components/esphome/sensor.py | 5 +++- tests/components/esphome/test_number.py | 35 +++++++++++++++++++++- tests/components/esphome/test_sensor.py | 29 +++++++++++++++++- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index 4e3d052e6ef..6be1822f90f 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -63,7 +63,10 @@ class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity): self._attr_native_min_value = static_info.min_value self._attr_native_max_value = static_info.max_value self._attr_native_step = static_info.step - self._attr_native_unit_of_measurement = static_info.unit_of_measurement + # protobuf doesn't support nullable strings so we need to check + # if the string is empty + if unit_of_measurement := static_info.unit_of_measurement: + self._attr_native_unit_of_measurement = unit_of_measurement if mode := static_info.mode: self._attr_mode = NUMBER_MODES.from_esphome(mode) else: diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 3185a5eb536..2e658389e03 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -76,7 +76,10 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity): super()._on_static_info_update(static_info) static_info = self._static_info self._attr_force_update = static_info.force_update - self._attr_native_unit_of_measurement = static_info.unit_of_measurement + # protobuf doesn't support nullable strings so we need to check + # if the string is empty + if unit_of_measurement := static_info.unit_of_measurement: + self._attr_native_unit_of_measurement = unit_of_measurement self._attr_device_class = try_parse_enum( SensorDeviceClass, static_info.device_class ) diff --git a/tests/components/esphome/test_number.py b/tests/components/esphome/test_number.py index cf3ee4876a8..dc90d1c1098 100644 --- a/tests/components/esphome/test_number.py +++ b/tests/components/esphome/test_number.py @@ -15,7 +15,7 @@ from homeassistant.components.number import ( DOMAIN as NUMBER_DOMAIN, SERVICE_SET_VALUE, ) -from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN from homeassistant.core import HomeAssistant @@ -89,3 +89,36 @@ async def test_generic_number_nan( state = hass.states.get("number.test_mynumber") assert state is not None assert state.state == STATE_UNKNOWN + + +async def test_generic_number_with_unit_of_measurement_as_empty_string( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic number entity with nan state.""" + entity_info = [ + NumberInfo( + object_id="mynumber", + key=1, + name="my number", + unique_id="my_number", + max_value=100, + min_value=0, + step=1, + unit_of_measurement="", + mode=ESPHomeNumberMode.SLIDER, + ) + ] + states = [NumberState(key=1, state=42)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + state = hass.states.get("number.test_mynumber") + assert state is not None + assert state.state == "42" + assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes diff --git a/tests/components/esphome/test_sensor.py b/tests/components/esphome/test_sensor.py index 9a1863c3c90..6c034e674ee 100644 --- a/tests/components/esphome/test_sensor.py +++ b/tests/components/esphome/test_sensor.py @@ -13,7 +13,7 @@ from aioesphomeapi import ( ) from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass -from homeassistant.const import ATTR_ICON, STATE_UNKNOWN +from homeassistant.const import ATTR_ICON, ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er from homeassistant.helpers.entity import EntityCategory @@ -275,3 +275,30 @@ async def test_generic_text_sensor( state = hass.states.get("sensor.test_mysensor") assert state is not None assert state.state == "i am a teapot" + + +async def test_generic_numeric_sensor_empty_string_uom( + hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry +) -> None: + """Test a generic numeric sensor that has an empty string as the uom.""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + unit_of_measurement="", + ) + ] + states = [SensorState(key=1, state=123, missing_state=False)] + user_service = [] + await mock_generic_device_entry( + mock_client=mock_client, + entity_info=entity_info, + user_service=user_service, + states=states, + ) + state = hass.states.get("sensor.test_mysensor") + assert state is not None + assert state.state == "123" + assert ATTR_UNIT_OF_MEASUREMENT not in state.attributes