mirror of
https://github.com/home-assistant/core.git
synced 2025-04-27 18:57:57 +00:00
Use backend-provided fan speed presets for Xiaomi vacuums, bum… (#32850)
* Use backend-provided fan speed presets for Xiaomi vacuums This needs input from Xiaomi vacuum owners to verify that it does not break anything. I have personally tested this on rockrobo v1 (old mapping). Related issues/PRs: home-assistant/core#32821 home-assistant/core#31268 home-assistant/core#27268 This is a WIP as it requires a new upstream release. The PR is https://github.com/rytilahti/python-miio/pull/643 * Bump version requirement for 0.5.0 * Bump requirements_test_all.txt, too * Fix linting; missing setup.cfg on local checkout caused wrong settings for black.. * Add tests for both fan speed types * Remove useless else.. * bump python-miio to 0.5.0.1 due to broken 0.5.0 packaging
This commit is contained in:
parent
cb058ff6c0
commit
e64104300f
@ -2,7 +2,7 @@
|
|||||||
"domain": "xiaomi_miio",
|
"domain": "xiaomi_miio",
|
||||||
"name": "Xiaomi miio",
|
"name": "Xiaomi miio",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
"documentation": "https://www.home-assistant.io/integrations/xiaomi_miio",
|
||||||
"requirements": ["construct==2.9.45", "python-miio==0.4.8"],
|
"requirements": ["construct==2.9.45", "python-miio==0.5.0.1"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@rytilahti", "@syssi"]
|
"codeowners": ["@rytilahti", "@syssi"]
|
||||||
}
|
}
|
||||||
|
@ -60,8 +60,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
|||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
|
||||||
FAN_SPEEDS = {"Silent": 38, "Standard": 60, "Medium": 77, "Turbo": 90, "Gentle": 105}
|
|
||||||
|
|
||||||
ATTR_CLEAN_START = "clean_start"
|
ATTR_CLEAN_START = "clean_start"
|
||||||
ATTR_CLEAN_STOP = "clean_stop"
|
ATTR_CLEAN_STOP = "clean_stop"
|
||||||
ATTR_CLEANING_TIME = "cleaning_time"
|
ATTR_CLEANING_TIME = "cleaning_time"
|
||||||
@ -246,6 +244,8 @@ class MiroboVacuum(StateVacuumDevice):
|
|||||||
self.clean_history = None
|
self.clean_history = None
|
||||||
self.dnd_state = None
|
self.dnd_state = None
|
||||||
self.last_clean = None
|
self.last_clean = None
|
||||||
|
self._fan_speeds = None
|
||||||
|
self._fan_speeds_reverse = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
@ -281,14 +281,17 @@ class MiroboVacuum(StateVacuumDevice):
|
|||||||
"""Return the fan speed of the vacuum cleaner."""
|
"""Return the fan speed of the vacuum cleaner."""
|
||||||
if self.vacuum_state is not None:
|
if self.vacuum_state is not None:
|
||||||
speed = self.vacuum_state.fanspeed
|
speed = self.vacuum_state.fanspeed
|
||||||
if speed in FAN_SPEEDS.values():
|
if speed in self._fan_speeds_reverse:
|
||||||
return [key for key, value in FAN_SPEEDS.items() if value == speed][0]
|
return self._fan_speeds_reverse[speed]
|
||||||
|
|
||||||
|
_LOGGER.debug("Unable to find reverse for %s", speed)
|
||||||
|
|
||||||
return speed
|
return speed
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_speed_list(self):
|
def fan_speed_list(self):
|
||||||
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
||||||
return list(sorted(FAN_SPEEDS.keys(), key=lambda s: FAN_SPEEDS[s]))
|
return list(self._fan_speeds)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_state_attributes(self):
|
def device_state_attributes(self):
|
||||||
@ -372,8 +375,8 @@ class MiroboVacuum(StateVacuumDevice):
|
|||||||
|
|
||||||
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
||||||
"""Set fan speed."""
|
"""Set fan speed."""
|
||||||
if fan_speed.capitalize() in FAN_SPEEDS:
|
if fan_speed in self._fan_speeds:
|
||||||
fan_speed = FAN_SPEEDS[fan_speed.capitalize()]
|
fan_speed = self._fan_speeds[fan_speed]
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
fan_speed = int(fan_speed)
|
fan_speed = int(fan_speed)
|
||||||
@ -453,6 +456,9 @@ class MiroboVacuum(StateVacuumDevice):
|
|||||||
state = self._vacuum.status()
|
state = self._vacuum.status()
|
||||||
self.vacuum_state = state
|
self.vacuum_state = state
|
||||||
|
|
||||||
|
self._fan_speeds = self._vacuum.fan_speed_presets()
|
||||||
|
self._fan_speeds_reverse = {v: k for k, v in self._fan_speeds.items()}
|
||||||
|
|
||||||
self.consumable_state = self._vacuum.consumable_status()
|
self.consumable_state = self._vacuum.consumable_status()
|
||||||
self.clean_history = self._vacuum.clean_history()
|
self.clean_history = self._vacuum.clean_history()
|
||||||
self.last_clean = self._vacuum.last_clean_details()
|
self.last_clean = self._vacuum.last_clean_details()
|
||||||
|
@ -1638,7 +1638,7 @@ python-juicenet==0.1.6
|
|||||||
# python-lirc==1.2.3
|
# python-lirc==1.2.3
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.4.8
|
python-miio==0.5.0.1
|
||||||
|
|
||||||
# homeassistant.components.mpd
|
# homeassistant.components.mpd
|
||||||
python-mpd2==1.0.0
|
python-mpd2==1.0.0
|
||||||
|
@ -620,7 +620,7 @@ python-forecastio==1.4.0
|
|||||||
python-izone==1.1.2
|
python-izone==1.1.2
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_miio
|
# homeassistant.components.xiaomi_miio
|
||||||
python-miio==0.4.8
|
python-miio==0.5.0.1
|
||||||
|
|
||||||
# homeassistant.components.nest
|
# homeassistant.components.nest
|
||||||
python-nest==4.1.0
|
python-nest==4.1.0
|
||||||
|
@ -100,6 +100,36 @@ def mirobo_is_got_error_fixture():
|
|||||||
yield mock_vacuum
|
yield mock_vacuum
|
||||||
|
|
||||||
|
|
||||||
|
old_fanspeeds = {
|
||||||
|
"Silent": 38,
|
||||||
|
"Standard": 60,
|
||||||
|
"Medium": 77,
|
||||||
|
"Turbo": 90,
|
||||||
|
}
|
||||||
|
new_fanspeeds = {
|
||||||
|
"Silent": 101,
|
||||||
|
"Standard": 102,
|
||||||
|
"Medium": 103,
|
||||||
|
"Turbo": 104,
|
||||||
|
"Gentle": 105,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_mirobo_fanspeeds", params=[old_fanspeeds, new_fanspeeds])
|
||||||
|
def mirobo_old_speeds_fixture(request):
|
||||||
|
"""Fixture for testing both types of fanspeeds."""
|
||||||
|
mock_vacuum = mock.MagicMock()
|
||||||
|
mock_vacuum.status().battery = 32
|
||||||
|
mock_vacuum.fan_speed_presets.return_value = request.param
|
||||||
|
mock_vacuum.status().fanspeed = list(request.param.values())[0]
|
||||||
|
|
||||||
|
with mock.patch(
|
||||||
|
"homeassistant.components.xiaomi_miio.vacuum.Vacuum"
|
||||||
|
) as mock_vaccum_cls:
|
||||||
|
mock_vaccum_cls.return_value = mock_vacuum
|
||||||
|
yield mock_vacuum
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_mirobo_is_on")
|
@pytest.fixture(name="mock_mirobo_is_on")
|
||||||
def mirobo_is_on_fixture():
|
def mirobo_is_on_fixture():
|
||||||
"""Mock mock_mirobo."""
|
"""Mock mock_mirobo."""
|
||||||
@ -204,14 +234,6 @@ async def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error):
|
|||||||
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-80"
|
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-80"
|
||||||
assert state.attributes.get(ATTR_CLEANING_TIME) == 155
|
assert state.attributes.get(ATTR_CLEANING_TIME) == 155
|
||||||
assert state.attributes.get(ATTR_CLEANED_AREA) == 123
|
assert state.attributes.get(ATTR_CLEANED_AREA) == 123
|
||||||
assert state.attributes.get(ATTR_FAN_SPEED) == "Silent"
|
|
||||||
assert state.attributes.get(ATTR_FAN_SPEED_LIST) == [
|
|
||||||
"Silent",
|
|
||||||
"Standard",
|
|
||||||
"Medium",
|
|
||||||
"Turbo",
|
|
||||||
"Gentle",
|
|
||||||
]
|
|
||||||
assert state.attributes.get(ATTR_MAIN_BRUSH_LEFT) == 12
|
assert state.attributes.get(ATTR_MAIN_BRUSH_LEFT) == 12
|
||||||
assert state.attributes.get(ATTR_SIDE_BRUSH_LEFT) == 12
|
assert state.attributes.get(ATTR_SIDE_BRUSH_LEFT) == 12
|
||||||
assert state.attributes.get(ATTR_FILTER_LEFT) == 12
|
assert state.attributes.get(ATTR_FILTER_LEFT) == 12
|
||||||
@ -257,40 +279,6 @@ async def test_xiaomi_vacuum_services(hass, caplog, mock_mirobo_is_got_error):
|
|||||||
mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True)
|
mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True)
|
||||||
mock_mirobo_is_got_error.reset_mock()
|
mock_mirobo_is_got_error.reset_mock()
|
||||||
|
|
||||||
# Set speed service:
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_SET_FAN_SPEED,
|
|
||||||
{"entity_id": entity_id, "fan_speed": 60},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mock_mirobo_is_got_error.assert_has_calls(
|
|
||||||
[mock.call.set_fan_speed(60)], any_order=True
|
|
||||||
)
|
|
||||||
mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True)
|
|
||||||
mock_mirobo_is_got_error.reset_mock()
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_SET_FAN_SPEED,
|
|
||||||
{"entity_id": entity_id, "fan_speed": "Medium"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
mock_mirobo_is_got_error.assert_has_calls(
|
|
||||||
[mock.call.set_fan_speed(77)], any_order=True
|
|
||||||
)
|
|
||||||
mock_mirobo_is_got_error.assert_has_calls(STATUS_CALLS, any_order=True)
|
|
||||||
mock_mirobo_is_got_error.reset_mock()
|
|
||||||
|
|
||||||
assert "ERROR" not in caplog.text
|
|
||||||
await hass.services.async_call(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_SET_FAN_SPEED,
|
|
||||||
{"entity_id": entity_id, "fan_speed": "invent"},
|
|
||||||
blocking=True,
|
|
||||||
)
|
|
||||||
assert "ERROR" in caplog.text
|
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_SEND_COMMAND,
|
SERVICE_SEND_COMMAND,
|
||||||
@ -346,14 +334,6 @@ async def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on):
|
|||||||
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-30"
|
assert state.attributes.get(ATTR_BATTERY_ICON) == "mdi:battery-30"
|
||||||
assert state.attributes.get(ATTR_CLEANING_TIME) == 175
|
assert state.attributes.get(ATTR_CLEANING_TIME) == 175
|
||||||
assert state.attributes.get(ATTR_CLEANED_AREA) == 133
|
assert state.attributes.get(ATTR_CLEANED_AREA) == 133
|
||||||
assert state.attributes.get(ATTR_FAN_SPEED) == 99
|
|
||||||
assert state.attributes.get(ATTR_FAN_SPEED_LIST) == [
|
|
||||||
"Silent",
|
|
||||||
"Standard",
|
|
||||||
"Medium",
|
|
||||||
"Turbo",
|
|
||||||
"Gentle",
|
|
||||||
]
|
|
||||||
assert state.attributes.get(ATTR_MAIN_BRUSH_LEFT) == 11
|
assert state.attributes.get(ATTR_MAIN_BRUSH_LEFT) == 11
|
||||||
assert state.attributes.get(ATTR_SIDE_BRUSH_LEFT) == 11
|
assert state.attributes.get(ATTR_SIDE_BRUSH_LEFT) == 11
|
||||||
assert state.attributes.get(ATTR_FILTER_LEFT) == 11
|
assert state.attributes.get(ATTR_FILTER_LEFT) == 11
|
||||||
@ -409,3 +389,67 @@ async def test_xiaomi_specific_services(hass, caplog, mock_mirobo_is_on):
|
|||||||
)
|
)
|
||||||
mock_mirobo_is_on.assert_has_calls(STATUS_CALLS, any_order=True)
|
mock_mirobo_is_on.assert_has_calls(STATUS_CALLS, any_order=True)
|
||||||
mock_mirobo_is_on.reset_mock()
|
mock_mirobo_is_on.reset_mock()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_xiaomi_vacuum_fanspeeds(hass, caplog, mock_mirobo_fanspeeds):
|
||||||
|
"""Test Xiaomi vacuum fanspeeds."""
|
||||||
|
entity_name = "test_vacuum_cleaner_2"
|
||||||
|
entity_id = f"{DOMAIN}.{entity_name}"
|
||||||
|
|
||||||
|
await async_setup_component(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
{
|
||||||
|
DOMAIN: {
|
||||||
|
CONF_PLATFORM: PLATFORM,
|
||||||
|
CONF_HOST: "192.168.1.100",
|
||||||
|
CONF_NAME: entity_name,
|
||||||
|
CONF_TOKEN: "12345678901234567890123456789012",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert "Initializing with host 192.168.1.100 (token 12345" in caplog.text
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state.attributes.get(ATTR_FAN_SPEED) == "Silent"
|
||||||
|
fanspeeds = state.attributes.get(ATTR_FAN_SPEED_LIST)
|
||||||
|
for speed in ["Silent", "Standard", "Medium", "Turbo"]:
|
||||||
|
assert speed in fanspeeds
|
||||||
|
|
||||||
|
# Set speed service:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_FAN_SPEED,
|
||||||
|
{"entity_id": entity_id, "fan_speed": 60},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_mirobo_fanspeeds.assert_has_calls(
|
||||||
|
[mock.call.set_fan_speed(60)], any_order=True
|
||||||
|
)
|
||||||
|
mock_mirobo_fanspeeds.assert_has_calls(STATUS_CALLS, any_order=True)
|
||||||
|
mock_mirobo_fanspeeds.reset_mock()
|
||||||
|
|
||||||
|
fan_speed_dict = mock_mirobo_fanspeeds.fan_speed_presets()
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_FAN_SPEED,
|
||||||
|
{"entity_id": entity_id, "fan_speed": "Medium"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
mock_mirobo_fanspeeds.assert_has_calls(
|
||||||
|
[mock.call.set_fan_speed(fan_speed_dict["Medium"])], any_order=True
|
||||||
|
)
|
||||||
|
mock_mirobo_fanspeeds.assert_has_calls(STATUS_CALLS, any_order=True)
|
||||||
|
mock_mirobo_fanspeeds.reset_mock()
|
||||||
|
|
||||||
|
assert "ERROR" not in caplog.text
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_FAN_SPEED,
|
||||||
|
{"entity_id": entity_id, "fan_speed": "invent"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert "ERROR" in caplog.text
|
||||||
|
Loading…
x
Reference in New Issue
Block a user