core/homeassistant/components/smartthings/sensor.py

1087 lines
38 KiB
Python

"""Support for sensors through the SmartThings cloud API."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from datetime import datetime
from typing import Any, cast
from pysmartthings import Attribute, Capability, SmartThings, Status
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
EntityCategory,
UnitOfArea,
UnitOfEnergy,
UnitOfMass,
UnitOfPower,
UnitOfTemperature,
UnitOfVolume,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from . import FullDevice, SmartThingsConfigEntry
from .const import MAIN
from .entity import SmartThingsEntity
THERMOSTAT_CAPABILITIES = {
Capability.TEMPERATURE_MEASUREMENT,
Capability.THERMOSTAT_HEATING_SETPOINT,
Capability.THERMOSTAT_MODE,
}
JOB_STATE_MAP = {
"airWash": "air_wash",
"airwash": "air_wash",
"aIRinse": "ai_rinse",
"aISpin": "ai_spin",
"aIWash": "ai_wash",
"aIDrying": "ai_drying",
"internalCare": "internal_care",
"continuousDehumidifying": "continuous_dehumidifying",
"thawingFrozenInside": "thawing_frozen_inside",
"delayWash": "delay_wash",
"weightSensing": "weight_sensing",
"freezeProtection": "freeze_protection",
"preDrain": "pre_drain",
"preWash": "pre_wash",
"prewash": "pre_wash",
"wrinklePrevent": "wrinkle_prevent",
"unknown": None,
}
OVEN_JOB_STATE_MAP = {
"scheduledStart": "scheduled_start",
"fastPreheat": "fast_preheat",
"scheduledEnd": "scheduled_end",
"stone_heating": "stone_heating",
"timeHoldPreheat": "time_hold_preheat",
}
MEDIA_PLAYBACK_STATE_MAP = {
"fast forwarding": "fast_forwarding",
}
ROBOT_CLEANER_TURBO_MODE_STATE_MAP = {
"extraSilence": "extra_silence",
}
ROBOT_CLEANER_MOVEMENT_MAP = {
"powerOff": "off",
}
OVEN_MODE = {
"Conventional": "conventional",
"Bake": "bake",
"BottomHeat": "bottom_heat",
"ConvectionBake": "convection_bake",
"ConvectionRoast": "convection_roast",
"Broil": "broil",
"ConvectionBroil": "convection_broil",
"SteamCook": "steam_cook",
"SteamBake": "steam_bake",
"SteamRoast": "steam_roast",
"SteamBottomHeatplusConvection": "steam_bottom_heat_plus_convection",
"Microwave": "microwave",
"MWplusGrill": "microwave_plus_grill",
"MWplusConvection": "microwave_plus_convection",
"MWplusHotBlast": "microwave_plus_hot_blast",
"MWplusHotBlast2": "microwave_plus_hot_blast_2",
"SlimMiddle": "slim_middle",
"SlimStrong": "slim_strong",
"SlowCook": "slow_cook",
"Proof": "proof",
"Dehydrate": "dehydrate",
"Others": "others",
"StrongSteam": "strong_steam",
"Descale": "descale",
"Rinse": "rinse",
}
WASHER_OPTIONS = ["pause", "run", "stop"]
def power_attributes(status: dict[str, Any]) -> dict[str, Any]:
"""Return the power attributes."""
state = {}
for attribute in ("start", "end"):
if (value := status.get(attribute)) is not None:
state[f"power_consumption_{attribute}"] = value
return state
@dataclass(frozen=True, kw_only=True)
class SmartThingsSensorEntityDescription(SensorEntityDescription):
"""Describe a SmartThings sensor entity."""
value_fn: Callable[[Any], str | float | int | datetime | None] = lambda value: value
extra_state_attributes_fn: Callable[[Any], dict[str, Any]] | None = None
unique_id_separator: str = "."
capability_ignore_list: list[set[Capability]] | None = None
options_attribute: Attribute | None = None
exists_fn: Callable[[Status], bool] | None = None
use_temperature_unit: bool = False
CAPABILITY_TO_SENSORS: dict[
Capability, dict[Attribute, list[SmartThingsSensorEntityDescription]]
] = {
# Haven't seen at devices yet
Capability.ACTIVITY_LIGHTING_MODE: {
Attribute.LIGHTING_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.LIGHTING_MODE,
translation_key="lighting_mode",
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
Capability.AIR_CONDITIONER_MODE: {
Attribute.AIR_CONDITIONER_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.AIR_CONDITIONER_MODE,
translation_key="air_conditioner_mode",
entity_category=EntityCategory.DIAGNOSTIC,
capability_ignore_list=[
{
Capability.TEMPERATURE_MEASUREMENT,
Capability.THERMOSTAT_COOLING_SETPOINT,
}
],
)
]
},
Capability.AIR_QUALITY_SENSOR: {
Attribute.AIR_QUALITY: [
SmartThingsSensorEntityDescription(
key=Attribute.AIR_QUALITY,
translation_key="air_quality",
native_unit_of_measurement="CAQI",
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.ALARM: {
Attribute.ALARM: [
SmartThingsSensorEntityDescription(
key=Attribute.ALARM,
translation_key="alarm",
options=["both", "strobe", "siren", "off"],
device_class=SensorDeviceClass.ENUM,
)
]
},
Capability.AUDIO_VOLUME: {
Attribute.VOLUME: [
SmartThingsSensorEntityDescription(
key=Attribute.VOLUME,
translation_key="audio_volume",
native_unit_of_measurement=PERCENTAGE,
)
]
},
Capability.BATTERY: {
Attribute.BATTERY: [
SmartThingsSensorEntityDescription(
key=Attribute.BATTERY,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
# Haven't seen at devices yet
Capability.BODY_MASS_INDEX_MEASUREMENT: {
Attribute.BMI_MEASUREMENT: [
SmartThingsSensorEntityDescription(
key=Attribute.BMI_MEASUREMENT,
translation_key="body_mass_index",
native_unit_of_measurement=f"{UnitOfMass.KILOGRAMS}/{UnitOfArea.SQUARE_METERS}",
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.BODY_WEIGHT_MEASUREMENT: {
Attribute.BODY_WEIGHT_MEASUREMENT: [
SmartThingsSensorEntityDescription(
key=Attribute.BODY_WEIGHT_MEASUREMENT,
translation_key="body_weight",
native_unit_of_measurement=UnitOfMass.KILOGRAMS,
device_class=SensorDeviceClass.WEIGHT,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.CARBON_DIOXIDE_MEASUREMENT: {
Attribute.CARBON_DIOXIDE: [
SmartThingsSensorEntityDescription(
key=Attribute.CARBON_DIOXIDE,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.CARBON_MONOXIDE_DETECTOR: {
Attribute.CARBON_MONOXIDE: [
SmartThingsSensorEntityDescription(
key=Attribute.CARBON_MONOXIDE,
translation_key="carbon_monoxide_detector",
options=["detected", "clear", "tested"],
device_class=SensorDeviceClass.ENUM,
)
]
},
# Haven't seen at devices yet
Capability.CARBON_MONOXIDE_MEASUREMENT: {
Attribute.CARBON_MONOXIDE_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.CARBON_MONOXIDE_LEVEL,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.DISHWASHER_OPERATING_STATE: {
Attribute.MACHINE_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.MACHINE_STATE,
translation_key="dishwasher_machine_state",
options=WASHER_OPTIONS,
device_class=SensorDeviceClass.ENUM,
)
],
Attribute.DISHWASHER_JOB_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.DISHWASHER_JOB_STATE,
translation_key="dishwasher_job_state",
options=[
"air_wash",
"cooling",
"drying",
"finish",
"pre_drain",
"pre_wash",
"rinse",
"spin",
"wash",
"wrinkle_prevent",
],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: JOB_STATE_MAP.get(value, value),
)
],
Attribute.COMPLETION_TIME: [
SmartThingsSensorEntityDescription(
key=Attribute.COMPLETION_TIME,
translation_key="completion_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
)
],
},
# part of the proposed spec, Haven't seen at devices yet
Capability.DRYER_MODE: {
Attribute.DRYER_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.DRYER_MODE,
translation_key="dryer_mode",
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
Capability.DRYER_OPERATING_STATE: {
Attribute.MACHINE_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.MACHINE_STATE,
translation_key="dryer_machine_state",
options=WASHER_OPTIONS,
device_class=SensorDeviceClass.ENUM,
)
],
Attribute.DRYER_JOB_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.DRYER_JOB_STATE,
translation_key="dryer_job_state",
options=[
"cooling",
"delay_wash",
"drying",
"finished",
"none",
"refreshing",
"weight_sensing",
"wrinkle_prevent",
"dehumidifying",
"ai_drying",
"sanitizing",
"internal_care",
"freeze_protection",
"continuous_dehumidifying",
"thawing_frozen_inside",
],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: JOB_STATE_MAP.get(value, value),
)
],
Attribute.COMPLETION_TIME: [
SmartThingsSensorEntityDescription(
key=Attribute.COMPLETION_TIME,
translation_key="completion_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
)
],
},
Capability.DUST_SENSOR: {
Attribute.DUST_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.DUST_LEVEL,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
)
],
Attribute.FINE_DUST_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.FINE_DUST_LEVEL,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
)
],
},
Capability.ENERGY_METER: {
Attribute.ENERGY: [
SmartThingsSensorEntityDescription(
key=Attribute.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
)
]
},
# Haven't seen at devices yet
Capability.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT: {
Attribute.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT: [
SmartThingsSensorEntityDescription(
key=Attribute.EQUIVALENT_CARBON_DIOXIDE_MEASUREMENT,
translation_key="equivalent_carbon_dioxide",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.FORMALDEHYDE_MEASUREMENT: {
Attribute.FORMALDEHYDE_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.FORMALDEHYDE_LEVEL,
translation_key="formaldehyde",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.GAS_METER: {
Attribute.GAS_METER: [
SmartThingsSensorEntityDescription(
key=Attribute.GAS_METER,
translation_key="gas_meter",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.MEASUREMENT,
)
],
Attribute.GAS_METER_CALORIFIC: [
SmartThingsSensorEntityDescription(
key=Attribute.GAS_METER_CALORIFIC,
translation_key="gas_meter_calorific",
)
],
Attribute.GAS_METER_TIME: [
SmartThingsSensorEntityDescription(
key=Attribute.GAS_METER_TIME,
translation_key="gas_meter_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
)
],
Attribute.GAS_METER_VOLUME: [
SmartThingsSensorEntityDescription(
key=Attribute.GAS_METER_VOLUME,
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
device_class=SensorDeviceClass.GAS,
state_class=SensorStateClass.MEASUREMENT,
)
],
},
# Haven't seen at devices yet
Capability.ILLUMINANCE_MEASUREMENT: {
Attribute.ILLUMINANCE: [
SmartThingsSensorEntityDescription(
key=Attribute.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
device_class=SensorDeviceClass.ILLUMINANCE,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.INFRARED_LEVEL: {
Attribute.INFRARED_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.INFRARED_LEVEL,
translation_key="infrared_level",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.MEDIA_INPUT_SOURCE: {
Attribute.INPUT_SOURCE: [
SmartThingsSensorEntityDescription(
key=Attribute.INPUT_SOURCE,
translation_key="media_input_source",
device_class=SensorDeviceClass.ENUM,
options_attribute=Attribute.SUPPORTED_INPUT_SOURCES,
value_fn=lambda value: value.lower() if value else None,
)
]
},
Capability.MEDIA_PLAYBACK_REPEAT: {
Attribute.PLAYBACK_REPEAT_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.PLAYBACK_REPEAT_MODE,
translation_key="media_playback_repeat",
)
]
},
Capability.MEDIA_PLAYBACK_SHUFFLE: {
Attribute.PLAYBACK_SHUFFLE: [
SmartThingsSensorEntityDescription(
key=Attribute.PLAYBACK_SHUFFLE,
translation_key="media_playback_shuffle",
)
]
},
Capability.MEDIA_PLAYBACK: {
Attribute.PLAYBACK_STATUS: [
SmartThingsSensorEntityDescription(
key=Attribute.PLAYBACK_STATUS,
translation_key="media_playback_status",
options=[
"paused",
"playing",
"stopped",
"fast_forwarding",
"rewinding",
"buffering",
],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: MEDIA_PLAYBACK_STATE_MAP.get(value, value),
)
]
},
Capability.ODOR_SENSOR: {
Attribute.ODOR_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.ODOR_LEVEL,
translation_key="odor_sensor",
)
]
},
Capability.OVEN_MODE: {
Attribute.OVEN_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.OVEN_MODE,
translation_key="oven_mode",
entity_category=EntityCategory.DIAGNOSTIC,
options=list(OVEN_MODE.values()),
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: OVEN_MODE.get(value, value),
)
]
},
Capability.OVEN_OPERATING_STATE: {
Attribute.MACHINE_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.MACHINE_STATE,
translation_key="oven_machine_state",
options=["ready", "running", "paused"],
device_class=SensorDeviceClass.ENUM,
)
],
Attribute.OVEN_JOB_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.OVEN_JOB_STATE,
translation_key="oven_job_state",
options=[
"cleaning",
"cooking",
"cooling",
"draining",
"preheat",
"ready",
"rinsing",
"finished",
"scheduled_start",
"warming",
"defrosting",
"sensing",
"searing",
"fast_preheat",
"scheduled_end",
"stone_heating",
"time_hold_preheat",
],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: OVEN_JOB_STATE_MAP.get(value, value),
)
],
Attribute.COMPLETION_TIME: [
SmartThingsSensorEntityDescription(
key=Attribute.COMPLETION_TIME,
translation_key="completion_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
)
],
},
Capability.OVEN_SETPOINT: {
Attribute.OVEN_SETPOINT: [
SmartThingsSensorEntityDescription(
key=Attribute.OVEN_SETPOINT,
translation_key="oven_setpoint",
device_class=SensorDeviceClass.TEMPERATURE,
use_temperature_unit=True,
# Set the value to None if it is 0 F (-17 C)
value_fn=lambda value: None if value in {0, -17} else value,
)
]
},
Capability.POWER_CONSUMPTION_REPORT: {
Attribute.POWER_CONSUMPTION: [
SmartThingsSensorEntityDescription(
key="energy_meter",
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value_fn=lambda value: value["energy"] / 1000,
suggested_display_precision=2,
exists_fn=lambda status: (
(value := cast(dict | None, status.value)) is not None
and "energy" in value
),
),
SmartThingsSensorEntityDescription(
key="power_meter",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
value_fn=lambda value: value["power"],
extra_state_attributes_fn=power_attributes,
suggested_display_precision=2,
exists_fn=lambda status: (
(value := cast(dict | None, status.value)) is not None
and "power" in value
),
),
SmartThingsSensorEntityDescription(
key="deltaEnergy_meter",
translation_key="energy_difference",
state_class=SensorStateClass.TOTAL,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value_fn=lambda value: value["deltaEnergy"] / 1000,
suggested_display_precision=2,
exists_fn=lambda status: (
(value := cast(dict | None, status.value)) is not None
and "deltaEnergy" in value
),
),
SmartThingsSensorEntityDescription(
key="powerEnergy_meter",
translation_key="power_energy",
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value_fn=lambda value: value["powerEnergy"] / 1000,
suggested_display_precision=2,
exists_fn=lambda status: (
(value := cast(dict | None, status.value)) is not None
and "powerEnergy" in value
),
),
SmartThingsSensorEntityDescription(
key="energySaved_meter",
translation_key="energy_saved",
state_class=SensorStateClass.TOTAL_INCREASING,
device_class=SensorDeviceClass.ENERGY,
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
value_fn=lambda value: value["energySaved"] / 1000,
suggested_display_precision=2,
exists_fn=lambda status: (
(value := cast(dict | None, status.value)) is not None
and "energySaved" in value
),
),
]
},
Capability.POWER_METER: {
Attribute.POWER: [
SmartThingsSensorEntityDescription(
key=Attribute.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.POWER_SOURCE: {
Attribute.POWER_SOURCE: [
SmartThingsSensorEntityDescription(
key=Attribute.POWER_SOURCE,
translation_key="power_source",
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
# part of the proposed spec
Capability.REFRIGERATION_SETPOINT: {
Attribute.REFRIGERATION_SETPOINT: [
SmartThingsSensorEntityDescription(
key=Attribute.REFRIGERATION_SETPOINT,
translation_key="refrigeration_setpoint",
device_class=SensorDeviceClass.TEMPERATURE,
)
]
},
Capability.RELATIVE_HUMIDITY_MEASUREMENT: {
Attribute.HUMIDITY: [
SmartThingsSensorEntityDescription(
key=Attribute.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.HUMIDITY,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.ROBOT_CLEANER_CLEANING_MODE: {
Attribute.ROBOT_CLEANER_CLEANING_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.ROBOT_CLEANER_CLEANING_MODE,
translation_key="robot_cleaner_cleaning_mode",
options=["auto", "part", "repeat", "manual", "stop", "map"],
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
)
],
},
Capability.ROBOT_CLEANER_MOVEMENT: {
Attribute.ROBOT_CLEANER_MOVEMENT: [
SmartThingsSensorEntityDescription(
key=Attribute.ROBOT_CLEANER_MOVEMENT,
translation_key="robot_cleaner_movement",
options=[
"homing",
"idle",
"charging",
"alarm",
"off",
"reserve",
"point",
"after",
"cleaning",
"pause",
],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: ROBOT_CLEANER_MOVEMENT_MAP.get(value, value),
)
]
},
Capability.ROBOT_CLEANER_TURBO_MODE: {
Attribute.ROBOT_CLEANER_TURBO_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.ROBOT_CLEANER_TURBO_MODE,
translation_key="robot_cleaner_turbo_mode",
options=["on", "off", "silence", "extra_silence"],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: ROBOT_CLEANER_TURBO_MODE_STATE_MAP.get(
value, value
),
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
# Haven't seen at devices yet
Capability.SIGNAL_STRENGTH: {
Attribute.LQI: [
SmartThingsSensorEntityDescription(
key=Attribute.LQI,
translation_key="link_quality",
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
)
],
Attribute.RSSI: [
SmartThingsSensorEntityDescription(
key=Attribute.RSSI,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
)
],
},
# Haven't seen at devices yet
Capability.SMOKE_DETECTOR: {
Attribute.SMOKE: [
SmartThingsSensorEntityDescription(
key=Attribute.SMOKE,
translation_key="smoke_detector",
options=["detected", "clear", "tested"],
device_class=SensorDeviceClass.ENUM,
)
]
},
Capability.TEMPERATURE_MEASUREMENT: {
Attribute.TEMPERATURE: [
SmartThingsSensorEntityDescription(
key=Attribute.TEMPERATURE,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.THERMOSTAT_COOLING_SETPOINT: {
Attribute.COOLING_SETPOINT: [
SmartThingsSensorEntityDescription(
key=Attribute.COOLING_SETPOINT,
translation_key="thermostat_cooling_setpoint",
device_class=SensorDeviceClass.TEMPERATURE,
capability_ignore_list=[
{
Capability.AIR_CONDITIONER_FAN_MODE,
Capability.TEMPERATURE_MEASUREMENT,
Capability.AIR_CONDITIONER_MODE,
},
THERMOSTAT_CAPABILITIES,
],
)
]
},
# Haven't seen at devices yet
Capability.THERMOSTAT_FAN_MODE: {
Attribute.THERMOSTAT_FAN_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.THERMOSTAT_FAN_MODE,
translation_key="thermostat_fan_mode",
entity_category=EntityCategory.DIAGNOSTIC,
capability_ignore_list=[THERMOSTAT_CAPABILITIES],
)
]
},
# Haven't seen at devices yet
Capability.THERMOSTAT_HEATING_SETPOINT: {
Attribute.HEATING_SETPOINT: [
SmartThingsSensorEntityDescription(
key=Attribute.HEATING_SETPOINT,
translation_key="thermostat_heating_setpoint",
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
capability_ignore_list=[THERMOSTAT_CAPABILITIES],
)
]
},
# Haven't seen at devices yet
Capability.THERMOSTAT_MODE: {
Attribute.THERMOSTAT_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.THERMOSTAT_MODE,
translation_key="thermostat_mode",
entity_category=EntityCategory.DIAGNOSTIC,
capability_ignore_list=[THERMOSTAT_CAPABILITIES],
)
]
},
# Haven't seen at devices yet
Capability.THERMOSTAT_OPERATING_STATE: {
Attribute.THERMOSTAT_OPERATING_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.THERMOSTAT_OPERATING_STATE,
translation_key="thermostat_operating_state",
capability_ignore_list=[THERMOSTAT_CAPABILITIES],
)
]
},
# deprecated capability
Capability.THERMOSTAT_SETPOINT: {
Attribute.THERMOSTAT_SETPOINT: [
SmartThingsSensorEntityDescription(
key=Attribute.THERMOSTAT_SETPOINT,
translation_key="thermostat_setpoint",
device_class=SensorDeviceClass.TEMPERATURE,
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
Capability.THREE_AXIS: {
Attribute.THREE_AXIS: [
SmartThingsSensorEntityDescription(
key="X Coordinate",
translation_key="x_coordinate",
unique_id_separator=" ",
value_fn=lambda value: value[0],
),
SmartThingsSensorEntityDescription(
key="Y Coordinate",
translation_key="y_coordinate",
unique_id_separator=" ",
value_fn=lambda value: value[1],
),
SmartThingsSensorEntityDescription(
key="Z Coordinate",
translation_key="z_coordinate",
unique_id_separator=" ",
value_fn=lambda value: value[2],
),
]
},
Capability.TV_CHANNEL: {
Attribute.TV_CHANNEL: [
SmartThingsSensorEntityDescription(
key=Attribute.TV_CHANNEL,
translation_key="tv_channel",
)
],
Attribute.TV_CHANNEL_NAME: [
SmartThingsSensorEntityDescription(
key=Attribute.TV_CHANNEL_NAME,
translation_key="tv_channel_name",
)
],
},
# Haven't seen at devices yet
Capability.TVOC_MEASUREMENT: {
Attribute.TVOC_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.TVOC_LEVEL,
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# Haven't seen at devices yet
Capability.ULTRAVIOLET_INDEX: {
Attribute.ULTRAVIOLET_INDEX: [
SmartThingsSensorEntityDescription(
key=Attribute.ULTRAVIOLET_INDEX,
translation_key="uv_index",
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.VERY_FINE_DUST_SENSOR: {
Attribute.VERY_FINE_DUST_LEVEL: [
SmartThingsSensorEntityDescription(
key=Attribute.VERY_FINE_DUST_LEVEL,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
Capability.VOLTAGE_MEASUREMENT: {
Attribute.VOLTAGE: [
SmartThingsSensorEntityDescription(
key=Attribute.VOLTAGE,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
)
]
},
# part of the proposed spec
Capability.WASHER_MODE: {
Attribute.WASHER_MODE: [
SmartThingsSensorEntityDescription(
key=Attribute.WASHER_MODE,
translation_key="washer_mode",
entity_category=EntityCategory.DIAGNOSTIC,
)
]
},
Capability.WASHER_OPERATING_STATE: {
Attribute.MACHINE_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.MACHINE_STATE,
translation_key="washer_machine_state",
options=WASHER_OPTIONS,
device_class=SensorDeviceClass.ENUM,
)
],
Attribute.WASHER_JOB_STATE: [
SmartThingsSensorEntityDescription(
key=Attribute.WASHER_JOB_STATE,
translation_key="washer_job_state",
options=[
"air_wash",
"ai_rinse",
"ai_spin",
"ai_wash",
"cooling",
"delay_wash",
"drying",
"finish",
"none",
"pre_wash",
"rinse",
"spin",
"wash",
"weight_sensing",
"wrinkle_prevent",
"freeze_protection",
],
device_class=SensorDeviceClass.ENUM,
value_fn=lambda value: JOB_STATE_MAP.get(value, value),
)
],
Attribute.COMPLETION_TIME: [
SmartThingsSensorEntityDescription(
key=Attribute.COMPLETION_TIME,
translation_key="completion_time",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=dt_util.parse_datetime,
)
],
},
}
UNITS = {
"C": UnitOfTemperature.CELSIUS,
"F": UnitOfTemperature.FAHRENHEIT,
"lux": LIGHT_LUX,
"mG": None,
"μg/m^3": CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
}
async def async_setup_entry(
hass: HomeAssistant,
entry: SmartThingsConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add sensors for a config entry."""
entry_data = entry.runtime_data
async_add_entities(
SmartThingsSensor(
entry_data.client,
device,
description,
capability,
attribute,
)
for device in entry_data.devices.values()
for capability, attributes in CAPABILITY_TO_SENSORS.items()
if capability in device.status[MAIN]
for attribute, descriptions in attributes.items()
for description in descriptions
if (
not description.capability_ignore_list
or not any(
all(capability in device.status[MAIN] for capability in capability_list)
for capability_list in description.capability_ignore_list
)
)
and (
not description.exists_fn
or description.exists_fn(device.status[MAIN][capability][attribute])
)
)
class SmartThingsSensor(SmartThingsEntity, SensorEntity):
"""Define a SmartThings Sensor."""
entity_description: SmartThingsSensorEntityDescription
def __init__(
self,
client: SmartThings,
device: FullDevice,
entity_description: SmartThingsSensorEntityDescription,
capability: Capability,
attribute: Attribute,
) -> None:
"""Init the class."""
capabilities_to_subscribe = {capability}
if entity_description.use_temperature_unit:
capabilities_to_subscribe.add(Capability.TEMPERATURE_MEASUREMENT)
super().__init__(client, device, capabilities_to_subscribe)
self._attr_unique_id = f"{device.device.device_id}{entity_description.unique_id_separator}{entity_description.key}"
self._attribute = attribute
self.capability = capability
self.entity_description = entity_description
@property
def native_value(self) -> str | float | datetime | int | None:
"""Return the state of the sensor."""
res = self.get_attribute_value(self.capability, self._attribute)
return self.entity_description.value_fn(res)
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in."""
if self.entity_description.use_temperature_unit:
unit = self._internal_state[Capability.TEMPERATURE_MEASUREMENT][
Attribute.TEMPERATURE
].unit
else:
unit = self._internal_state[self.capability][self._attribute].unit
return (
UNITS.get(unit, unit)
if unit
else self.entity_description.native_unit_of_measurement
)
@property
def extra_state_attributes(self) -> Mapping[str, Any] | None:
"""Return the state attributes."""
if self.entity_description.extra_state_attributes_fn:
return self.entity_description.extra_state_attributes_fn(
self.get_attribute_value(self.capability, self._attribute)
)
return None
@property
def options(self) -> list[str] | None:
"""Return the options for this sensor."""
if self.entity_description.options_attribute:
if (
options := self.get_attribute_value(
self.capability, self.entity_description.options_attribute
)
) is None:
return []
return [option.lower() for option in options]
return super().options