mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 17:27:52 +00:00
Add support for binary sensor states in Google Assistant (#127652)
This commit is contained in:
parent
784ad20fb6
commit
f7f1830b7e
@ -78,6 +78,7 @@ TYPE_AWNING = f"{PREFIX_TYPES}AWNING"
|
||||
TYPE_BLINDS = f"{PREFIX_TYPES}BLINDS"
|
||||
TYPE_CAMERA = f"{PREFIX_TYPES}CAMERA"
|
||||
TYPE_CURTAIN = f"{PREFIX_TYPES}CURTAIN"
|
||||
TYPE_CARBON_MONOXIDE_DETECTOR = f"{PREFIX_TYPES}CARBON_MONOXIDE_DETECTOR"
|
||||
TYPE_DEHUMIDIFIER = f"{PREFIX_TYPES}DEHUMIDIFIER"
|
||||
TYPE_DOOR = f"{PREFIX_TYPES}DOOR"
|
||||
TYPE_DOORBELL = f"{PREFIX_TYPES}DOORBELL"
|
||||
@ -93,6 +94,7 @@ TYPE_SCENE = f"{PREFIX_TYPES}SCENE"
|
||||
TYPE_SENSOR = f"{PREFIX_TYPES}SENSOR"
|
||||
TYPE_SETTOP = f"{PREFIX_TYPES}SETTOP"
|
||||
TYPE_SHUTTER = f"{PREFIX_TYPES}SHUTTER"
|
||||
TYPE_SMOKE_DETECTOR = f"{PREFIX_TYPES}SMOKE_DETECTOR"
|
||||
TYPE_SPEAKER = f"{PREFIX_TYPES}SPEAKER"
|
||||
TYPE_SWITCH = f"{PREFIX_TYPES}SWITCH"
|
||||
TYPE_THERMOSTAT = f"{PREFIX_TYPES}THERMOSTAT"
|
||||
@ -136,6 +138,7 @@ EVENT_SYNC_RECEIVED = "google_assistant_sync"
|
||||
|
||||
DOMAIN_TO_GOOGLE_TYPES = {
|
||||
alarm_control_panel.DOMAIN: TYPE_ALARM,
|
||||
binary_sensor.DOMAIN: TYPE_SENSOR,
|
||||
button.DOMAIN: TYPE_SCENE,
|
||||
camera.DOMAIN: TYPE_CAMERA,
|
||||
climate.DOMAIN: TYPE_THERMOSTAT,
|
||||
@ -168,6 +171,14 @@ DEVICE_CLASS_TO_GOOGLE_TYPES = {
|
||||
binary_sensor.DOMAIN,
|
||||
binary_sensor.BinarySensorDeviceClass.GARAGE_DOOR,
|
||||
): TYPE_GARAGE,
|
||||
(
|
||||
binary_sensor.DOMAIN,
|
||||
binary_sensor.BinarySensorDeviceClass.SMOKE,
|
||||
): TYPE_SMOKE_DETECTOR,
|
||||
(
|
||||
binary_sensor.DOMAIN,
|
||||
binary_sensor.BinarySensorDeviceClass.CO,
|
||||
): TYPE_CARBON_MONOXIDE_DETECTOR,
|
||||
(cover.DOMAIN, cover.CoverDeviceClass.AWNING): TYPE_AWNING,
|
||||
(cover.DOMAIN, cover.CoverDeviceClass.CURTAIN): TYPE_CURTAIN,
|
||||
(cover.DOMAIN, cover.CoverDeviceClass.DOOR): TYPE_DOOR,
|
||||
|
@ -2706,6 +2706,21 @@ class SensorStateTrait(_Trait):
|
||||
),
|
||||
}
|
||||
|
||||
binary_sensor_types = {
|
||||
binary_sensor.BinarySensorDeviceClass.CO: (
|
||||
"CarbonMonoxideLevel",
|
||||
["carbon monoxide detected", "no carbon monoxide detected", "unknown"],
|
||||
),
|
||||
binary_sensor.BinarySensorDeviceClass.SMOKE: (
|
||||
"SmokeLevel",
|
||||
["smoke detected", "no smoke detected", "unknown"],
|
||||
),
|
||||
binary_sensor.BinarySensorDeviceClass.MOISTURE: (
|
||||
"WaterLeak",
|
||||
["leak", "no leak", "unknown"],
|
||||
),
|
||||
}
|
||||
|
||||
name = TRAIT_SENSOR_STATE
|
||||
commands: list[str] = []
|
||||
|
||||
@ -2728,24 +2743,37 @@ class SensorStateTrait(_Trait):
|
||||
@classmethod
|
||||
def supported(cls, domain, features, device_class, _):
|
||||
"""Test if state is supported."""
|
||||
return domain == sensor.DOMAIN and device_class in cls.sensor_types
|
||||
return (domain == sensor.DOMAIN and device_class in cls.sensor_types) or (
|
||||
domain == binary_sensor.DOMAIN and device_class in cls.binary_sensor_types
|
||||
)
|
||||
|
||||
def sync_attributes(self) -> dict[str, Any]:
|
||||
"""Return attributes for a sync request."""
|
||||
device_class = self.state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
data = self.sensor_types.get(device_class)
|
||||
|
||||
if device_class is None or data is None:
|
||||
return {}
|
||||
def create_sensor_state(
|
||||
name: str,
|
||||
raw_value_unit: str | None = None,
|
||||
available_states: list[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
sensor_state: dict[str, Any] = {
|
||||
"name": name,
|
||||
}
|
||||
if raw_value_unit:
|
||||
sensor_state["numericCapabilities"] = {"rawValueUnit": raw_value_unit}
|
||||
if available_states:
|
||||
sensor_state["descriptiveCapabilities"] = {
|
||||
"availableStates": available_states
|
||||
}
|
||||
return {"sensorStatesSupported": [sensor_state]}
|
||||
|
||||
sensor_state = {
|
||||
"name": data[0],
|
||||
"numericCapabilities": {"rawValueUnit": data[1]},
|
||||
}
|
||||
|
||||
if device_class == sensor.SensorDeviceClass.AQI:
|
||||
sensor_state["descriptiveCapabilities"] = {
|
||||
"availableStates": [
|
||||
if self.state.domain == sensor.DOMAIN:
|
||||
sensor_data = self.sensor_types.get(device_class)
|
||||
if device_class is None or sensor_data is None:
|
||||
return {}
|
||||
available_states: list[str] | None = None
|
||||
if device_class == sensor.SensorDeviceClass.AQI:
|
||||
available_states = [
|
||||
"healthy",
|
||||
"moderate",
|
||||
"unhealthy for sensitive groups",
|
||||
@ -2753,30 +2781,53 @@ class SensorStateTrait(_Trait):
|
||||
"very unhealthy",
|
||||
"hazardous",
|
||||
"unknown",
|
||||
],
|
||||
}
|
||||
|
||||
return {"sensorStatesSupported": [sensor_state]}
|
||||
]
|
||||
return create_sensor_state(sensor_data[0], sensor_data[1], available_states)
|
||||
binary_sensor_data = self.binary_sensor_types.get(device_class)
|
||||
if device_class is None or binary_sensor_data is None:
|
||||
return {}
|
||||
return create_sensor_state(
|
||||
binary_sensor_data[0], available_states=binary_sensor_data[1]
|
||||
)
|
||||
|
||||
def query_attributes(self) -> dict[str, Any]:
|
||||
"""Return the attributes of this trait for this entity."""
|
||||
device_class = self.state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
data = self.sensor_types.get(device_class)
|
||||
|
||||
if device_class is None or data is None:
|
||||
def create_sensor_state(
|
||||
name: str, raw_value: float | None = None, current_state: str | None = None
|
||||
) -> dict[str, Any]:
|
||||
sensor_state: dict[str, Any] = {
|
||||
"name": name,
|
||||
"rawValue": raw_value,
|
||||
}
|
||||
if current_state:
|
||||
sensor_state["currentSensorState"] = current_state
|
||||
return {"currentSensorStateData": [sensor_state]}
|
||||
|
||||
if self.state.domain == sensor.DOMAIN:
|
||||
sensor_data = self.sensor_types.get(device_class)
|
||||
if device_class is None or sensor_data is None:
|
||||
return {}
|
||||
try:
|
||||
value = float(self.state.state)
|
||||
except ValueError:
|
||||
value = None
|
||||
if self.state.state == STATE_UNKNOWN:
|
||||
value = None
|
||||
current_state: str | None = None
|
||||
if device_class == sensor.SensorDeviceClass.AQI:
|
||||
current_state = self._air_quality_description_for_aqi(value)
|
||||
return create_sensor_state(sensor_data[0], value, current_state)
|
||||
|
||||
binary_sensor_data = self.binary_sensor_types.get(device_class)
|
||||
if device_class is None or binary_sensor_data is None:
|
||||
return {}
|
||||
|
||||
try:
|
||||
value = float(self.state.state)
|
||||
except ValueError:
|
||||
value = None
|
||||
if self.state.state == STATE_UNKNOWN:
|
||||
value = None
|
||||
sensor_data = {"name": data[0], "rawValue": value}
|
||||
|
||||
if device_class == sensor.SensorDeviceClass.AQI:
|
||||
sensor_data["currentSensorState"] = self._air_quality_description_for_aqi(
|
||||
value
|
||||
)
|
||||
|
||||
return {"currentSensorStateData": [sensor_data]}
|
||||
value = {
|
||||
STATE_ON: 0,
|
||||
STATE_OFF: 1,
|
||||
STATE_UNKNOWN: 2,
|
||||
}[self.state.state]
|
||||
return create_sensor_state(
|
||||
binary_sensor_data[0], current_state=binary_sensor_data[1][value]
|
||||
)
|
||||
|
@ -4069,3 +4069,90 @@ async def test_sensorstate(
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("state", "identifier"),
|
||||
[
|
||||
(STATE_ON, 0),
|
||||
(STATE_OFF, 1),
|
||||
(STATE_UNKNOWN, 2),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
("device_class", "name", "states"),
|
||||
[
|
||||
(
|
||||
binary_sensor.BinarySensorDeviceClass.CO,
|
||||
"CarbonMonoxideLevel",
|
||||
["carbon monoxide detected", "no carbon monoxide detected", "unknown"],
|
||||
),
|
||||
(
|
||||
binary_sensor.BinarySensorDeviceClass.SMOKE,
|
||||
"SmokeLevel",
|
||||
["smoke detected", "no smoke detected", "unknown"],
|
||||
),
|
||||
(
|
||||
binary_sensor.BinarySensorDeviceClass.MOISTURE,
|
||||
"WaterLeak",
|
||||
["leak", "no leak", "unknown"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_binary_sensorstate(
|
||||
hass: HomeAssistant,
|
||||
state: str,
|
||||
identifier: int,
|
||||
device_class: binary_sensor.BinarySensorDeviceClass,
|
||||
name: str,
|
||||
states: list[str],
|
||||
) -> None:
|
||||
"""Test SensorState trait support for binary sensor domain."""
|
||||
|
||||
assert helpers.get_google_type(binary_sensor.DOMAIN, None) is not None
|
||||
assert trait.SensorStateTrait.supported(
|
||||
binary_sensor.DOMAIN, None, device_class, None
|
||||
)
|
||||
|
||||
trt = trait.SensorStateTrait(
|
||||
hass,
|
||||
State(
|
||||
"binary_sensor.test",
|
||||
state,
|
||||
{
|
||||
"device_class": device_class,
|
||||
},
|
||||
),
|
||||
BASIC_CONFIG,
|
||||
)
|
||||
|
||||
assert trt.sync_attributes() == {
|
||||
"sensorStatesSupported": [
|
||||
{
|
||||
"name": name,
|
||||
"descriptiveCapabilities": {
|
||||
"availableStates": states,
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
assert trt.query_attributes() == {
|
||||
"currentSensorStateData": [
|
||||
{
|
||||
"name": name,
|
||||
"currentSensorState": states[identifier],
|
||||
"rawValue": None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
assert helpers.get_google_type(binary_sensor.DOMAIN, None) is not None
|
||||
assert (
|
||||
trait.SensorStateTrait.supported(
|
||||
binary_sensor.DOMAIN,
|
||||
None,
|
||||
binary_sensor.BinarySensorDeviceClass.TAMPER,
|
||||
None,
|
||||
)
|
||||
is False
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user