diff --git a/.coveragerc b/.coveragerc index 2d8405a8dbd..237571a212f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -316,7 +316,6 @@ omit = homeassistant/components/esphome/lock.py homeassistant/components/esphome/media_player.py homeassistant/components/esphome/number.py - homeassistant/components/esphome/sensor.py homeassistant/components/esphome/switch.py homeassistant/components/etherscan/sensor.py homeassistant/components/eufy/* diff --git a/homeassistant/components/esphome/sensor.py b/homeassistant/components/esphome/sensor.py index 47757247557..6c1fca1ffef 100644 --- a/homeassistant/components/esphome/sensor.py +++ b/homeassistant/components/esphome/sensor.py @@ -95,9 +95,7 @@ class EsphomeSensor(EsphomeEntity[SensorInfo, SensorState], SensorEntity): def native_value(self) -> datetime | str | None: """Return the state of the entity.""" state = self._state - if math.isnan(state.state): - return None - if state.missing_state: + if math.isnan(state.state) or state.missing_state: return None if self._attr_device_class == SensorDeviceClass.TIMESTAMP: return dt_util.utc_from_timestamp(state.state) diff --git a/tests/components/esphome/test_sensor.py b/tests/components/esphome/test_sensor.py new file mode 100644 index 00000000000..5517198341a --- /dev/null +++ b/tests/components/esphome/test_sensor.py @@ -0,0 +1,182 @@ +"""Test ESPHome sensors.""" +from aioesphomeapi import ( + APIClient, + LastResetType, + SensorInfo, + SensorState, + SensorStateClass as ESPHomeSensorStateClass, + TextSensorInfo, + TextSensorState, +) + +from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass +from homeassistant.const import STATE_UNKNOWN +from homeassistant.core import HomeAssistant + + +async def test_generic_numeric_sensor( + 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", + ) + ] + 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" + + +async def test_generic_numeric_sensor_state_class_measurement( + 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", + state_class=ESPHomeSensorStateClass.MEASUREMENT, + device_class="power", + unit_of_measurement="W", + ) + ] + 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_STATE_CLASS] == SensorStateClass.MEASUREMENT + + +async def test_generic_numeric_sensor_device_class_timestamp( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a sensor entity that uses timestamp (epoch).""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + device_class="timestamp", + ) + ] + states = [SensorState(key=1, state=1687459432.466624)] + 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 == "2023-06-22T18:43:52+00:00" + + +async def test_generic_numeric_sensor_legacy_last_reset_convert( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a state class of measurement with last reset type of auto is converted to total increasing.""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + last_reset_type=LastResetType.AUTO, + state_class=ESPHomeSensorStateClass.MEASUREMENT, + ) + ] + 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_STATE_CLASS] == SensorStateClass.TOTAL_INCREASING + + +async def test_generic_numeric_sensor_missing_state( + hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry +) -> None: + """Test a generic numeric sensor that is missing state.""" + entity_info = [ + SensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + ) + ] + states = [SensorState(key=1, state=True, missing_state=True)] + 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_text_sensor( + hass: HomeAssistant, + mock_client: APIClient, + mock_generic_device_entry, +) -> None: + """Test a generic text sensor entity.""" + entity_info = [ + TextSensorInfo( + object_id="mysensor", + key=1, + name="my sensor", + unique_id="my_sensor", + ) + ] + states = [TextSensorState(key=1, state="i am a teapot")] + 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 == "i am a teapot"