From d49c5d511b2fc03d66c88d8907dc88e6a9b20514 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Tue, 26 Oct 2021 05:55:03 -0700 Subject: [PATCH] Add entity_category to SmartThings sensors (#58375) --- .../components/smartthings/binary_sensor.py | 9 + .../components/smartthings/sensor.py | 232 +++++++++++++++--- .../smartthings/test_binary_sensor.py | 26 +- tests/components/smartthings/test_sensor.py | 4 + 4 files changed, 234 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 74eb253ebbb..79b4176d9ea 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -15,6 +15,7 @@ from homeassistant.components.binary_sensor import ( DEVICE_CLASS_SOUND, BinarySensorEntity, ) +from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -41,6 +42,9 @@ ATTRIB_TO_CLASS = { Attribute.valve: DEVICE_CLASS_OPENING, Attribute.water: DEVICE_CLASS_MOISTURE, } +ATTRIB_TO_ENTTIY_CATEGORY = { + Attribute.tamper: ENTITY_CATEGORY_DIAGNOSTIC, +} async def async_setup_entry(hass, config_entry, async_add_entities): @@ -88,3 +92,8 @@ class SmartThingsBinarySensor(SmartThingsEntity, BinarySensorEntity): def device_class(self): """Return the class of this device.""" return ATTRIB_TO_CLASS[self._attribute] + + @property + def entity_category(self): + """Return the entity category of this device.""" + return ATTRIB_TO_ENTTIY_CATEGORY.get(self._attribute) diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index f5ab5562229..eab840bd629 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -28,6 +28,8 @@ from homeassistant.const import ( DEVICE_CLASS_VOLTAGE, ELECTRIC_POTENTIAL_VOLT, ENERGY_KILO_WATT_HOUR, + ENTITY_CATEGORY_CONFIG, + ENTITY_CATEGORY_DIAGNOSTIC, LIGHT_LUX, MASS_KILOGRAMS, PERCENTAGE, @@ -40,22 +42,54 @@ from homeassistant.const import ( from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN -Map = namedtuple("map", "attribute name default_unit device_class state_class") +Map = namedtuple( + "map", "attribute name default_unit device_class state_class entity_category" +) CAPABILITY_TO_SENSORS = { Capability.activity_lighting_mode: [ - Map(Attribute.lighting_mode, "Activity Lighting Mode", None, None, None) + Map( + Attribute.lighting_mode, + "Activity Lighting Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) ], Capability.air_conditioner_mode: [ - Map(Attribute.air_conditioner_mode, "Air Conditioner Mode", None, None, None) + Map( + Attribute.air_conditioner_mode, + "Air Conditioner Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) ], Capability.air_quality_sensor: [ - Map(Attribute.air_quality, "Air Quality", "CAQI", None, STATE_CLASS_MEASUREMENT) + Map( + Attribute.air_quality, + "Air Quality", + "CAQI", + None, + STATE_CLASS_MEASUREMENT, + None, + ) + ], + Capability.alarm: [Map(Attribute.alarm, "Alarm", None, None, None, None)], + Capability.audio_volume: [ + Map(Attribute.volume, "Volume", PERCENTAGE, None, None, None) ], - Capability.alarm: [Map(Attribute.alarm, "Alarm", None, None, None)], - Capability.audio_volume: [Map(Attribute.volume, "Volume", PERCENTAGE, None, None)], Capability.battery: [ - Map(Attribute.battery, "Battery", PERCENTAGE, DEVICE_CLASS_BATTERY, None) + Map( + Attribute.battery, + "Battery", + PERCENTAGE, + DEVICE_CLASS_BATTERY, + None, + ENTITY_CATEGORY_DIAGNOSTIC, + ) ], Capability.body_mass_index_measurement: [ Map( @@ -64,6 +98,7 @@ CAPABILITY_TO_SENSORS = { f"{MASS_KILOGRAMS}/{AREA_SQUARE_METERS}", None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.body_weight_measurement: [ @@ -73,6 +108,7 @@ CAPABILITY_TO_SENSORS = { MASS_KILOGRAMS, None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.carbon_dioxide_measurement: [ @@ -82,10 +118,18 @@ CAPABILITY_TO_SENSORS = { CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_CO2, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.carbon_monoxide_detector: [ - Map(Attribute.carbon_monoxide, "Carbon Monoxide Detector", None, None, None) + Map( + Attribute.carbon_monoxide, + "Carbon Monoxide Detector", + None, + None, + None, + None, + ) ], Capability.carbon_monoxide_measurement: [ Map( @@ -94,29 +138,50 @@ CAPABILITY_TO_SENSORS = { CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_CO, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.dishwasher_operating_state: [ - Map(Attribute.machine_state, "Dishwasher Machine State", None, None, None), - Map(Attribute.dishwasher_job_state, "Dishwasher Job State", None, None, None), + Map( + Attribute.machine_state, "Dishwasher Machine State", None, None, None, None + ), + Map( + Attribute.dishwasher_job_state, + "Dishwasher Job State", + None, + None, + None, + None, + ), Map( Attribute.completion_time, "Dishwasher Completion Time", None, DEVICE_CLASS_TIMESTAMP, None, + None, ), ], - Capability.dryer_mode: [Map(Attribute.dryer_mode, "Dryer Mode", None, None, None)], + Capability.dryer_mode: [ + Map( + Attribute.dryer_mode, + "Dryer Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) + ], Capability.dryer_operating_state: [ - Map(Attribute.machine_state, "Dryer Machine State", None, None, None), - Map(Attribute.dryer_job_state, "Dryer Job State", None, None, None), + Map(Attribute.machine_state, "Dryer Machine State", None, None, None, None), + Map(Attribute.dryer_job_state, "Dryer Job State", None, None, None, None), Map( Attribute.completion_time, "Dryer Completion Time", None, DEVICE_CLASS_TIMESTAMP, None, + None, ), ], Capability.dust_sensor: [ @@ -126,8 +191,16 @@ CAPABILITY_TO_SENSORS = { None, None, STATE_CLASS_MEASUREMENT, + None, + ), + Map( + Attribute.dust_level, + "Dust Level", + None, + None, + STATE_CLASS_MEASUREMENT, + None, ), - Map(Attribute.dust_level, "Dust Level", None, None, STATE_CLASS_MEASUREMENT), ], Capability.energy_meter: [ Map( @@ -136,6 +209,7 @@ CAPABILITY_TO_SENSORS = { ENERGY_KILO_WATT_HOUR, DEVICE_CLASS_ENERGY, STATE_CLASS_TOTAL_INCREASING, + None, ) ], Capability.equivalent_carbon_dioxide_measurement: [ @@ -145,6 +219,7 @@ CAPABILITY_TO_SENSORS = { CONCENTRATION_PARTS_PER_MILLION, None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.formaldehyde_measurement: [ @@ -154,6 +229,7 @@ CAPABILITY_TO_SENSORS = { CONCENTRATION_PARTS_PER_MILLION, None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.gas_meter: [ @@ -163,14 +239,18 @@ CAPABILITY_TO_SENSORS = { ENERGY_KILO_WATT_HOUR, None, STATE_CLASS_MEASUREMENT, + None, + ), + Map( + Attribute.gas_meter_calorific, "Gas Meter Calorific", None, None, None, None ), - Map(Attribute.gas_meter_calorific, "Gas Meter Calorific", None, None, None), Map( Attribute.gas_meter_time, "Gas Meter Time", None, DEVICE_CLASS_TIMESTAMP, None, + None, ), Map( Attribute.gas_meter_volume, @@ -178,6 +258,7 @@ CAPABILITY_TO_SENSORS = { VOLUME_CUBIC_METERS, None, STATE_CLASS_MEASUREMENT, + None, ), ], Capability.illuminance_measurement: [ @@ -187,6 +268,7 @@ CAPABILITY_TO_SENSORS = { LIGHT_LUX, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.infrared_level: [ @@ -196,31 +278,50 @@ CAPABILITY_TO_SENSORS = { PERCENTAGE, None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.media_input_source: [ - Map(Attribute.input_source, "Media Input Source", None, None, None) + Map(Attribute.input_source, "Media Input Source", None, None, None, None) ], Capability.media_playback_repeat: [ - Map(Attribute.playback_repeat_mode, "Media Playback Repeat", None, None, None) + Map( + Attribute.playback_repeat_mode, + "Media Playback Repeat", + None, + None, + None, + None, + ) ], Capability.media_playback_shuffle: [ - Map(Attribute.playback_shuffle, "Media Playback Shuffle", None, None, None) + Map( + Attribute.playback_shuffle, "Media Playback Shuffle", None, None, None, None + ) ], Capability.media_playback: [ - Map(Attribute.playback_status, "Media Playback Status", None, None, None) + Map(Attribute.playback_status, "Media Playback Status", None, None, None, None) ], Capability.odor_sensor: [ - Map(Attribute.odor_level, "Odor Sensor", None, None, None) + Map(Attribute.odor_level, "Odor Sensor", None, None, None, None) + ], + Capability.oven_mode: [ + Map( + Attribute.oven_mode, + "Oven Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) ], - Capability.oven_mode: [Map(Attribute.oven_mode, "Oven Mode", None, None, None)], Capability.oven_operating_state: [ - Map(Attribute.machine_state, "Oven Machine State", None, None, None), - Map(Attribute.oven_job_state, "Oven Job State", None, None, None), - Map(Attribute.completion_time, "Oven Completion Time", None, None, None), + Map(Attribute.machine_state, "Oven Machine State", None, None, None, None), + Map(Attribute.oven_job_state, "Oven Job State", None, None, None, None), + Map(Attribute.completion_time, "Oven Completion Time", None, None, None, None), ], Capability.oven_setpoint: [ - Map(Attribute.oven_setpoint, "Oven Set Point", None, None, None) + Map(Attribute.oven_setpoint, "Oven Set Point", None, None, None, None) ], Capability.power_consumption_report: [], Capability.power_meter: [ @@ -230,10 +331,18 @@ CAPABILITY_TO_SENSORS = { POWER_WATT, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.power_source: [ - Map(Attribute.power_source, "Power Source", None, None, None) + Map( + Attribute.power_source, + "Power Source", + None, + None, + None, + ENTITY_CATEGORY_DIAGNOSTIC, + ) ], Capability.refrigeration_setpoint: [ Map( @@ -242,6 +351,7 @@ CAPABILITY_TO_SENSORS = { None, DEVICE_CLASS_TEMPERATURE, None, + None, ) ], Capability.relative_humidity_measurement: [ @@ -251,6 +361,7 @@ CAPABILITY_TO_SENSORS = { PERCENTAGE, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.robot_cleaner_cleaning_mode: [ @@ -260,11 +371,17 @@ CAPABILITY_TO_SENSORS = { None, None, None, + ENTITY_CATEGORY_CONFIG, ) ], Capability.robot_cleaner_movement: [ Map( - Attribute.robot_cleaner_movement, "Robot Cleaner Movement", None, None, None + Attribute.robot_cleaner_movement, + "Robot Cleaner Movement", + None, + None, + None, + None, ) ], Capability.robot_cleaner_turbo_mode: [ @@ -274,20 +391,29 @@ CAPABILITY_TO_SENSORS = { None, None, None, + ENTITY_CATEGORY_CONFIG, ) ], Capability.signal_strength: [ - Map(Attribute.lqi, "LQI Signal Strength", None, None, STATE_CLASS_MEASUREMENT), + Map( + Attribute.lqi, + "LQI Signal Strength", + None, + None, + STATE_CLASS_MEASUREMENT, + ENTITY_CATEGORY_DIAGNOSTIC, + ), Map( Attribute.rssi, "RSSI Signal Strength", None, DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, + ENTITY_CATEGORY_DIAGNOSTIC, ), ], Capability.smoke_detector: [ - Map(Attribute.smoke, "Smoke Detector", None, None, None) + Map(Attribute.smoke, "Smoke Detector", None, None, None, None) ], Capability.temperature_measurement: [ Map( @@ -296,6 +422,7 @@ CAPABILITY_TO_SENSORS = { None, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.thermostat_cooling_setpoint: [ @@ -305,10 +432,18 @@ CAPABILITY_TO_SENSORS = { None, DEVICE_CLASS_TEMPERATURE, None, + None, ) ], Capability.thermostat_fan_mode: [ - Map(Attribute.thermostat_fan_mode, "Thermostat Fan Mode", None, None, None) + Map( + Attribute.thermostat_fan_mode, + "Thermostat Fan Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) ], Capability.thermostat_heating_setpoint: [ Map( @@ -317,10 +452,18 @@ CAPABILITY_TO_SENSORS = { None, DEVICE_CLASS_TEMPERATURE, None, + ENTITY_CATEGORY_CONFIG, ) ], Capability.thermostat_mode: [ - Map(Attribute.thermostat_mode, "Thermostat Mode", None, None, None) + Map( + Attribute.thermostat_mode, + "Thermostat Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) ], Capability.thermostat_operating_state: [ Map( @@ -329,6 +472,7 @@ CAPABILITY_TO_SENSORS = { None, None, None, + None, ) ], Capability.thermostat_setpoint: [ @@ -338,12 +482,13 @@ CAPABILITY_TO_SENSORS = { None, DEVICE_CLASS_TEMPERATURE, None, + ENTITY_CATEGORY_CONFIG, ) ], Capability.three_axis: [], Capability.tv_channel: [ - Map(Attribute.tv_channel, "Tv Channel", None, None, None), - Map(Attribute.tv_channel_name, "Tv Channel Name", None, None, None), + Map(Attribute.tv_channel, "Tv Channel", None, None, None, None), + Map(Attribute.tv_channel_name, "Tv Channel Name", None, None, None, None), ], Capability.tvoc_measurement: [ Map( @@ -352,6 +497,7 @@ CAPABILITY_TO_SENSORS = { CONCENTRATION_PARTS_PER_MILLION, None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.ultraviolet_index: [ @@ -361,6 +507,7 @@ CAPABILITY_TO_SENSORS = { None, None, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.voltage_measurement: [ @@ -370,20 +517,29 @@ CAPABILITY_TO_SENSORS = { ELECTRIC_POTENTIAL_VOLT, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT, + None, ) ], Capability.washer_mode: [ - Map(Attribute.washer_mode, "Washer Mode", None, None, None) + Map( + Attribute.washer_mode, + "Washer Mode", + None, + None, + None, + ENTITY_CATEGORY_CONFIG, + ) ], Capability.washer_operating_state: [ - Map(Attribute.machine_state, "Washer Machine State", None, None, None), - Map(Attribute.washer_job_state, "Washer Job State", None, None, None), + Map(Attribute.machine_state, "Washer Machine State", None, None, None, None), + Map(Attribute.washer_job_state, "Washer Job State", None, None, None, None), Map( Attribute.completion_time, "Washer Completion Time", None, DEVICE_CLASS_TIMESTAMP, None, + None, ), ], } @@ -431,6 +587,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): m.default_unit, m.device_class, m.state_class, + m.entity_category, ) for m in maps ] @@ -448,6 +605,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): m.default_unit, m.device_class, m.state_class, + m.entity_category, ) for m in maps ] @@ -474,6 +632,7 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity): default_unit: str, device_class: str, state_class: str | None, + entity_category: str | None, ) -> None: """Init the class.""" super().__init__(device) @@ -482,6 +641,7 @@ class SmartThingsSensor(SmartThingsEntity, SensorEntity): self._device_class = device_class self._default_unit = default_unit self._attr_state_class = state_class + self._attr_entity_category = entity_category @property def name(self) -> str: diff --git a/tests/components/smartthings/test_binary_sensor.py b/tests/components/smartthings/test_binary_sensor.py index f3d548c1e39..efc34424ae0 100644 --- a/tests/components/smartthings/test_binary_sensor.py +++ b/tests/components/smartthings/test_binary_sensor.py @@ -13,7 +13,11 @@ from homeassistant.components.binary_sensor import ( from homeassistant.components.smartthings import binary_sensor from homeassistant.components.smartthings.const import DOMAIN, SIGNAL_SMARTTHINGS_UPDATE from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import ATTR_FRIENDLY_NAME, STATE_UNAVAILABLE +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_UNAVAILABLE, +) from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_send @@ -100,3 +104,23 @@ async def test_unload_config_entry(hass, device_factory): hass.states.get("binary_sensor.motion_sensor_1_motion").state == STATE_UNAVAILABLE ) + + +async def test_entity_category(hass, device_factory): + """Tests the state attributes properly match the light types.""" + device1 = device_factory( + "Motion Sensor 1", [Capability.motion_sensor], {Attribute.motion: "inactive"} + ) + device2 = device_factory( + "Tamper Sensor 2", [Capability.tamper_alert], {Attribute.tamper: "inactive"} + ) + await setup_platform(hass, BINARY_SENSOR_DOMAIN, devices=[device1, device2]) + + entity_registry = er.async_get(hass) + entry = entity_registry.async_get("binary_sensor.motion_sensor_1_motion") + assert entry + assert entry.entity_category is None + + entry = entity_registry.async_get("binary_sensor.tamper_sensor_2_tamper") + assert entry + assert entry.entity_category == ENTITY_CATEGORY_DIAGNOSTIC diff --git a/tests/components/smartthings/test_sensor.py b/tests/components/smartthings/test_sensor.py index f36f05616d6..049666baf99 100644 --- a/tests/components/smartthings/test_sensor.py +++ b/tests/components/smartthings/test_sensor.py @@ -17,6 +17,7 @@ from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, + ENTITY_CATEGORY_DIAGNOSTIC, PERCENTAGE, STATE_UNAVAILABLE, STATE_UNKNOWN, @@ -94,6 +95,7 @@ async def test_entity_and_device_attributes(hass, device_factory): entry = entity_registry.async_get("sensor.sensor_1_battery") assert entry assert entry.unique_id == f"{device.device_id}.{Attribute.battery}" + assert entry.entity_category == ENTITY_CATEGORY_DIAGNOSTIC entry = device_registry.async_get_device({(DOMAIN, device.device_id)}) assert entry assert entry.name == device.label @@ -120,6 +122,7 @@ async def test_energy_sensors_for_switch_device(hass, device_factory): entry = entity_registry.async_get("sensor.switch_1_energy_meter") assert entry assert entry.unique_id == f"{device.device_id}.{Attribute.energy}" + assert entry.entity_category is None entry = device_registry.async_get_device({(DOMAIN, device.device_id)}) assert entry assert entry.name == device.label @@ -132,6 +135,7 @@ async def test_energy_sensors_for_switch_device(hass, device_factory): entry = entity_registry.async_get("sensor.switch_1_power_meter") assert entry assert entry.unique_id == f"{device.device_id}.{Attribute.power}" + assert entry.entity_category is None entry = device_registry.async_get_device({(DOMAIN, device.device_id)}) assert entry assert entry.name == device.label