diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index b9840fb2b68..06fc0a1c493 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -220,31 +220,33 @@ def get_accessory( # noqa: C901 a_type = "TemperatureSensor" elif device_class == SensorDeviceClass.HUMIDITY and unit == PERCENTAGE: a_type = "HumiditySensor" - elif ( - device_class == SensorDeviceClass.PM10 - or SensorDeviceClass.PM10 in state.entity_id - ): + elif device_class == SensorDeviceClass.PM10: a_type = "PM10Sensor" - elif ( - device_class == SensorDeviceClass.PM25 - or SensorDeviceClass.PM25 in state.entity_id - ): + elif device_class == SensorDeviceClass.PM25: a_type = "PM25Sensor" elif device_class == SensorDeviceClass.NITROGEN_DIOXIDE: a_type = "NitrogenDioxideSensor" elif device_class == SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS: a_type = "VolatileOrganicCompoundsSensor" - elif ( - device_class == SensorDeviceClass.GAS - or SensorDeviceClass.GAS in state.entity_id - ): + elif device_class == SensorDeviceClass.GAS: a_type = "AirQualitySensor" elif device_class == SensorDeviceClass.CO: a_type = "CarbonMonoxideSensor" - elif device_class == SensorDeviceClass.CO2 or "co2" in state.entity_id: + elif device_class == SensorDeviceClass.CO2: a_type = "CarbonDioxideSensor" elif device_class == SensorDeviceClass.ILLUMINANCE or unit == LIGHT_LUX: a_type = "LightSensor" + + # Fallbacks based on entity_id + elif SensorDeviceClass.PM10 in state.entity_id: + a_type = "PM10Sensor" + elif SensorDeviceClass.PM25 in state.entity_id: + a_type = "PM25Sensor" + elif SensorDeviceClass.GAS in state.entity_id: + a_type = "AirQualitySensor" + elif "co2" in state.entity_id: + a_type = "CarbonDioxideSensor" + else: _LOGGER.debug( "%s: Unsupported sensor type (device_class=%s) (unit=%s)", diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index de5cda71513..77029f5d5dc 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -20,6 +20,13 @@ from homeassistant.components.homekit.const import ( TYPE_SWITCH, TYPE_VALVE, ) +from homeassistant.components.homekit.type_sensors import ( + AirQualitySensor, + CarbonDioxideSensor, + PM10Sensor, + PM25Sensor, + TemperatureSensor, +) from homeassistant.components.media_player import ( MediaPlayerDeviceClass, MediaPlayerEntityFeature, @@ -42,6 +49,20 @@ from homeassistant.const import ( from homeassistant.core import State +def get_identified_type(entity_id, attrs, config=None): + """Helper to return the accessory type name selected by get_accessory.""" + + def passthrough(type: type): + return lambda *args, **kwargs: type + + # Patch TYPES so that get_accessory returns a type instead of an instance. + with patch.dict( + TYPES, {type_name: passthrough(v) for type_name, v in TYPES.items()} + ): + entity_state = State(entity_id, "irrelevant", attrs) + return get_accessory(None, None, entity_state, 2, config or {}) + + def test_not_supported(caplog: pytest.LogCaptureFixture) -> None: """Test if none is returned if entity isn't supported.""" # not supported entity @@ -425,3 +446,58 @@ def test_type_camera(type_name, entity_id, state, attrs) -> None: entity_state = State(entity_id, state, attrs) get_accessory(None, None, entity_state, 2, {}) assert mock_type.called + + +@pytest.mark.parametrize( + ("expected_type", "entity_id", "attrs"), + [ + ( + PM10Sensor, + "sensor.air_quality_pm25", + {ATTR_DEVICE_CLASS: SensorDeviceClass.PM10}, + ), + ( + PM25Sensor, + "sensor.air_quality_pm10", + {ATTR_DEVICE_CLASS: SensorDeviceClass.PM25}, + ), + ( + AirQualitySensor, + "sensor.co2_sensor", + {ATTR_DEVICE_CLASS: SensorDeviceClass.GAS}, + ), + ( + CarbonDioxideSensor, + "sensor.air_quality_gas", + {ATTR_DEVICE_CLASS: SensorDeviceClass.CO2}, + ), + ( + TemperatureSensor, + "sensor.random_sensor", + {ATTR_DEVICE_CLASS: SensorDeviceClass.TEMPERATURE}, + ), + ], +) +def test_explicit_device_class_takes_precedence( + expected_type, entity_id, attrs +) -> None: + """Test that explicit device_class takes precedence over entity_id hints.""" + identified_type = get_identified_type(entity_id, attrs=attrs) + assert identified_type == expected_type + + +@pytest.mark.parametrize( + ("expected_type", "entity_id", "attrs"), + [ + (PM10Sensor, "sensor.air_quality_pm10", {}), + (PM25Sensor, "sensor.air_quality_pm25", {}), + (AirQualitySensor, "sensor.air_quality_gas", {}), + (CarbonDioxideSensor, "sensor.airmeter_co2", {}), + ], +) +def test_entity_id_fallback_when_no_device_class( + expected_type, entity_id, attrs +) -> None: + """Test that entity_id is used as fallback when device_class is not set.""" + identified_type = get_identified_type(entity_id, attrs=attrs) + assert identified_type == expected_type