diff --git a/.coveragerc b/.coveragerc index 70d9fd5e0e2..984adba59f7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -312,7 +312,6 @@ omit = homeassistant/components/esphome/domain_data.py homeassistant/components/esphome/entry_data.py homeassistant/components/esphome/light.py - homeassistant/components/esphome/number.py homeassistant/components/esphome/switch.py homeassistant/components/etherscan/sensor.py homeassistant/components/eufy/* diff --git a/homeassistant/components/esphome/number.py b/homeassistant/components/esphome/number.py index e876fe412f6..4e3d052e6ef 100644 --- a/homeassistant/components/esphome/number.py +++ b/homeassistant/components/esphome/number.py @@ -74,9 +74,7 @@ class EsphomeNumber(EsphomeEntity[NumberInfo, NumberState], NumberEntity): def native_value(self) -> float | None: """Return the state of the entity.""" state = self._state - if math.isnan(state.state): - return None - if state.missing_state: + if state.missing_state or math.isnan(state.state): return None return state.state diff --git a/tests/components/esphome/test_number.py b/tests/components/esphome/test_number.py new file mode 100644 index 00000000000..8157c5f5c3d --- /dev/null +++ b/tests/components/esphome/test_number.py @@ -0,0 +1,91 @@ +"""Test ESPHome numbers.""" + +import math +from unittest.mock import call + +from aioesphomeapi import ( + APIClient, + NumberInfo, + NumberMode as ESPHomeNumberMode, + NumberState, +) + +from homeassistant.components.number import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + + +async def test_generic_number_entity( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic number entity.""" + 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="%", + ) + ] + states = [NumberState(key=1, state=50)] + 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_my_number") + assert state is not None + assert state.state == "50" + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "number.test_my_number", ATTR_VALUE: 50}, + blocking=True, + ) + mock_client.number_command.assert_has_calls([call(1, 50)]) + mock_client.number_command.reset_mock() + + +async def test_generic_number_nan( + 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=math.nan)] + 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_my_number") + assert state is not None + assert state.state == STATE_UNKNOWN diff --git a/tests/components/esphome/test_sensor.py b/tests/components/esphome/test_sensor.py index 5517198341a..8f4eb0f9513 100644 --- a/tests/components/esphome/test_sensor.py +++ b/tests/components/esphome/test_sensor.py @@ -1,6 +1,9 @@ """Test ESPHome sensors.""" +import math + from aioesphomeapi import ( APIClient, + EntityCategory as ESPHomeEntityCategory, LastResetType, SensorInfo, SensorState, @@ -10,8 +13,10 @@ from aioesphomeapi import ( ) from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass -from homeassistant.const import STATE_UNKNOWN +from homeassistant.const import ATTR_ICON, STATE_UNKNOWN from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.entity import EntityCategory async def test_generic_numeric_sensor( @@ -41,6 +46,41 @@ async def test_generic_numeric_sensor( assert state.state == "50" +async def test_generic_numeric_sensor_with_entity_category_and_icon( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic sensor entity.""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + entity_category=ESPHomeEntityCategory.CONFIG, + icon="mdi:leaf", + ) + ] + states = [SensorState(key=1, state=50)] + 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_my_sensor") + assert state is not None + assert state.state == "50" + assert state.attributes[ATTR_ICON] == "mdi:leaf" + entity_reg = er.async_get(hass) + entry = entity_reg.async_get("sensor.test_my_sensor") + assert entry is not None + assert entry.unique_id == "my_sensor" + assert entry.entity_category is EntityCategory.CONFIG + + async def test_generic_numeric_sensor_state_class_measurement( hass: HomeAssistant, mock_client: APIClient, @@ -70,6 +110,11 @@ async def test_generic_numeric_sensor_state_class_measurement( assert state is not None assert state.state == "50" assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT + entity_reg = er.async_get(hass) + entry = entity_reg.async_get("sensor.test_my_sensor") + assert entry is not None + assert entry.unique_id == "my_sensor" + assert entry.entity_category is None async def test_generic_numeric_sensor_device_class_timestamp( @@ -130,6 +175,56 @@ async def test_generic_numeric_sensor_legacy_last_reset_convert( assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.TOTAL_INCREASING +async def test_generic_numeric_sensor_no_state( + hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry +) -> None: + """Test a generic numeric sensor that has no state.""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + ) + ] + states = [] + 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_my_sensor") + assert state is not None + assert state.state == STATE_UNKNOWN + + +async def test_generic_numeric_sensor_nan_state( + hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry +) -> None: + """Test a generic numeric sensor that has nan state.""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + ) + ] + states = [SensorState(key=1, state=math.nan, 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_my_sensor") + assert state is not None + assert state.state == STATE_UNKNOWN + + async def test_generic_numeric_sensor_missing_state( hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry ) -> None: