Alexa fan preset_mode support (#50466)
* fan preset_modes * process preset mode updates from alexa correctly * add tests * codecov patch additional tests
This commit is contained in:
parent
0e7c2cddf7
commit
7403ba1e81
6 changed files with 189 additions and 6 deletions
|
@ -1155,8 +1155,6 @@ class AlexaPowerLevelController(AlexaCapability):
|
|||
if self.entity.domain == fan.DOMAIN:
|
||||
return self.entity.attributes.get(fan.ATTR_PERCENTAGE) or 0
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class AlexaSecurityPanelController(AlexaCapability):
|
||||
"""Implements Alexa.SecurityPanelController.
|
||||
|
@ -1304,6 +1302,12 @@ class AlexaModeController(AlexaCapability):
|
|||
if mode in (fan.DIRECTION_FORWARD, fan.DIRECTION_REVERSE, STATE_UNKNOWN):
|
||||
return f"{fan.ATTR_DIRECTION}.{mode}"
|
||||
|
||||
# Fan preset_mode
|
||||
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
|
||||
mode = self.entity.attributes.get(fan.ATTR_PRESET_MODE, None)
|
||||
if mode in self.entity.attributes.get(fan.ATTR_PRESET_MODES, None):
|
||||
return f"{fan.ATTR_PRESET_MODE}.{mode}"
|
||||
|
||||
# Cover Position
|
||||
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
# Return state instead of position when using ModeController.
|
||||
|
@ -1342,6 +1346,17 @@ class AlexaModeController(AlexaCapability):
|
|||
)
|
||||
return self._resource.serialize_capability_resources()
|
||||
|
||||
# Fan preset_mode
|
||||
if self.instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
|
||||
self._resource = AlexaModeResource(
|
||||
[AlexaGlobalCatalog.SETTING_PRESET], False
|
||||
)
|
||||
for preset_mode in self.entity.attributes.get(fan.ATTR_PRESET_MODES, []):
|
||||
self._resource.add_mode(
|
||||
f"{fan.ATTR_PRESET_MODE}.{preset_mode}", [preset_mode]
|
||||
)
|
||||
return self._resource.serialize_capability_resources()
|
||||
|
||||
# Cover Position Resources
|
||||
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
self._resource = AlexaModeResource(
|
||||
|
|
|
@ -535,6 +535,7 @@ class FanCapabilities(AlexaEntity):
|
|||
if supported & fan.SUPPORT_SET_SPEED:
|
||||
yield AlexaPercentageController(self.entity)
|
||||
yield AlexaPowerLevelController(self.entity)
|
||||
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
|
||||
yield AlexaRangeController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_SPEED}"
|
||||
)
|
||||
|
@ -542,6 +543,10 @@ class FanCapabilities(AlexaEntity):
|
|||
yield AlexaToggleController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_OSCILLATING}"
|
||||
)
|
||||
if supported & fan.SUPPORT_PRESET_MODE:
|
||||
yield AlexaModeController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}"
|
||||
)
|
||||
if supported & fan.SUPPORT_DIRECTION:
|
||||
yield AlexaModeController(
|
||||
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_DIRECTION}"
|
||||
|
|
|
@ -958,6 +958,16 @@ async def async_api_set_mode(hass, config, directive, context):
|
|||
service = fan.SERVICE_SET_DIRECTION
|
||||
data[fan.ATTR_DIRECTION] = direction
|
||||
|
||||
# Fan preset_mode
|
||||
elif instance == f"{fan.DOMAIN}.{fan.ATTR_PRESET_MODE}":
|
||||
preset_mode = mode.split(".")[1]
|
||||
if preset_mode in entity.attributes.get(fan.ATTR_PRESET_MODES):
|
||||
service = fan.SERVICE_SET_PRESET_MODE
|
||||
data[fan.ATTR_PRESET_MODE] = preset_mode
|
||||
else:
|
||||
msg = f"Entity '{entity.entity_id}' does not support Preset '{preset_mode}'"
|
||||
raise AlexaInvalidValueError(msg)
|
||||
|
||||
# Cover Position
|
||||
elif instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
|
||||
position = mode.split(".")[1]
|
||||
|
|
|
@ -400,6 +400,48 @@ async def test_report_fan_speed_state(hass):
|
|||
properties.assert_equal("Alexa.RangeController", "rangeValue", 3)
|
||||
|
||||
|
||||
async def test_report_fan_preset_mode(hass):
|
||||
"""Test ModeController reports fan preset_mode correctly."""
|
||||
hass.states.async_set(
|
||||
"fan.preset_mode",
|
||||
"eco",
|
||||
{
|
||||
"friendly_name": "eco enabled fan",
|
||||
"supported_features": 8,
|
||||
"preset_mode": "eco",
|
||||
"preset_modes": ["eco", "smart", "whoosh"],
|
||||
},
|
||||
)
|
||||
properties = await reported_properties(hass, "fan.preset_mode")
|
||||
properties.assert_equal("Alexa.ModeController", "mode", "preset_mode.eco")
|
||||
|
||||
hass.states.async_set(
|
||||
"fan.preset_mode",
|
||||
"smart",
|
||||
{
|
||||
"friendly_name": "smart enabled fan",
|
||||
"supported_features": 8,
|
||||
"preset_mode": "smart",
|
||||
"preset_modes": ["eco", "smart", "whoosh"],
|
||||
},
|
||||
)
|
||||
properties = await reported_properties(hass, "fan.preset_mode")
|
||||
properties.assert_equal("Alexa.ModeController", "mode", "preset_mode.smart")
|
||||
|
||||
hass.states.async_set(
|
||||
"fan.preset_mode",
|
||||
"whoosh",
|
||||
{
|
||||
"friendly_name": "whoosh enabled fan",
|
||||
"supported_features": 8,
|
||||
"preset_mode": "whoosh",
|
||||
"preset_modes": ["eco", "smart", "whoosh"],
|
||||
},
|
||||
)
|
||||
properties = await reported_properties(hass, "fan.preset_mode")
|
||||
properties.assert_equal("Alexa.ModeController", "mode", "preset_mode.whoosh")
|
||||
|
||||
|
||||
async def test_report_fan_oscillating(hass):
|
||||
"""Test ToggleController reports fan oscillating correctly."""
|
||||
hass.states.async_set(
|
||||
|
|
|
@ -846,6 +846,89 @@ async def test_fan_range_off(hass):
|
|||
)
|
||||
|
||||
|
||||
async def test_preset_mode_fan(hass, caplog):
|
||||
"""Test fan discovery.
|
||||
|
||||
This one has preset modes.
|
||||
"""
|
||||
device = (
|
||||
"fan.test_7",
|
||||
"off",
|
||||
{
|
||||
"friendly_name": "Test fan 7",
|
||||
"supported_features": 8,
|
||||
"preset_modes": ["auto", "eco", "smart", "whoosh"],
|
||||
"preset_mode": "auto",
|
||||
},
|
||||
)
|
||||
appliance = await discovery_test(device, hass)
|
||||
|
||||
assert appliance["endpointId"] == "fan#test_7"
|
||||
assert appliance["displayCategories"][0] == "FAN"
|
||||
assert appliance["friendlyName"] == "Test fan 7"
|
||||
|
||||
capabilities = assert_endpoint_capabilities(
|
||||
appliance,
|
||||
"Alexa.EndpointHealth",
|
||||
"Alexa.ModeController",
|
||||
"Alexa.PowerController",
|
||||
"Alexa",
|
||||
)
|
||||
|
||||
range_capability = get_capability(capabilities, "Alexa.ModeController")
|
||||
assert range_capability is not None
|
||||
assert range_capability["instance"] == "fan.preset_mode"
|
||||
|
||||
properties = range_capability["properties"]
|
||||
assert properties["nonControllable"] is False
|
||||
assert {"name": "mode"} in properties["supported"]
|
||||
|
||||
capability_resources = range_capability["capabilityResources"]
|
||||
assert capability_resources is not None
|
||||
assert {
|
||||
"@type": "asset",
|
||||
"value": {"assetId": "Alexa.Setting.Preset"},
|
||||
} in capability_resources["friendlyNames"]
|
||||
|
||||
configuration = range_capability["configuration"]
|
||||
assert configuration is not None
|
||||
|
||||
call, _ = await assert_request_calls_service(
|
||||
"Alexa.ModeController",
|
||||
"SetMode",
|
||||
"fan#test_7",
|
||||
"fan.set_preset_mode",
|
||||
hass,
|
||||
payload={"mode": "preset_mode.eco"},
|
||||
instance="fan.preset_mode",
|
||||
)
|
||||
assert call.data["preset_mode"] == "eco"
|
||||
|
||||
call, _ = await assert_request_calls_service(
|
||||
"Alexa.ModeController",
|
||||
"SetMode",
|
||||
"fan#test_7",
|
||||
"fan.set_preset_mode",
|
||||
hass,
|
||||
payload={"mode": "preset_mode.whoosh"},
|
||||
instance="fan.preset_mode",
|
||||
)
|
||||
assert call.data["preset_mode"] == "whoosh"
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
await assert_request_calls_service(
|
||||
"Alexa.ModeController",
|
||||
"SetMode",
|
||||
"fan#test_7",
|
||||
"fan.set_preset_mode",
|
||||
hass,
|
||||
payload={"mode": "preset_mode.invalid"},
|
||||
instance="fan.preset_mode",
|
||||
)
|
||||
assert "Entity 'fan.test_7' does not support Preset 'invalid'" in caplog.text
|
||||
caplog.clear()
|
||||
|
||||
|
||||
async def test_lock(hass):
|
||||
"""Test lock discovery."""
|
||||
device = ("lock.test", "off", {"friendly_name": "Test lock"})
|
||||
|
@ -2484,7 +2567,7 @@ async def test_alarm_control_panel_disarmed(hass):
|
|||
properties = ReportedProperties(msg["context"]["properties"])
|
||||
properties.assert_equal("Alexa.SecurityPanelController", "armState", "ARMED_AWAY")
|
||||
|
||||
call, msg = await assert_request_calls_service(
|
||||
_, msg = await assert_request_calls_service(
|
||||
"Alexa.SecurityPanelController",
|
||||
"Arm",
|
||||
"alarm_control_panel#test_1",
|
||||
|
|
|
@ -50,10 +50,13 @@ async def test_report_state_instance(hass, aioclient_mock):
|
|||
"off",
|
||||
{
|
||||
"friendly_name": "Test fan",
|
||||
"supported_features": 3,
|
||||
"speed": "off",
|
||||
"supported_features": 15,
|
||||
"speed": None,
|
||||
"speed_list": ["off", "low", "high"],
|
||||
"oscillating": False,
|
||||
"preset_mode": None,
|
||||
"preset_modes": ["auto", "smart"],
|
||||
"percentage": None,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -64,10 +67,13 @@ async def test_report_state_instance(hass, aioclient_mock):
|
|||
"on",
|
||||
{
|
||||
"friendly_name": "Test fan",
|
||||
"supported_features": 3,
|
||||
"supported_features": 15,
|
||||
"speed": "high",
|
||||
"speed_list": ["off", "low", "high"],
|
||||
"oscillating": True,
|
||||
"preset_mode": "smart",
|
||||
"preset_modes": ["auto", "smart"],
|
||||
"percentage": 90,
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -82,11 +88,33 @@ async def test_report_state_instance(hass, aioclient_mock):
|
|||
assert call_json["event"]["header"]["name"] == "ChangeReport"
|
||||
|
||||
change_reports = call_json["event"]["payload"]["change"]["properties"]
|
||||
|
||||
checks = 0
|
||||
for report in change_reports:
|
||||
if report["name"] == "toggleState":
|
||||
assert report["value"] == "ON"
|
||||
assert report["instance"] == "fan.oscillating"
|
||||
assert report["namespace"] == "Alexa.ToggleController"
|
||||
checks += 1
|
||||
if report["name"] == "mode":
|
||||
assert report["value"] == "preset_mode.smart"
|
||||
assert report["instance"] == "fan.preset_mode"
|
||||
assert report["namespace"] == "Alexa.ModeController"
|
||||
checks += 1
|
||||
if report["name"] == "percentage":
|
||||
assert report["value"] == 90
|
||||
assert report["namespace"] == "Alexa.PercentageController"
|
||||
checks += 1
|
||||
if report["name"] == "powerLevel":
|
||||
assert report["value"] == 90
|
||||
assert report["namespace"] == "Alexa.PowerLevelController"
|
||||
checks += 1
|
||||
if report["name"] == "rangeValue":
|
||||
assert report["value"] == 2
|
||||
assert report["instance"] == "fan.speed"
|
||||
assert report["namespace"] == "Alexa.RangeController"
|
||||
checks += 1
|
||||
assert checks == 5
|
||||
|
||||
assert call_json["event"]["endpoint"]["endpointId"] == "fan#test_fan"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue