mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add humidifier support to google_assistant (#37157)
This commit is contained in:
parent
01ba578016
commit
06e977b444
@ -7,6 +7,7 @@ from homeassistant.components import (
|
|||||||
cover,
|
cover,
|
||||||
fan,
|
fan,
|
||||||
group,
|
group,
|
||||||
|
humidifier,
|
||||||
input_boolean,
|
input_boolean,
|
||||||
input_select,
|
input_select,
|
||||||
light,
|
light,
|
||||||
@ -44,6 +45,7 @@ DEFAULT_EXPOSED_DOMAINS = [
|
|||||||
"cover",
|
"cover",
|
||||||
"fan",
|
"fan",
|
||||||
"group",
|
"group",
|
||||||
|
"humidifier",
|
||||||
"input_boolean",
|
"input_boolean",
|
||||||
"input_select",
|
"input_select",
|
||||||
"light",
|
"light",
|
||||||
@ -76,6 +78,8 @@ TYPE_TV = f"{PREFIX_TYPES}TV"
|
|||||||
TYPE_SPEAKER = f"{PREFIX_TYPES}SPEAKER"
|
TYPE_SPEAKER = f"{PREFIX_TYPES}SPEAKER"
|
||||||
TYPE_ALARM = f"{PREFIX_TYPES}SECURITYSYSTEM"
|
TYPE_ALARM = f"{PREFIX_TYPES}SECURITYSYSTEM"
|
||||||
TYPE_SETTOP = f"{PREFIX_TYPES}SETTOP"
|
TYPE_SETTOP = f"{PREFIX_TYPES}SETTOP"
|
||||||
|
TYPE_HUMIDIFIER = f"{PREFIX_TYPES}HUMIDIFIER"
|
||||||
|
TYPE_DEHUMIDIFIER = f"{PREFIX_TYPES}DEHUMIDIFIER"
|
||||||
|
|
||||||
SERVICE_REQUEST_SYNC = "request_sync"
|
SERVICE_REQUEST_SYNC = "request_sync"
|
||||||
HOMEGRAPH_URL = "https://homegraph.googleapis.com/"
|
HOMEGRAPH_URL = "https://homegraph.googleapis.com/"
|
||||||
@ -114,6 +118,7 @@ DOMAIN_TO_GOOGLE_TYPES = {
|
|||||||
cover.DOMAIN: TYPE_BLINDS,
|
cover.DOMAIN: TYPE_BLINDS,
|
||||||
fan.DOMAIN: TYPE_FAN,
|
fan.DOMAIN: TYPE_FAN,
|
||||||
group.DOMAIN: TYPE_SWITCH,
|
group.DOMAIN: TYPE_SWITCH,
|
||||||
|
humidifier.DOMAIN: TYPE_HUMIDIFIER,
|
||||||
input_boolean.DOMAIN: TYPE_SWITCH,
|
input_boolean.DOMAIN: TYPE_SWITCH,
|
||||||
input_select.DOMAIN: TYPE_SENSOR,
|
input_select.DOMAIN: TYPE_SENSOR,
|
||||||
light.DOMAIN: TYPE_LIGHT,
|
light.DOMAIN: TYPE_LIGHT,
|
||||||
@ -140,6 +145,8 @@ DEVICE_CLASS_TO_GOOGLE_TYPES = {
|
|||||||
(media_player.DOMAIN, media_player.DEVICE_CLASS_TV): TYPE_TV,
|
(media_player.DOMAIN, media_player.DEVICE_CLASS_TV): TYPE_TV,
|
||||||
(sensor.DOMAIN, sensor.DEVICE_CLASS_TEMPERATURE): TYPE_SENSOR,
|
(sensor.DOMAIN, sensor.DEVICE_CLASS_TEMPERATURE): TYPE_SENSOR,
|
||||||
(sensor.DOMAIN, sensor.DEVICE_CLASS_HUMIDITY): TYPE_SENSOR,
|
(sensor.DOMAIN, sensor.DEVICE_CLASS_HUMIDITY): TYPE_SENSOR,
|
||||||
|
(humidifier.DOMAIN, humidifier.DEVICE_CLASS_HUMIDIFIER): TYPE_HUMIDIFIER,
|
||||||
|
(humidifier.DOMAIN, humidifier.DEVICE_CLASS_DEHUMIDIFIER): TYPE_DEHUMIDIFIER,
|
||||||
}
|
}
|
||||||
|
|
||||||
CHALLENGE_ACK_NEEDED = "ackNeeded"
|
CHALLENGE_ACK_NEEDED = "ackNeeded"
|
||||||
|
@ -20,6 +20,7 @@ from homeassistant.components import (
|
|||||||
vacuum,
|
vacuum,
|
||||||
)
|
)
|
||||||
from homeassistant.components.climate import const as climate
|
from homeassistant.components.climate import const as climate
|
||||||
|
from homeassistant.components.humidifier import const as humidifier
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ASSUMED_STATE,
|
ATTR_ASSUMED_STATE,
|
||||||
ATTR_CODE,
|
ATTR_CODE,
|
||||||
@ -123,6 +124,7 @@ COMMAND_MEDIA_SEEK_RELATIVE = f"{PREFIX_COMMANDS}mediaSeekRelative"
|
|||||||
COMMAND_MEDIA_SEEK_TO_POSITION = f"{PREFIX_COMMANDS}mediaSeekToPosition"
|
COMMAND_MEDIA_SEEK_TO_POSITION = f"{PREFIX_COMMANDS}mediaSeekToPosition"
|
||||||
COMMAND_MEDIA_SHUFFLE = f"{PREFIX_COMMANDS}mediaShuffle"
|
COMMAND_MEDIA_SHUFFLE = f"{PREFIX_COMMANDS}mediaShuffle"
|
||||||
COMMAND_MEDIA_STOP = f"{PREFIX_COMMANDS}mediaStop"
|
COMMAND_MEDIA_STOP = f"{PREFIX_COMMANDS}mediaStop"
|
||||||
|
COMMAND_SET_HUMIDITY = f"{PREFIX_COMMANDS}SetHumidity"
|
||||||
|
|
||||||
|
|
||||||
TRAITS = []
|
TRAITS = []
|
||||||
@ -287,6 +289,7 @@ class OnOffTrait(_Trait):
|
|||||||
fan.DOMAIN,
|
fan.DOMAIN,
|
||||||
light.DOMAIN,
|
light.DOMAIN,
|
||||||
media_player.DOMAIN,
|
media_player.DOMAIN,
|
||||||
|
humidifier.DOMAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
@ -295,7 +298,7 @@ class OnOffTrait(_Trait):
|
|||||||
|
|
||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
"""Return OnOff query attributes."""
|
"""Return OnOff query attributes."""
|
||||||
return {"on": self.state.state != STATE_OFF}
|
return {"on": self.state.state not in (STATE_OFF, STATE_UNKNOWN)}
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute an OnOff command."""
|
"""Execute an OnOff command."""
|
||||||
@ -883,11 +886,14 @@ class HumiditySettingTrait(_Trait):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
name = TRAIT_HUMIDITY_SETTING
|
name = TRAIT_HUMIDITY_SETTING
|
||||||
commands = []
|
commands = [COMMAND_SET_HUMIDITY]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def supported(domain, features, device_class):
|
def supported(domain, features, device_class):
|
||||||
"""Test if state is supported."""
|
"""Test if state is supported."""
|
||||||
|
if domain == humidifier.DOMAIN:
|
||||||
|
return True
|
||||||
|
|
||||||
return domain == sensor.DOMAIN and device_class == sensor.DEVICE_CLASS_HUMIDITY
|
return domain == sensor.DOMAIN and device_class == sensor.DEVICE_CLASS_HUMIDITY
|
||||||
|
|
||||||
def sync_attributes(self):
|
def sync_attributes(self):
|
||||||
@ -895,11 +901,22 @@ class HumiditySettingTrait(_Trait):
|
|||||||
response = {}
|
response = {}
|
||||||
attrs = self.state.attributes
|
attrs = self.state.attributes
|
||||||
domain = self.state.domain
|
domain = self.state.domain
|
||||||
|
|
||||||
if domain == sensor.DOMAIN:
|
if domain == sensor.DOMAIN:
|
||||||
device_class = attrs.get(ATTR_DEVICE_CLASS)
|
device_class = attrs.get(ATTR_DEVICE_CLASS)
|
||||||
if device_class == sensor.DEVICE_CLASS_HUMIDITY:
|
if device_class == sensor.DEVICE_CLASS_HUMIDITY:
|
||||||
response["queryOnlyHumiditySetting"] = True
|
response["queryOnlyHumiditySetting"] = True
|
||||||
|
|
||||||
|
elif domain == humidifier.DOMAIN:
|
||||||
|
response["humiditySetpointRange"] = {
|
||||||
|
"minPercent": round(
|
||||||
|
float(self.state.attributes[humidifier.ATTR_MIN_HUMIDITY])
|
||||||
|
),
|
||||||
|
"maxPercent": round(
|
||||||
|
float(self.state.attributes[humidifier.ATTR_MAX_HUMIDITY])
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def query_attributes(self):
|
def query_attributes(self):
|
||||||
@ -907,6 +924,7 @@ class HumiditySettingTrait(_Trait):
|
|||||||
response = {}
|
response = {}
|
||||||
attrs = self.state.attributes
|
attrs = self.state.attributes
|
||||||
domain = self.state.domain
|
domain = self.state.domain
|
||||||
|
|
||||||
if domain == sensor.DOMAIN:
|
if domain == sensor.DOMAIN:
|
||||||
device_class = attrs.get(ATTR_DEVICE_CLASS)
|
device_class = attrs.get(ATTR_DEVICE_CLASS)
|
||||||
if device_class == sensor.DEVICE_CLASS_HUMIDITY:
|
if device_class == sensor.DEVICE_CLASS_HUMIDITY:
|
||||||
@ -914,16 +932,34 @@ class HumiditySettingTrait(_Trait):
|
|||||||
if current_humidity not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
if current_humidity not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||||
response["humidityAmbientPercent"] = round(float(current_humidity))
|
response["humidityAmbientPercent"] = round(float(current_humidity))
|
||||||
|
|
||||||
|
elif domain == humidifier.DOMAIN:
|
||||||
|
target_humidity = attrs.get(humidifier.ATTR_HUMIDITY)
|
||||||
|
if target_humidity is not None:
|
||||||
|
response["humiditySetpointPercent"] = round(float(target_humidity))
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute a humidity command."""
|
"""Execute a humidity command."""
|
||||||
domain = self.state.domain
|
domain = self.state.domain
|
||||||
|
|
||||||
if domain == sensor.DOMAIN:
|
if domain == sensor.DOMAIN:
|
||||||
raise SmartHomeError(
|
raise SmartHomeError(
|
||||||
ERR_NOT_SUPPORTED, "Execute is not supported by sensor"
|
ERR_NOT_SUPPORTED, "Execute is not supported by sensor"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if command == COMMAND_SET_HUMIDITY:
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
humidifier.DOMAIN,
|
||||||
|
humidifier.SERVICE_SET_HUMIDITY,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
|
humidifier.ATTR_HUMIDITY: params["humidity"],
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
context=data.context,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@register_trait
|
@register_trait
|
||||||
class LockUnlockTrait(_Trait):
|
class LockUnlockTrait(_Trait):
|
||||||
@ -1151,7 +1187,6 @@ class FanSpeedTrait(_Trait):
|
|||||||
speed = attrs.get(fan.ATTR_SPEED)
|
speed = attrs.get(fan.ATTR_SPEED)
|
||||||
if speed is not None:
|
if speed is not None:
|
||||||
response["on"] = speed != fan.SPEED_OFF
|
response["on"] = speed != fan.SPEED_OFF
|
||||||
response["online"] = True
|
|
||||||
response["currentFanSpeedSetting"] = speed
|
response["currentFanSpeedSetting"] = speed
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -1189,6 +1224,9 @@ class ModesTrait(_Trait):
|
|||||||
if domain == input_select.DOMAIN:
|
if domain == input_select.DOMAIN:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if domain == humidifier.DOMAIN and features & humidifier.SUPPORT_MODES:
|
||||||
|
return True
|
||||||
|
|
||||||
if domain != media_player.DOMAIN:
|
if domain != media_player.DOMAIN:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -1241,6 +1279,9 @@ class ModesTrait(_Trait):
|
|||||||
)
|
)
|
||||||
elif self.state.domain == input_select.DOMAIN:
|
elif self.state.domain == input_select.DOMAIN:
|
||||||
modes.append(_generate("option", attrs[input_select.ATTR_OPTIONS]))
|
modes.append(_generate("option", attrs[input_select.ATTR_OPTIONS]))
|
||||||
|
elif self.state.domain == humidifier.DOMAIN:
|
||||||
|
if humidifier.ATTR_AVAILABLE_MODES in attrs:
|
||||||
|
modes.append(_generate("mode", attrs[humidifier.ATTR_AVAILABLE_MODES]))
|
||||||
|
|
||||||
payload = {"availableModes": modes}
|
payload = {"availableModes": modes}
|
||||||
|
|
||||||
@ -1262,16 +1303,18 @@ class ModesTrait(_Trait):
|
|||||||
mode_settings["sound mode"] = attrs.get(media_player.ATTR_SOUND_MODE)
|
mode_settings["sound mode"] = attrs.get(media_player.ATTR_SOUND_MODE)
|
||||||
elif self.state.domain == input_select.DOMAIN:
|
elif self.state.domain == input_select.DOMAIN:
|
||||||
mode_settings["option"] = self.state.state
|
mode_settings["option"] = self.state.state
|
||||||
|
elif self.state.domain == humidifier.DOMAIN:
|
||||||
|
if humidifier.ATTR_MODE in attrs:
|
||||||
|
mode_settings["mode"] = attrs.get(humidifier.ATTR_MODE)
|
||||||
|
|
||||||
if mode_settings:
|
if mode_settings:
|
||||||
response["on"] = self.state.state != STATE_OFF
|
response["on"] = self.state.state not in (STATE_OFF, STATE_UNKNOWN)
|
||||||
response["online"] = True
|
|
||||||
response["currentModeSettings"] = mode_settings
|
response["currentModeSettings"] = mode_settings
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
async def execute(self, command, data, params, challenge):
|
async def execute(self, command, data, params, challenge):
|
||||||
"""Execute an SetModes command."""
|
"""Execute a SetModes command."""
|
||||||
settings = params.get("updateModeSettings")
|
settings = params.get("updateModeSettings")
|
||||||
|
|
||||||
if self.state.domain == input_select.DOMAIN:
|
if self.state.domain == input_select.DOMAIN:
|
||||||
@ -1286,8 +1329,22 @@ class ModesTrait(_Trait):
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
context=data.context,
|
context=data.context,
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.state.domain == humidifier.DOMAIN:
|
||||||
|
requested_mode = settings["mode"]
|
||||||
|
await self.hass.services.async_call(
|
||||||
|
humidifier.DOMAIN,
|
||||||
|
humidifier.SERVICE_SET_MODE,
|
||||||
|
{
|
||||||
|
humidifier.ATTR_MODE: requested_mode,
|
||||||
|
ATTR_ENTITY_ID: self.state.entity_id,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
context=data.context,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if self.state.domain != media_player.DOMAIN:
|
if self.state.domain != media_player.DOMAIN:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Received an Options command for unrecognised domain %s",
|
"Received an Options command for unrecognised domain %s",
|
||||||
|
@ -122,6 +122,7 @@ ALLOWED_USED_COMPONENTS = {
|
|||||||
"cover",
|
"cover",
|
||||||
"device_tracker",
|
"device_tracker",
|
||||||
"fan",
|
"fan",
|
||||||
|
"humidifier",
|
||||||
"image_processing",
|
"image_processing",
|
||||||
"light",
|
"light",
|
||||||
"lock",
|
"lock",
|
||||||
|
@ -250,6 +250,40 @@ DEMO_DEVICES = [
|
|||||||
"type": "action.devices.types.THERMOSTAT",
|
"type": "action.devices.types.THERMOSTAT",
|
||||||
"willReportState": False,
|
"willReportState": False,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "humidifier.humidifier",
|
||||||
|
"name": {"name": "Humidifier"},
|
||||||
|
"traits": [
|
||||||
|
"action.devices.traits.HumiditySetting",
|
||||||
|
"action.devices.traits.OnOff",
|
||||||
|
],
|
||||||
|
"type": "action.devices.types.HUMIDIFIER",
|
||||||
|
"willReportState": False,
|
||||||
|
"attributes": {"humiditySetpointRange": {"minPercent": 0, "maxPercent": 100}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "humidifier.dehumidifier",
|
||||||
|
"name": {"name": "Dehumidifier"},
|
||||||
|
"traits": [
|
||||||
|
"action.devices.traits.HumiditySetting",
|
||||||
|
"action.devices.traits.OnOff",
|
||||||
|
],
|
||||||
|
"type": "action.devices.types.DEHUMIDIFIER",
|
||||||
|
"willReportState": False,
|
||||||
|
"attributes": {"humiditySetpointRange": {"minPercent": 0, "maxPercent": 100}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "humidifier.hygrostat",
|
||||||
|
"name": {"name": "Hygrostat"},
|
||||||
|
"traits": [
|
||||||
|
"action.devices.traits.HumiditySetting",
|
||||||
|
"action.devices.traits.Modes",
|
||||||
|
"action.devices.traits.OnOff",
|
||||||
|
],
|
||||||
|
"type": "action.devices.types.HUMIDIFIER",
|
||||||
|
"willReportState": False,
|
||||||
|
"attributes": {"humiditySetpointRange": {"minPercent": 0, "maxPercent": 100}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "lock.front_door",
|
"id": "lock.front_door",
|
||||||
"name": {"name": "Front Door"},
|
"name": {"name": "Front Door"},
|
||||||
|
@ -17,6 +17,7 @@ from homeassistant.components import (
|
|||||||
switch,
|
switch,
|
||||||
)
|
)
|
||||||
from homeassistant.components.climate import const as climate
|
from homeassistant.components.climate import const as climate
|
||||||
|
from homeassistant.components.humidifier import const as humidifier
|
||||||
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
|
from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES
|
||||||
|
|
||||||
from . import DEMO_DEVICES
|
from . import DEMO_DEVICES
|
||||||
@ -96,6 +97,12 @@ def hass_fixture(loop, hass):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
loop.run_until_complete(
|
||||||
|
setup.async_setup_component(
|
||||||
|
hass, humidifier.DOMAIN, {"humidifier": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
loop.run_until_complete(
|
loop.run_until_complete(
|
||||||
setup.async_setup_component(hass, lock.DOMAIN, {"lock": [{"platform": "demo"}]})
|
setup.async_setup_component(hass, lock.DOMAIN, {"lock": [{"platform": "demo"}]})
|
||||||
)
|
)
|
||||||
@ -292,6 +299,52 @@ async def test_query_climate_request_f(hass_fixture, assistant_client, auth_head
|
|||||||
hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS
|
hass_fixture.config.units.temperature_unit = const.TEMP_CELSIUS
|
||||||
|
|
||||||
|
|
||||||
|
async def test_query_humidifier_request(hass_fixture, assistant_client, auth_header):
|
||||||
|
"""Test a query request."""
|
||||||
|
reqid = "5711642932632160984"
|
||||||
|
data = {
|
||||||
|
"requestId": reqid,
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"intent": "action.devices.QUERY",
|
||||||
|
"payload": {
|
||||||
|
"devices": [
|
||||||
|
{"id": "humidifier.humidifier"},
|
||||||
|
{"id": "humidifier.dehumidifier"},
|
||||||
|
{"id": "humidifier.hygrostat"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
result = await assistant_client.post(
|
||||||
|
ga.const.GOOGLE_ASSISTANT_API_ENDPOINT,
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=auth_header,
|
||||||
|
)
|
||||||
|
assert result.status == 200
|
||||||
|
body = await result.json()
|
||||||
|
assert body.get("requestId") == reqid
|
||||||
|
devices = body["payload"]["devices"]
|
||||||
|
assert len(devices) == 3
|
||||||
|
assert devices["humidifier.humidifier"] == {
|
||||||
|
"on": True,
|
||||||
|
"online": True,
|
||||||
|
"humiditySetpointPercent": 68,
|
||||||
|
}
|
||||||
|
assert devices["humidifier.dehumidifier"] == {
|
||||||
|
"on": True,
|
||||||
|
"online": True,
|
||||||
|
"humiditySetpointPercent": 54,
|
||||||
|
}
|
||||||
|
assert devices["humidifier.hygrostat"] == {
|
||||||
|
"on": True,
|
||||||
|
"online": True,
|
||||||
|
"humiditySetpointPercent": 50,
|
||||||
|
"currentModeSettings": {"mode": "home"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_execute_request(hass_fixture, assistant_client, auth_header):
|
async def test_execute_request(hass_fixture, assistant_client, auth_header):
|
||||||
"""Test an execute request."""
|
"""Test an execute request."""
|
||||||
reqid = "5711642932632160985"
|
reqid = "5711642932632160985"
|
||||||
@ -346,6 +399,33 @@ async def test_execute_request(hass_fixture, assistant_client, auth_header):
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"devices": [{"id": "humidifier.humidifier"}],
|
||||||
|
"execution": [
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.OnOff",
|
||||||
|
"params": {"on": False},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"devices": [{"id": "humidifier.dehumidifier"}],
|
||||||
|
"execution": [
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.SetHumidity",
|
||||||
|
"params": {"humidity": 45},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"devices": [{"id": "humidifier.hygrostat"}],
|
||||||
|
"execution": [
|
||||||
|
{
|
||||||
|
"command": "action.devices.commands.SetModes",
|
||||||
|
"params": {"updateModeSettings": {"mode": "eco"}},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -360,7 +440,7 @@ async def test_execute_request(hass_fixture, assistant_client, auth_header):
|
|||||||
body = await result.json()
|
body = await result.json()
|
||||||
assert body.get("requestId") == reqid
|
assert body.get("requestId") == reqid
|
||||||
commands = body["payload"]["commands"]
|
commands = body["payload"]["commands"]
|
||||||
assert len(commands) == 6
|
assert len(commands) == 9
|
||||||
|
|
||||||
assert not any(result["status"] == "ERROR" for result in commands)
|
assert not any(result["status"] == "ERROR" for result in commands)
|
||||||
|
|
||||||
@ -381,3 +461,12 @@ async def test_execute_request(hass_fixture, assistant_client, auth_header):
|
|||||||
|
|
||||||
lounge = hass_fixture.states.get("media_player.lounge_room")
|
lounge = hass_fixture.states.get("media_player.lounge_room")
|
||||||
assert lounge.state == "off"
|
assert lounge.state == "off"
|
||||||
|
|
||||||
|
humidifier_state = hass_fixture.states.get("humidifier.humidifier")
|
||||||
|
assert humidifier_state.state == "off"
|
||||||
|
|
||||||
|
dehumidifier = hass_fixture.states.get("humidifier.dehumidifier")
|
||||||
|
assert dehumidifier.attributes.get(humidifier.ATTR_HUMIDITY) == 45
|
||||||
|
|
||||||
|
hygrostat = hass_fixture.states.get("humidifier.hygrostat")
|
||||||
|
assert hygrostat.attributes.get(humidifier.ATTR_MODE) == "eco"
|
||||||
|
@ -24,6 +24,7 @@ from homeassistant.components import (
|
|||||||
)
|
)
|
||||||
from homeassistant.components.climate import const as climate
|
from homeassistant.components.climate import const as climate
|
||||||
from homeassistant.components.google_assistant import const, error, helpers, trait
|
from homeassistant.components.google_assistant import const, error, helpers, trait
|
||||||
|
from homeassistant.components.humidifier import const as humidifier
|
||||||
from homeassistant.config import async_process_ha_core_config
|
from homeassistant.config import async_process_ha_core_config
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ASSUMED_STATE,
|
ATTR_ASSUMED_STATE,
|
||||||
@ -294,6 +295,33 @@ async def test_onoff_media_player(hass):
|
|||||||
assert off_calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"}
|
assert off_calls[0].data == {ATTR_ENTITY_ID: "media_player.bla"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_onoff_humidifier(hass):
|
||||||
|
"""Test OnOff trait support for humidifier domain."""
|
||||||
|
assert helpers.get_google_type(humidifier.DOMAIN, None) is not None
|
||||||
|
assert trait.OnOffTrait.supported(humidifier.DOMAIN, 0, None)
|
||||||
|
|
||||||
|
trt_on = trait.OnOffTrait(hass, State("humidifier.bla", STATE_ON), BASIC_CONFIG)
|
||||||
|
|
||||||
|
assert trt_on.sync_attributes() == {}
|
||||||
|
|
||||||
|
assert trt_on.query_attributes() == {"on": True}
|
||||||
|
|
||||||
|
trt_off = trait.OnOffTrait(hass, State("humidifier.bla", STATE_OFF), BASIC_CONFIG)
|
||||||
|
|
||||||
|
assert trt_off.query_attributes() == {"on": False}
|
||||||
|
|
||||||
|
on_calls = async_mock_service(hass, humidifier.DOMAIN, SERVICE_TURN_ON)
|
||||||
|
await trt_on.execute(trait.COMMAND_ONOFF, BASIC_DATA, {"on": True}, {})
|
||||||
|
assert len(on_calls) == 1
|
||||||
|
assert on_calls[0].data == {ATTR_ENTITY_ID: "humidifier.bla"}
|
||||||
|
|
||||||
|
off_calls = async_mock_service(hass, humidifier.DOMAIN, SERVICE_TURN_OFF)
|
||||||
|
|
||||||
|
await trt_on.execute(trait.COMMAND_ONOFF, BASIC_DATA, {"on": False}, {})
|
||||||
|
assert len(off_calls) == 1
|
||||||
|
assert off_calls[0].data == {ATTR_ENTITY_ID: "humidifier.bla"}
|
||||||
|
|
||||||
|
|
||||||
async def test_dock_vacuum(hass):
|
async def test_dock_vacuum(hass):
|
||||||
"""Test dock trait support for vacuum domain."""
|
"""Test dock trait support for vacuum domain."""
|
||||||
assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
|
assert helpers.get_google_type(vacuum.DOMAIN, None) is not None
|
||||||
@ -780,6 +808,42 @@ async def test_temperature_setting_climate_setpoint_auto(hass):
|
|||||||
assert calls[0].data == {ATTR_ENTITY_ID: "climate.bla", ATTR_TEMPERATURE: 19}
|
assert calls[0].data == {ATTR_ENTITY_ID: "climate.bla", ATTR_TEMPERATURE: 19}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_humidity_setting_humidifier_setpoint(hass):
|
||||||
|
"""Test HumiditySetting trait support for humidifier domain - setpoint."""
|
||||||
|
assert helpers.get_google_type(humidifier.DOMAIN, None) is not None
|
||||||
|
assert trait.HumiditySettingTrait.supported(humidifier.DOMAIN, 0, None)
|
||||||
|
|
||||||
|
trt = trait.HumiditySettingTrait(
|
||||||
|
hass,
|
||||||
|
State(
|
||||||
|
"humidifier.bla",
|
||||||
|
STATE_ON,
|
||||||
|
{
|
||||||
|
humidifier.ATTR_MIN_HUMIDITY: 20,
|
||||||
|
humidifier.ATTR_MAX_HUMIDITY: 90,
|
||||||
|
humidifier.ATTR_HUMIDITY: 38,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
assert trt.sync_attributes() == {
|
||||||
|
"humiditySetpointRange": {"minPercent": 20, "maxPercent": 90}
|
||||||
|
}
|
||||||
|
assert trt.query_attributes() == {
|
||||||
|
"humiditySetpointPercent": 38,
|
||||||
|
}
|
||||||
|
assert trt.can_execute(trait.COMMAND_SET_HUMIDITY, {})
|
||||||
|
|
||||||
|
calls = async_mock_service(hass, humidifier.DOMAIN, humidifier.SERVICE_SET_HUMIDITY)
|
||||||
|
|
||||||
|
await trt.execute(trait.COMMAND_SET_HUMIDITY, BASIC_DATA, {"humidity": 32}, {})
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data == {
|
||||||
|
ATTR_ENTITY_ID: "humidifier.bla",
|
||||||
|
humidifier.ATTR_HUMIDITY: 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_lock_unlock_lock(hass):
|
async def test_lock_unlock_lock(hass):
|
||||||
"""Test LockUnlock trait locking support for lock domain."""
|
"""Test LockUnlock trait locking support for lock domain."""
|
||||||
assert helpers.get_google_type(lock.DOMAIN, None) is not None
|
assert helpers.get_google_type(lock.DOMAIN, None) is not None
|
||||||
@ -1238,7 +1302,6 @@ async def test_fan_speed(hass):
|
|||||||
assert trt.query_attributes() == {
|
assert trt.query_attributes() == {
|
||||||
"currentFanSpeedSetting": "low",
|
"currentFanSpeedSetting": "low",
|
||||||
"on": True,
|
"on": True,
|
||||||
"online": True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trt.can_execute(trait.COMMAND_FANSPEED, params={"fanSpeed": "medium"})
|
assert trt.can_execute(trait.COMMAND_FANSPEED, params={"fanSpeed": "medium"})
|
||||||
@ -1313,7 +1376,6 @@ async def test_modes_media_player(hass):
|
|||||||
assert trt.query_attributes() == {
|
assert trt.query_attributes() == {
|
||||||
"currentModeSettings": {"input source": "game"},
|
"currentModeSettings": {"input source": "game"},
|
||||||
"on": True,
|
"on": True,
|
||||||
"online": True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trt.can_execute(
|
assert trt.can_execute(
|
||||||
@ -1382,7 +1444,6 @@ async def test_modes_input_select(hass):
|
|||||||
assert trt.query_attributes() == {
|
assert trt.query_attributes() == {
|
||||||
"currentModeSettings": {"option": "abc"},
|
"currentModeSettings": {"option": "abc"},
|
||||||
"on": True,
|
"on": True,
|
||||||
"online": True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trt.can_execute(
|
assert trt.can_execute(
|
||||||
@ -1400,6 +1461,80 @@ async def test_modes_input_select(hass):
|
|||||||
assert calls[0].data == {"entity_id": "input_select.bla", "option": "xyz"}
|
assert calls[0].data == {"entity_id": "input_select.bla", "option": "xyz"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_modes_humidifier(hass):
|
||||||
|
"""Test Humidifier Mode trait."""
|
||||||
|
assert helpers.get_google_type(humidifier.DOMAIN, None) is not None
|
||||||
|
assert trait.ModesTrait.supported(humidifier.DOMAIN, humidifier.SUPPORT_MODES, None)
|
||||||
|
|
||||||
|
trt = trait.ModesTrait(
|
||||||
|
hass,
|
||||||
|
State(
|
||||||
|
"humidifier.humidifier",
|
||||||
|
STATE_OFF,
|
||||||
|
attributes={
|
||||||
|
humidifier.ATTR_AVAILABLE_MODES: [
|
||||||
|
humidifier.MODE_NORMAL,
|
||||||
|
humidifier.MODE_AUTO,
|
||||||
|
humidifier.MODE_AWAY,
|
||||||
|
],
|
||||||
|
ATTR_SUPPORTED_FEATURES: humidifier.SUPPORT_MODES,
|
||||||
|
humidifier.ATTR_MIN_HUMIDITY: 30,
|
||||||
|
humidifier.ATTR_MAX_HUMIDITY: 99,
|
||||||
|
humidifier.ATTR_HUMIDITY: 50,
|
||||||
|
humidifier.ATTR_MODE: humidifier.MODE_AUTO,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BASIC_CONFIG,
|
||||||
|
)
|
||||||
|
|
||||||
|
attribs = trt.sync_attributes()
|
||||||
|
assert attribs == {
|
||||||
|
"availableModes": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"name_values": [{"name_synonym": ["mode"], "lang": "en"}],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"setting_name": "normal",
|
||||||
|
"setting_values": [
|
||||||
|
{"setting_synonym": ["normal"], "lang": "en"}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"setting_name": "auto",
|
||||||
|
"setting_values": [{"setting_synonym": ["auto"], "lang": "en"}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"setting_name": "away",
|
||||||
|
"setting_values": [{"setting_synonym": ["away"], "lang": "en"}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"ordered": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert trt.query_attributes() == {
|
||||||
|
"currentModeSettings": {"mode": "auto"},
|
||||||
|
"on": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert trt.can_execute(
|
||||||
|
trait.COMMAND_MODES, params={"updateModeSettings": {"mode": "away"}}
|
||||||
|
)
|
||||||
|
|
||||||
|
calls = async_mock_service(hass, humidifier.DOMAIN, humidifier.SERVICE_SET_MODE)
|
||||||
|
await trt.execute(
|
||||||
|
trait.COMMAND_MODES, BASIC_DATA, {"updateModeSettings": {"mode": "away"}}, {},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data == {
|
||||||
|
"entity_id": "humidifier.humidifier",
|
||||||
|
"mode": "away",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_sound_modes(hass):
|
async def test_sound_modes(hass):
|
||||||
"""Test Mode trait."""
|
"""Test Mode trait."""
|
||||||
assert helpers.get_google_type(media_player.DOMAIN, None) is not None
|
assert helpers.get_google_type(media_player.DOMAIN, None) is not None
|
||||||
@ -1450,7 +1585,6 @@ async def test_sound_modes(hass):
|
|||||||
assert trt.query_attributes() == {
|
assert trt.query_attributes() == {
|
||||||
"currentModeSettings": {"sound mode": "stereo"},
|
"currentModeSettings": {"sound mode": "stereo"},
|
||||||
"on": True,
|
"on": True,
|
||||||
"online": True,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trt.can_execute(
|
assert trt.can_execute(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user