441 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			441 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Python
		
	
	
"""Test ESPHome sensors."""
 | 
						|
 | 
						|
from collections.abc import Awaitable, Callable
 | 
						|
import logging
 | 
						|
import math
 | 
						|
 | 
						|
from aioesphomeapi import (
 | 
						|
    APIClient,
 | 
						|
    EntityCategory as ESPHomeEntityCategory,
 | 
						|
    EntityInfo,
 | 
						|
    EntityState,
 | 
						|
    LastResetType,
 | 
						|
    SensorInfo,
 | 
						|
    SensorState,
 | 
						|
    SensorStateClass as ESPHomeSensorStateClass,
 | 
						|
    TextSensorInfo,
 | 
						|
    TextSensorState,
 | 
						|
    UserService,
 | 
						|
)
 | 
						|
 | 
						|
from homeassistant.components.sensor import (
 | 
						|
    ATTR_STATE_CLASS,
 | 
						|
    SensorDeviceClass,
 | 
						|
    SensorStateClass,
 | 
						|
)
 | 
						|
from homeassistant.const import (
 | 
						|
    ATTR_DEVICE_CLASS,
 | 
						|
    ATTR_ICON,
 | 
						|
    ATTR_UNIT_OF_MEASUREMENT,
 | 
						|
    STATE_UNKNOWN,
 | 
						|
    EntityCategory,
 | 
						|
)
 | 
						|
from homeassistant.core import HomeAssistant
 | 
						|
from homeassistant.helpers import entity_registry as er
 | 
						|
 | 
						|
from .conftest import MockESPHomeDevice
 | 
						|
 | 
						|
 | 
						|
async def test_generic_numeric_sensor(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_client: APIClient,
 | 
						|
    mock_esphome_device: Callable[
 | 
						|
        [APIClient, list[EntityInfo], list[UserService], list[EntityState]],
 | 
						|
        Awaitable[MockESPHomeDevice],
 | 
						|
    ],
 | 
						|
) -> None:
 | 
						|
    """Test a generic sensor entity."""
 | 
						|
    logging.getLogger("homeassistant.components.esphome").setLevel(logging.DEBUG)
 | 
						|
    entity_info = [
 | 
						|
        SensorInfo(
 | 
						|
            object_id="mysensor",
 | 
						|
            key=1,
 | 
						|
            name="my sensor",
 | 
						|
            unique_id="my_sensor",
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [SensorState(key=1, state=50)]
 | 
						|
    user_service = []
 | 
						|
    mock_device = await mock_esphome_device(
 | 
						|
        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 == "50"
 | 
						|
 | 
						|
    # Test updating state
 | 
						|
    mock_device.set_state(SensorState(key=1, state=60))
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    state = hass.states.get("sensor.test_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "60"
 | 
						|
 | 
						|
    # Test sending the same state again
 | 
						|
    mock_device.set_state(SensorState(key=1, state=60))
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    state = hass.states.get("sensor.test_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "60"
 | 
						|
 | 
						|
    # Test we can still update after the same state
 | 
						|
    mock_device.set_state(SensorState(key=1, state=70))
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    state = hass.states.get("sensor.test_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "70"
 | 
						|
 | 
						|
    # Test invalid data from the underlying api does not crash us
 | 
						|
    mock_device.set_state(SensorState(key=1, state=object()))
 | 
						|
    await hass.async_block_till_done()
 | 
						|
    state = hass.states.get("sensor.test_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "70"
 | 
						|
 | 
						|
 | 
						|
async def test_generic_numeric_sensor_with_entity_category_and_icon(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    entity_registry: er.EntityRegistry,
 | 
						|
    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.DIAGNOSTIC,
 | 
						|
            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_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "50"
 | 
						|
    assert state.attributes[ATTR_ICON] == "mdi:leaf"
 | 
						|
    entry = entity_registry.async_get("sensor.test_mysensor")
 | 
						|
    assert entry is not None
 | 
						|
    # Note that ESPHome includes the EntityInfo type in the unique id
 | 
						|
    # as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
 | 
						|
    assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
 | 
						|
    assert entry.entity_category is EntityCategory.DIAGNOSTIC
 | 
						|
 | 
						|
 | 
						|
async def test_generic_numeric_sensor_state_class_measurement(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    entity_registry: er.EntityRegistry,
 | 
						|
    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_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "50"
 | 
						|
    assert state.attributes[ATTR_STATE_CLASS] == SensorStateClass.MEASUREMENT
 | 
						|
    entry = entity_registry.async_get("sensor.test_mysensor")
 | 
						|
    assert entry is not None
 | 
						|
    # Note that ESPHome includes the EntityInfo type in the unique id
 | 
						|
    # as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
 | 
						|
    assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
 | 
						|
    assert entry.entity_category is None
 | 
						|
 | 
						|
 | 
						|
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_mysensor")
 | 
						|
    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_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "50"
 | 
						|
    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_mysensor")
 | 
						|
    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_mysensor")
 | 
						|
    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:
 | 
						|
    """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_mysensor")
 | 
						|
    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_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == "i am a teapot"
 | 
						|
 | 
						|
 | 
						|
async def test_generic_text_sensor_missing_state(
 | 
						|
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
 | 
						|
) -> None:
 | 
						|
    """Test a generic text sensor that is missing state."""
 | 
						|
    entity_info = [
 | 
						|
        TextSensorInfo(
 | 
						|
            object_id="mysensor",
 | 
						|
            key=1,
 | 
						|
            name="my sensor",
 | 
						|
            unique_id="my_sensor",
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [TextSensorState(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_mysensor")
 | 
						|
    assert state is not None
 | 
						|
    assert state.state == STATE_UNKNOWN
 | 
						|
 | 
						|
 | 
						|
async def test_generic_text_sensor_device_class_timestamp(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_client: APIClient,
 | 
						|
    mock_generic_device_entry,
 | 
						|
) -> None:
 | 
						|
    """Test a sensor entity that uses timestamp (datetime)."""
 | 
						|
    entity_info = [
 | 
						|
        TextSensorInfo(
 | 
						|
            object_id="mysensor",
 | 
						|
            key=1,
 | 
						|
            name="my sensor",
 | 
						|
            unique_id="my_sensor",
 | 
						|
            device_class=SensorDeviceClass.TIMESTAMP,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [TextSensorState(key=1, state="2023-06-22T18:43:52+00:00")]
 | 
						|
    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 == "2023-06-22T18:43:52+00:00"
 | 
						|
    assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
 | 
						|
 | 
						|
 | 
						|
async def test_generic_text_sensor_device_class_date(
 | 
						|
    hass: HomeAssistant,
 | 
						|
    mock_client: APIClient,
 | 
						|
    mock_generic_device_entry,
 | 
						|
) -> None:
 | 
						|
    """Test a sensor entity that uses date (datetime)."""
 | 
						|
    entity_info = [
 | 
						|
        TextSensorInfo(
 | 
						|
            object_id="mysensor",
 | 
						|
            key=1,
 | 
						|
            name="my sensor",
 | 
						|
            unique_id="my_sensor",
 | 
						|
            device_class=SensorDeviceClass.DATE,
 | 
						|
        )
 | 
						|
    ]
 | 
						|
    states = [TextSensorState(key=1, state="2023-06-22T18:43:52+00:00")]
 | 
						|
    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 == "2023-06-22"
 | 
						|
    assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.DATE
 | 
						|
 | 
						|
 | 
						|
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
 |