Add Braava support to iRobot Roomba component (#33616)
* Add Braava support to iRobot Roomba component * Replace async_add_job with async_add_executor_job in roomba * Improve readability in roomba * Improve error handling in roomba * Cleanup async_update in roomba * Split into multiple files in roomba * Hide protocal details in braava * Switch to push in braava * Bump roombapy version to 1.5.1 * Add roomba files to .coveragerc * Fix typo * Remove side effects from init in roomba * Implement StateVacuumDevice in Roomba * Add IRobotEntity base class to braava * Fix state in roomba * Add @shenxn as a codeowner of braava
This commit is contained in:
parent
b3eba49a2f
commit
61a5793073
12 changed files with 520 additions and 374 deletions
|
@ -597,6 +597,11 @@ omit =
|
||||||
homeassistant/components/ripple/sensor.py
|
homeassistant/components/ripple/sensor.py
|
||||||
homeassistant/components/rocketchat/notify.py
|
homeassistant/components/rocketchat/notify.py
|
||||||
homeassistant/components/roku/remote.py
|
homeassistant/components/roku/remote.py
|
||||||
|
homeassistant/components/roomba/binary_sensor.py
|
||||||
|
homeassistant/components/roomba/braava.py
|
||||||
|
homeassistant/components/roomba/irobot_base.py
|
||||||
|
homeassistant/components/roomba/roomba.py
|
||||||
|
homeassistant/components/roomba/sensor.py
|
||||||
homeassistant/components/roomba/vacuum.py
|
homeassistant/components/roomba/vacuum.py
|
||||||
homeassistant/components/route53/*
|
homeassistant/components/route53/*
|
||||||
homeassistant/components/rova/sensor.py
|
homeassistant/components/rova/sensor.py
|
||||||
|
|
|
@ -317,7 +317,7 @@ homeassistant/components/rfxtrx/* @danielhiversen
|
||||||
homeassistant/components/ring/* @balloob
|
homeassistant/components/ring/* @balloob
|
||||||
homeassistant/components/rmvtransport/* @cgtobi
|
homeassistant/components/rmvtransport/* @cgtobi
|
||||||
homeassistant/components/roku/* @ctalkington
|
homeassistant/components/roku/* @ctalkington
|
||||||
homeassistant/components/roomba/* @pschmitt @cyr-ius
|
homeassistant/components/roomba/* @pschmitt @cyr-ius @shenxn
|
||||||
homeassistant/components/safe_mode/* @home-assistant/core
|
homeassistant/components/safe_mode/* @home-assistant/core
|
||||||
homeassistant/components/saj/* @fredericvl
|
homeassistant/components/saj/* @fredericvl
|
||||||
homeassistant/components/salt/* @bjornorri
|
homeassistant/components/salt/* @bjornorri
|
||||||
|
|
|
@ -96,6 +96,7 @@ async def async_setup_entry(hass, config_entry):
|
||||||
continuous=config_entry.options[CONF_CONTINUOUS],
|
continuous=config_entry.options[CONF_CONTINUOUS],
|
||||||
delay=config_entry.options[CONF_DELAY],
|
delay=config_entry.options[CONF_DELAY],
|
||||||
)
|
)
|
||||||
|
roomba.exclude = "wifistat" # ignore wifistat to avoid unnecessary updates
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not await async_connect_or_timeout(hass, roomba):
|
if not await async_connect_or_timeout(hass, roomba):
|
||||||
|
|
|
@ -5,6 +5,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||||
|
|
||||||
from . import roomba_reported_state
|
from . import roomba_reported_state
|
||||||
from .const import BLID, DOMAIN, ROOMBA_SESSION
|
from .const import BLID, DOMAIN, ROOMBA_SESSION
|
||||||
|
from .irobot_base import IRobotEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -17,23 +18,15 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
status = roomba_reported_state(roomba).get("bin", {})
|
status = roomba_reported_state(roomba).get("bin", {})
|
||||||
if "full" in status:
|
if "full" in status:
|
||||||
roomba_vac = RoombaBinStatus(roomba, blid)
|
roomba_vac = RoombaBinStatus(roomba, blid)
|
||||||
|
roomba_vac.register_callback()
|
||||||
async_add_entities([roomba_vac], True)
|
async_add_entities([roomba_vac], True)
|
||||||
|
|
||||||
|
|
||||||
class RoombaBinStatus(BinarySensorDevice):
|
class RoombaBinStatus(IRobotEntity, BinarySensorDevice):
|
||||||
"""Class to hold Roomba Sensor basic info."""
|
"""Class to hold Roomba Sensor basic info."""
|
||||||
|
|
||||||
ICON = "mdi:delete-variant"
|
ICON = "mdi:delete-variant"
|
||||||
|
|
||||||
def __init__(self, roomba, blid):
|
|
||||||
"""Initialize the sensor object."""
|
|
||||||
self.vacuum = roomba
|
|
||||||
self.vacuum_state = roomba_reported_state(roomba)
|
|
||||||
self._blid = blid
|
|
||||||
self._name = self.vacuum_state.get("name")
|
|
||||||
self._identifier = f"roomba_{self._blid}"
|
|
||||||
self._bin_status = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
|
@ -52,23 +45,8 @@ class RoombaBinStatus(BinarySensorDevice):
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self._bin_status
|
bin_status = (
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self):
|
|
||||||
"""Return the device info of the vacuum cleaner."""
|
|
||||||
return {
|
|
||||||
"identifiers": {(DOMAIN, self._identifier)},
|
|
||||||
"name": str(self._name),
|
|
||||||
}
|
|
||||||
|
|
||||||
async def async_update(self):
|
|
||||||
"""Return the update info of the vacuum cleaner."""
|
|
||||||
# No data, no update
|
|
||||||
if not self.vacuum.master_state:
|
|
||||||
_LOGGER.debug("Roomba %s has no data yet. Skip update", self.name)
|
|
||||||
return
|
|
||||||
self._bin_status = (
|
|
||||||
roomba_reported_state(self.vacuum).get("bin", {}).get("full", False)
|
roomba_reported_state(self.vacuum).get("bin", {}).get("full", False)
|
||||||
)
|
)
|
||||||
_LOGGER.debug("Update Full Bin status from the vacuum: %s", self._bin_status)
|
_LOGGER.debug("Update Full Bin status from the vacuum: %s", bin_status)
|
||||||
|
return bin_status
|
||||||
|
|
135
homeassistant/components/roomba/braava.py
Normal file
135
homeassistant/components/roomba/braava.py
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
"""Class for Braava devices."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.vacuum import SUPPORT_FAN_SPEED
|
||||||
|
|
||||||
|
from .irobot_base import SUPPORT_IROBOT, IRobotVacuum
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_DETECTED_PAD = "detected_pad"
|
||||||
|
ATTR_LID_CLOSED = "lid_closed"
|
||||||
|
ATTR_TANK_PRESENT = "tank_present"
|
||||||
|
ATTR_TANK_LEVEL = "tank_level"
|
||||||
|
ATTR_PAD_WETNESS = "spray_amount"
|
||||||
|
|
||||||
|
OVERLAP_STANDARD = 67
|
||||||
|
OVERLAP_DEEP = 85
|
||||||
|
OVERLAP_EXTENDED = 25
|
||||||
|
MOP_STANDARD = "Standard"
|
||||||
|
MOP_DEEP = "Deep"
|
||||||
|
MOP_EXTENDED = "Extended"
|
||||||
|
BRAAVA_MOP_BEHAVIORS = [MOP_STANDARD, MOP_DEEP, MOP_EXTENDED]
|
||||||
|
BRAAVA_SPRAY_AMOUNT = [1, 2, 3]
|
||||||
|
|
||||||
|
# Braava Jets can set mopping behavior through fanspeed
|
||||||
|
SUPPORT_BRAAVA = SUPPORT_IROBOT | SUPPORT_FAN_SPEED
|
||||||
|
|
||||||
|
|
||||||
|
class BraavaJet(IRobotVacuum):
|
||||||
|
"""Braava Jet."""
|
||||||
|
|
||||||
|
def __init__(self, roomba, blid):
|
||||||
|
"""Initialize the Roomba handler."""
|
||||||
|
super().__init__(roomba, blid)
|
||||||
|
|
||||||
|
# Initialize fan speed list
|
||||||
|
speed_list = []
|
||||||
|
for behavior in BRAAVA_MOP_BEHAVIORS:
|
||||||
|
for spray in BRAAVA_SPRAY_AMOUNT:
|
||||||
|
speed_list.append(f"{behavior}-{spray}")
|
||||||
|
self._speed_list = speed_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Flag vacuum cleaner robot features that are supported."""
|
||||||
|
return SUPPORT_BRAAVA
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_speed(self):
|
||||||
|
"""Return the fan speed of the vacuum cleaner."""
|
||||||
|
# Mopping behavior and spray amount as fan speed
|
||||||
|
rank_overlap = self.vacuum_state.get("rankOverlap", {})
|
||||||
|
behavior = None
|
||||||
|
if rank_overlap == OVERLAP_STANDARD:
|
||||||
|
behavior = MOP_STANDARD
|
||||||
|
elif rank_overlap == OVERLAP_DEEP:
|
||||||
|
behavior = MOP_DEEP
|
||||||
|
elif rank_overlap == OVERLAP_EXTENDED:
|
||||||
|
behavior = MOP_EXTENDED
|
||||||
|
pad_wetness = self.vacuum_state.get("padWetness", {})
|
||||||
|
# "disposable" and "reusable" values are always the same
|
||||||
|
pad_wetness_value = pad_wetness.get("disposable")
|
||||||
|
return f"{behavior}-{pad_wetness_value}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_speed_list(self):
|
||||||
|
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
||||||
|
return self._speed_list
|
||||||
|
|
||||||
|
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
||||||
|
"""Set fan speed."""
|
||||||
|
try:
|
||||||
|
split = fan_speed.split("-", 1)
|
||||||
|
behavior = split[0]
|
||||||
|
spray = int(split[1])
|
||||||
|
if behavior.capitalize() in BRAAVA_MOP_BEHAVIORS:
|
||||||
|
behavior = behavior.capitalize()
|
||||||
|
except IndexError:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Fan speed error: expected {behavior}-{spray_amount}, got '%s'",
|
||||||
|
fan_speed,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.error("Spray amount error: expected integer, got '%s'", split[1])
|
||||||
|
return
|
||||||
|
if behavior not in BRAAVA_MOP_BEHAVIORS:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Mop behavior error: expected one of %s, got '%s'",
|
||||||
|
str(BRAAVA_MOP_BEHAVIORS),
|
||||||
|
behavior,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if spray not in BRAAVA_SPRAY_AMOUNT:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Spray amount error: expected one of %s, got '%d'",
|
||||||
|
str(BRAAVA_SPRAY_AMOUNT),
|
||||||
|
spray,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
overlap = 0
|
||||||
|
if behavior == MOP_STANDARD:
|
||||||
|
overlap = OVERLAP_STANDARD
|
||||||
|
elif behavior == MOP_DEEP:
|
||||||
|
overlap = OVERLAP_DEEP
|
||||||
|
else:
|
||||||
|
overlap = OVERLAP_EXTENDED
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.vacuum.set_preference, "rankOverlap", overlap
|
||||||
|
)
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.vacuum.set_preference,
|
||||||
|
"padWetness",
|
||||||
|
{"disposable": spray, "reusable": spray},
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes of the device."""
|
||||||
|
state_attrs = super().device_state_attributes
|
||||||
|
|
||||||
|
# Get Braava state
|
||||||
|
state = self.vacuum_state
|
||||||
|
detected_pad = state.get("detectedPad")
|
||||||
|
mop_ready = state.get("mopReady", {})
|
||||||
|
lid_closed = mop_ready.get("lidClosed")
|
||||||
|
tank_present = mop_ready.get("tankPresent")
|
||||||
|
tank_level = state.get("tankLvl")
|
||||||
|
state_attrs[ATTR_DETECTED_PAD] = detected_pad
|
||||||
|
state_attrs[ATTR_LID_CLOSED] = lid_closed
|
||||||
|
state_attrs[ATTR_TANK_PRESENT] = tank_present
|
||||||
|
state_attrs[ATTR_TANK_LEVEL] = tank_level
|
||||||
|
|
||||||
|
return state_attrs
|
250
homeassistant/components/roomba/irobot_base.py
Normal file
250
homeassistant/components/roomba/irobot_base.py
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
"""Base class for iRobot devices."""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.vacuum import (
|
||||||
|
STATE_CLEANING,
|
||||||
|
STATE_DOCKED,
|
||||||
|
STATE_ERROR,
|
||||||
|
STATE_IDLE,
|
||||||
|
STATE_PAUSED,
|
||||||
|
STATE_RETURNING,
|
||||||
|
SUPPORT_BATTERY,
|
||||||
|
SUPPORT_LOCATE,
|
||||||
|
SUPPORT_PAUSE,
|
||||||
|
SUPPORT_RETURN_HOME,
|
||||||
|
SUPPORT_SEND_COMMAND,
|
||||||
|
SUPPORT_START,
|
||||||
|
SUPPORT_STATE,
|
||||||
|
SUPPORT_STOP,
|
||||||
|
StateVacuumDevice,
|
||||||
|
)
|
||||||
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from . import roomba_reported_state
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_CLEANING_TIME = "cleaning_time"
|
||||||
|
ATTR_CLEANED_AREA = "cleaned_area"
|
||||||
|
ATTR_ERROR = "error"
|
||||||
|
ATTR_POSITION = "position"
|
||||||
|
ATTR_SOFTWARE_VERSION = "software_version"
|
||||||
|
|
||||||
|
# Commonly supported features
|
||||||
|
SUPPORT_IROBOT = (
|
||||||
|
SUPPORT_BATTERY
|
||||||
|
| SUPPORT_PAUSE
|
||||||
|
| SUPPORT_RETURN_HOME
|
||||||
|
| SUPPORT_SEND_COMMAND
|
||||||
|
| SUPPORT_START
|
||||||
|
| SUPPORT_STATE
|
||||||
|
| SUPPORT_STOP
|
||||||
|
| SUPPORT_LOCATE
|
||||||
|
)
|
||||||
|
|
||||||
|
STATE_MAP = {
|
||||||
|
"": STATE_IDLE,
|
||||||
|
"charge": STATE_DOCKED,
|
||||||
|
"hmMidMsn": STATE_CLEANING, # Recharging at the middle of a cycle
|
||||||
|
"hmPostMsn": STATE_RETURNING, # Cycle finished
|
||||||
|
"hmUsrDock": STATE_RETURNING,
|
||||||
|
"pause": STATE_PAUSED,
|
||||||
|
"run": STATE_CLEANING,
|
||||||
|
"stop": STATE_IDLE,
|
||||||
|
"stuck": STATE_ERROR,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IRobotEntity(Entity):
|
||||||
|
"""Base class for iRobot Entities."""
|
||||||
|
|
||||||
|
def __init__(self, roomba, blid):
|
||||||
|
"""Initialize the iRobot handler."""
|
||||||
|
self.vacuum = roomba
|
||||||
|
self._blid = blid
|
||||||
|
vacuum_state = roomba_reported_state(roomba)
|
||||||
|
self._name = vacuum_state.get("name")
|
||||||
|
self._version = vacuum_state.get("softwareVer")
|
||||||
|
self._sku = vacuum_state.get("sku")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Disable polling."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def robot_unique_id(self):
|
||||||
|
"""Return the uniqueid of the vacuum cleaner."""
|
||||||
|
return f"roomba_{self._blid}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self):
|
||||||
|
"""Return the uniqueid of the vacuum cleaner."""
|
||||||
|
return self.robot_unique_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_info(self):
|
||||||
|
"""Return the device info of the vacuum cleaner."""
|
||||||
|
return {
|
||||||
|
"identifiers": {(DOMAIN, self.robot_unique_id)},
|
||||||
|
"manufacturer": "iRobot",
|
||||||
|
"name": str(self._name),
|
||||||
|
"sw_version": self._version,
|
||||||
|
"model": self._sku,
|
||||||
|
}
|
||||||
|
|
||||||
|
def register_callback(self):
|
||||||
|
"""Register callback function."""
|
||||||
|
self.vacuum.register_on_message_callback(self.on_message)
|
||||||
|
|
||||||
|
def on_message(self, json_data):
|
||||||
|
"""Update state on message change."""
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
|
||||||
|
class IRobotVacuum(IRobotEntity, StateVacuumDevice):
|
||||||
|
"""Base class for iRobot robots."""
|
||||||
|
|
||||||
|
def __init__(self, roomba, blid):
|
||||||
|
"""Initialize the iRobot handler."""
|
||||||
|
super().__init__(roomba, blid)
|
||||||
|
self.vacuum_state = roomba_reported_state(roomba)
|
||||||
|
self._cap_position = self.vacuum_state.get("cap", {}).get("pose") == 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Flag vacuum cleaner robot features that are supported."""
|
||||||
|
return SUPPORT_IROBOT
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_speed(self):
|
||||||
|
"""Return the fan speed of the vacuum cleaner."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_speed_list(self):
|
||||||
|
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
||||||
|
return []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def battery_level(self):
|
||||||
|
"""Return the battery level of the vacuum cleaner."""
|
||||||
|
return self.vacuum_state.get("batPct")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
"""Return the state of the vacuum cleaner."""
|
||||||
|
clean_mission_status = self.vacuum_state.get("cleanMissionStatus", {})
|
||||||
|
cycle = clean_mission_status.get("cycle")
|
||||||
|
phase = clean_mission_status.get("phase")
|
||||||
|
try:
|
||||||
|
state = STATE_MAP[phase]
|
||||||
|
except KeyError:
|
||||||
|
return STATE_ERROR
|
||||||
|
if cycle != "none" and state != STATE_CLEANING and state != STATE_RETURNING:
|
||||||
|
state = STATE_PAUSED
|
||||||
|
return state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return True # Always available, otherwise setup will fail
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of the device."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes of the device."""
|
||||||
|
state = self.vacuum_state
|
||||||
|
|
||||||
|
# Roomba software version
|
||||||
|
software_version = state.get("softwareVer")
|
||||||
|
|
||||||
|
# Error message in plain english
|
||||||
|
error_msg = "None"
|
||||||
|
if hasattr(self.vacuum, "error_message"):
|
||||||
|
error_msg = self.vacuum.error_message
|
||||||
|
|
||||||
|
# Set properties that are to appear in the GUI
|
||||||
|
state_attrs = {ATTR_SOFTWARE_VERSION: software_version}
|
||||||
|
|
||||||
|
# Only add cleaning time and cleaned area attrs when the vacuum is
|
||||||
|
# currently on
|
||||||
|
if self.state == STATE_CLEANING:
|
||||||
|
# Get clean mission status
|
||||||
|
mission_state = state.get("cleanMissionStatus", {})
|
||||||
|
cleaning_time = mission_state.get("mssnM")
|
||||||
|
cleaned_area = mission_state.get("sqft") # Imperial
|
||||||
|
# Convert to m2 if the unit_system is set to metric
|
||||||
|
if cleaned_area and self.hass.config.units.is_metric:
|
||||||
|
cleaned_area = round(cleaned_area * 0.0929)
|
||||||
|
state_attrs[ATTR_CLEANING_TIME] = cleaning_time
|
||||||
|
state_attrs[ATTR_CLEANED_AREA] = cleaned_area
|
||||||
|
|
||||||
|
# Skip error attr if there is none
|
||||||
|
if error_msg and error_msg != "None":
|
||||||
|
state_attrs[ATTR_ERROR] = error_msg
|
||||||
|
|
||||||
|
# Not all Roombas expose position data
|
||||||
|
# https://github.com/koalazak/dorita980/issues/48
|
||||||
|
if self._cap_position:
|
||||||
|
pos_state = state.get("pose", {})
|
||||||
|
position = None
|
||||||
|
pos_x = pos_state.get("point", {}).get("x")
|
||||||
|
pos_y = pos_state.get("point", {}).get("y")
|
||||||
|
theta = pos_state.get("theta")
|
||||||
|
if all(item is not None for item in [pos_x, pos_y, theta]):
|
||||||
|
position = f"({pos_x}, {pos_y}, {theta})"
|
||||||
|
state_attrs[ATTR_POSITION] = position
|
||||||
|
|
||||||
|
return state_attrs
|
||||||
|
|
||||||
|
def on_message(self, json_data):
|
||||||
|
"""Update state on message change."""
|
||||||
|
_LOGGER.debug("Got new state from the vacuum: %s", json_data)
|
||||||
|
self.vacuum_state = self.vacuum.master_state.get("state", {}).get(
|
||||||
|
"reported", {}
|
||||||
|
)
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
async def async_start(self):
|
||||||
|
"""Start or resume the cleaning task."""
|
||||||
|
if self.state == STATE_PAUSED:
|
||||||
|
await self.hass.async_add_executor_job(self.vacuum.send_command, "resume")
|
||||||
|
else:
|
||||||
|
await self.hass.async_add_executor_job(self.vacuum.send_command, "start")
|
||||||
|
|
||||||
|
async def async_stop(self, **kwargs):
|
||||||
|
"""Stop the vacuum cleaner."""
|
||||||
|
await self.hass.async_add_executor_job(self.vacuum.send_command, "stop")
|
||||||
|
|
||||||
|
async def async_pause(self):
|
||||||
|
"""Pause the cleaning cycle."""
|
||||||
|
await self.hass.async_add_executor_job(self.vacuum.send_command, "pause")
|
||||||
|
|
||||||
|
async def async_return_to_base(self, **kwargs):
|
||||||
|
"""Set the vacuum cleaner to return to the dock."""
|
||||||
|
if self.state == STATE_CLEANING:
|
||||||
|
await self.async_pause()
|
||||||
|
for _ in range(0, 10):
|
||||||
|
if self.state == STATE_PAUSED:
|
||||||
|
break
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
await self.hass.async_add_executor_job(self.vacuum.send_command, "dock")
|
||||||
|
|
||||||
|
async def async_locate(self, **kwargs):
|
||||||
|
"""Located vacuum."""
|
||||||
|
await self.hass.async_add_executor_job(self.vacuum.send_command, "find")
|
||||||
|
|
||||||
|
async def async_send_command(self, command, params=None, **kwargs):
|
||||||
|
"""Send raw command."""
|
||||||
|
_LOGGER.debug("async_send_command %s (%s), %s", command, params, kwargs)
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.vacuum.send_command, command, params
|
||||||
|
)
|
||||||
|
return True
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "iRobot Roomba",
|
"name": "iRobot Roomba",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
"documentation": "https://www.home-assistant.io/integrations/roomba",
|
||||||
"requirements": ["roombapy==1.5.0"],
|
"requirements": ["roombapy==1.5.1"],
|
||||||
"dependencies": [],
|
"dependencies": [],
|
||||||
"codeowners": ["@pschmitt", "@cyr-ius"]
|
"codeowners": ["@pschmitt", "@cyr-ius", "@shenxn"]
|
||||||
}
|
}
|
||||||
|
|
95
homeassistant/components/roomba/roomba.py
Normal file
95
homeassistant/components/roomba/roomba.py
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
"""Class for Roomba devices."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from homeassistant.components.vacuum import SUPPORT_FAN_SPEED
|
||||||
|
|
||||||
|
from .irobot_base import SUPPORT_IROBOT, IRobotVacuum
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ATTR_BIN_FULL = "bin_full"
|
||||||
|
ATTR_BIN_PRESENT = "bin_present"
|
||||||
|
|
||||||
|
FAN_SPEED_AUTOMATIC = "Automatic"
|
||||||
|
FAN_SPEED_ECO = "Eco"
|
||||||
|
FAN_SPEED_PERFORMANCE = "Performance"
|
||||||
|
FAN_SPEEDS = [FAN_SPEED_AUTOMATIC, FAN_SPEED_ECO, FAN_SPEED_PERFORMANCE]
|
||||||
|
|
||||||
|
# Only Roombas with CarpetBost can set their fanspeed
|
||||||
|
SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_IROBOT | SUPPORT_FAN_SPEED
|
||||||
|
|
||||||
|
|
||||||
|
class RoombaVacuum(IRobotVacuum):
|
||||||
|
"""Basic Roomba robot (without carpet boost)."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes of the device."""
|
||||||
|
state_attrs = super().device_state_attributes
|
||||||
|
|
||||||
|
# Get bin state
|
||||||
|
bin_raw_state = self.vacuum_state.get("bin", {})
|
||||||
|
bin_state = {}
|
||||||
|
if bin_raw_state.get("present") is not None:
|
||||||
|
bin_state[ATTR_BIN_PRESENT] = bin_raw_state.get("present")
|
||||||
|
if bin_raw_state.get("full") is not None:
|
||||||
|
bin_state[ATTR_BIN_FULL] = bin_raw_state.get("full")
|
||||||
|
state_attrs.update(bin_state)
|
||||||
|
|
||||||
|
return state_attrs
|
||||||
|
|
||||||
|
|
||||||
|
class RoombaVacuumCarpetBoost(RoombaVacuum):
|
||||||
|
"""Roomba robot with carpet boost."""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self):
|
||||||
|
"""Flag vacuum cleaner robot features that are supported."""
|
||||||
|
return SUPPORT_ROOMBA_CARPET_BOOST
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_speed(self):
|
||||||
|
"""Return the fan speed of the vacuum cleaner."""
|
||||||
|
fan_speed = None
|
||||||
|
carpet_boost = self.vacuum_state.get("carpetBoost")
|
||||||
|
high_perf = self.vacuum_state.get("vacHigh")
|
||||||
|
if carpet_boost is not None and high_perf is not None:
|
||||||
|
if carpet_boost:
|
||||||
|
fan_speed = FAN_SPEED_AUTOMATIC
|
||||||
|
elif high_perf:
|
||||||
|
fan_speed = FAN_SPEED_PERFORMANCE
|
||||||
|
else: # carpet_boost and high_perf are False
|
||||||
|
fan_speed = FAN_SPEED_ECO
|
||||||
|
return fan_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fan_speed_list(self):
|
||||||
|
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
||||||
|
return FAN_SPEEDS
|
||||||
|
|
||||||
|
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
||||||
|
"""Set fan speed."""
|
||||||
|
if fan_speed.capitalize() in FAN_SPEEDS:
|
||||||
|
fan_speed = fan_speed.capitalize()
|
||||||
|
_LOGGER.debug("Set fan speed to: %s", fan_speed)
|
||||||
|
high_perf = None
|
||||||
|
carpet_boost = None
|
||||||
|
if fan_speed == FAN_SPEED_AUTOMATIC:
|
||||||
|
high_perf = False
|
||||||
|
carpet_boost = True
|
||||||
|
elif fan_speed == FAN_SPEED_ECO:
|
||||||
|
high_perf = False
|
||||||
|
carpet_boost = False
|
||||||
|
elif fan_speed == FAN_SPEED_PERFORMANCE:
|
||||||
|
high_perf = True
|
||||||
|
carpet_boost = False
|
||||||
|
else:
|
||||||
|
_LOGGER.error("No such fan speed available: %s", fan_speed)
|
||||||
|
return
|
||||||
|
# The set_preference method does only accept string values
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.vacuum.set_preference, "carpetBoost", str(carpet_boost)
|
||||||
|
)
|
||||||
|
await self.hass.async_add_executor_job(
|
||||||
|
self.vacuum.set_preference, "vacHigh", str(high_perf)
|
||||||
|
)
|
|
@ -2,10 +2,10 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE
|
from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE
|
||||||
from homeassistant.helpers.entity import Entity
|
|
||||||
|
|
||||||
from . import roomba_reported_state
|
from . import roomba_reported_state
|
||||||
from .const import BLID, DOMAIN, ROOMBA_SESSION
|
from .const import BLID, DOMAIN, ROOMBA_SESSION
|
||||||
|
from .irobot_base import IRobotEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,21 +16,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
roomba = domain_data[ROOMBA_SESSION]
|
roomba = domain_data[ROOMBA_SESSION]
|
||||||
blid = domain_data[BLID]
|
blid = domain_data[BLID]
|
||||||
roomba_vac = RoombaBattery(roomba, blid)
|
roomba_vac = RoombaBattery(roomba, blid)
|
||||||
|
roomba_vac.register_callback()
|
||||||
async_add_entities([roomba_vac], True)
|
async_add_entities([roomba_vac], True)
|
||||||
|
|
||||||
|
|
||||||
class RoombaBattery(Entity):
|
class RoombaBattery(IRobotEntity):
|
||||||
"""Class to hold Roomba Sensor basic info."""
|
"""Class to hold Roomba Sensor basic info."""
|
||||||
|
|
||||||
def __init__(self, roomba, blid):
|
|
||||||
"""Initialize the sensor object."""
|
|
||||||
self.vacuum = roomba
|
|
||||||
self.vacuum_state = roomba_reported_state(roomba)
|
|
||||||
self._blid = blid
|
|
||||||
self._name = self.vacuum_state.get("name")
|
|
||||||
self._identifier = f"roomba_{self._blid}"
|
|
||||||
self._battery_level = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the sensor."""
|
"""Return the name of the sensor."""
|
||||||
|
@ -54,23 +46,6 @@ class RoombaBattery(Entity):
|
||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self._battery_level
|
battery_level = roomba_reported_state(self.vacuum).get("batPct")
|
||||||
|
_LOGGER.debug("Update battery level status from the vacuum: %s", battery_level)
|
||||||
@property
|
return battery_level
|
||||||
def device_info(self):
|
|
||||||
"""Return the device info of the vacuum cleaner."""
|
|
||||||
return {
|
|
||||||
"identifiers": {(DOMAIN, self._identifier)},
|
|
||||||
"name": str(self._name),
|
|
||||||
}
|
|
||||||
|
|
||||||
async def async_update(self):
|
|
||||||
"""Return the update info of the vacuum cleaner."""
|
|
||||||
# No data, no update
|
|
||||||
if not self.vacuum.master_state:
|
|
||||||
_LOGGER.debug("Roomba %s has no data yet. Skip update", self.name)
|
|
||||||
return
|
|
||||||
self._battery_level = roomba_reported_state(self.vacuum).get("batPct")
|
|
||||||
_LOGGER.debug(
|
|
||||||
"Update battery level status from the vacuum: %s", self._battery_level
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,325 +1,32 @@
|
||||||
"""Support for Wi-Fi enabled iRobot Roombas."""
|
"""Support for Wi-Fi enabled iRobot Roombas."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.vacuum import (
|
|
||||||
SUPPORT_BATTERY,
|
|
||||||
SUPPORT_FAN_SPEED,
|
|
||||||
SUPPORT_LOCATE,
|
|
||||||
SUPPORT_PAUSE,
|
|
||||||
SUPPORT_RETURN_HOME,
|
|
||||||
SUPPORT_SEND_COMMAND,
|
|
||||||
SUPPORT_STATUS,
|
|
||||||
SUPPORT_STOP,
|
|
||||||
SUPPORT_TURN_OFF,
|
|
||||||
SUPPORT_TURN_ON,
|
|
||||||
VacuumDevice,
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import roomba_reported_state
|
from . import roomba_reported_state
|
||||||
|
from .braava import BraavaJet
|
||||||
from .const import BLID, DOMAIN, ROOMBA_SESSION
|
from .const import BLID, DOMAIN, ROOMBA_SESSION
|
||||||
|
from .roomba import RoombaVacuum, RoombaVacuumCarpetBoost
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ATTR_BIN_FULL = "bin_full"
|
|
||||||
ATTR_BIN_PRESENT = "bin_present"
|
|
||||||
ATTR_CLEANING_TIME = "cleaning_time"
|
|
||||||
ATTR_CLEANED_AREA = "cleaned_area"
|
|
||||||
ATTR_ERROR = "error"
|
|
||||||
ATTR_POSITION = "position"
|
|
||||||
ATTR_SOFTWARE_VERSION = "software_version"
|
|
||||||
|
|
||||||
CAP_POSITION = "position"
|
|
||||||
CAP_CARPET_BOOST = "carpet_boost"
|
|
||||||
|
|
||||||
FAN_SPEED_AUTOMATIC = "Automatic"
|
|
||||||
FAN_SPEED_ECO = "Eco"
|
|
||||||
FAN_SPEED_PERFORMANCE = "Performance"
|
|
||||||
FAN_SPEEDS = [FAN_SPEED_AUTOMATIC, FAN_SPEED_ECO, FAN_SPEED_PERFORMANCE]
|
|
||||||
|
|
||||||
|
|
||||||
# Commonly supported features
|
|
||||||
SUPPORT_ROOMBA = (
|
|
||||||
SUPPORT_BATTERY
|
|
||||||
| SUPPORT_PAUSE
|
|
||||||
| SUPPORT_RETURN_HOME
|
|
||||||
| SUPPORT_SEND_COMMAND
|
|
||||||
| SUPPORT_STATUS
|
|
||||||
| SUPPORT_STOP
|
|
||||||
| SUPPORT_TURN_OFF
|
|
||||||
| SUPPORT_TURN_ON
|
|
||||||
| SUPPORT_LOCATE
|
|
||||||
)
|
|
||||||
|
|
||||||
# Only Roombas with CarpetBost can set their fanspeed
|
|
||||||
SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_ROOMBA | SUPPORT_FAN_SPEED
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the iRobot Roomba vacuum cleaner."""
|
"""Set up the iRobot Roomba vacuum cleaner."""
|
||||||
domain_data = hass.data[DOMAIN][config_entry.entry_id]
|
domain_data = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
roomba = domain_data[ROOMBA_SESSION]
|
roomba = domain_data[ROOMBA_SESSION]
|
||||||
blid = domain_data[BLID]
|
blid = domain_data[BLID]
|
||||||
roomba_vac = RoombaVacuum(roomba, blid)
|
|
||||||
|
# Get the capabilities of our unit
|
||||||
|
state = roomba_reported_state(roomba)
|
||||||
|
capabilities = state.get("cap", {})
|
||||||
|
cap_carpet_boost = capabilities.get("carpetBoost")
|
||||||
|
detected_pad = state.get("detectedPad")
|
||||||
|
if detected_pad is not None:
|
||||||
|
constructor = BraavaJet
|
||||||
|
elif cap_carpet_boost == 1:
|
||||||
|
constructor = RoombaVacuumCarpetBoost
|
||||||
|
else:
|
||||||
|
constructor = RoombaVacuum
|
||||||
|
|
||||||
|
roomba_vac = constructor(roomba, blid)
|
||||||
|
roomba_vac.register_callback()
|
||||||
async_add_entities([roomba_vac], True)
|
async_add_entities([roomba_vac], True)
|
||||||
|
|
||||||
|
|
||||||
class RoombaVacuum(VacuumDevice):
|
|
||||||
"""Representation of a Roomba Vacuum cleaner robot."""
|
|
||||||
|
|
||||||
def __init__(self, roomba, blid):
|
|
||||||
"""Initialize the Roomba handler."""
|
|
||||||
self._available = False
|
|
||||||
self._battery_level = None
|
|
||||||
self._capabilities = {}
|
|
||||||
self._fan_speed = None
|
|
||||||
self._is_on = False
|
|
||||||
self._state_attrs = {}
|
|
||||||
self._status = None
|
|
||||||
self.vacuum = roomba
|
|
||||||
self.vacuum_state = roomba_reported_state(roomba)
|
|
||||||
self._blid = blid
|
|
||||||
self._name = self.vacuum_state.get("name")
|
|
||||||
self._version = self.vacuum_state.get("softwareVer")
|
|
||||||
self._sku = self.vacuum_state.get("sku")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self):
|
|
||||||
"""Return the uniqueid of the vacuum cleaner."""
|
|
||||||
return f"roomba_{self._blid}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_info(self):
|
|
||||||
"""Return the device info of the vacuum cleaner."""
|
|
||||||
return {
|
|
||||||
"identifiers": {(DOMAIN, self.unique_id)},
|
|
||||||
"manufacturer": "iRobot",
|
|
||||||
"name": str(self._name),
|
|
||||||
"sw_version": self._version,
|
|
||||||
"model": self._sku,
|
|
||||||
}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self):
|
|
||||||
"""Flag vacuum cleaner robot features that are supported."""
|
|
||||||
if self._capabilities.get(CAP_CARPET_BOOST):
|
|
||||||
return SUPPORT_ROOMBA_CARPET_BOOST
|
|
||||||
return SUPPORT_ROOMBA
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fan_speed(self):
|
|
||||||
"""Return the fan speed of the vacuum cleaner."""
|
|
||||||
return self._fan_speed
|
|
||||||
|
|
||||||
@property
|
|
||||||
def fan_speed_list(self):
|
|
||||||
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
|
||||||
if self._capabilities.get(CAP_CARPET_BOOST):
|
|
||||||
return FAN_SPEEDS
|
|
||||||
|
|
||||||
@property
|
|
||||||
def battery_level(self):
|
|
||||||
"""Return the battery level of the vacuum cleaner."""
|
|
||||||
return self._battery_level
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status(self):
|
|
||||||
"""Return the status of the vacuum cleaner."""
|
|
||||||
return self._status
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_on(self) -> bool:
|
|
||||||
"""Return True if entity is on."""
|
|
||||||
return self._is_on
|
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return True if entity is available."""
|
|
||||||
return self._available
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
"""Return the name of the device."""
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device_state_attributes(self):
|
|
||||||
"""Return the state attributes of the device."""
|
|
||||||
return self._state_attrs
|
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs):
|
|
||||||
"""Turn the vacuum on."""
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, "start")
|
|
||||||
self._is_on = True
|
|
||||||
|
|
||||||
async def async_turn_off(self, **kwargs):
|
|
||||||
"""Turn the vacuum off and return to home."""
|
|
||||||
await self.async_stop()
|
|
||||||
await self.async_return_to_base()
|
|
||||||
|
|
||||||
async def async_stop(self, **kwargs):
|
|
||||||
"""Stop the vacuum cleaner."""
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, "stop")
|
|
||||||
self._is_on = False
|
|
||||||
|
|
||||||
async def async_resume(self, **kwargs):
|
|
||||||
"""Resume the cleaning cycle."""
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, "resume")
|
|
||||||
self._is_on = True
|
|
||||||
|
|
||||||
async def async_pause(self):
|
|
||||||
"""Pause the cleaning cycle."""
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, "pause")
|
|
||||||
self._is_on = False
|
|
||||||
|
|
||||||
async def async_start_pause(self, **kwargs):
|
|
||||||
"""Pause the cleaning task or resume it."""
|
|
||||||
if self.vacuum_state and self.is_on: # vacuum is running
|
|
||||||
await self.async_pause()
|
|
||||||
elif self._status == "Stopped": # vacuum is stopped
|
|
||||||
await self.async_resume()
|
|
||||||
else: # vacuum is off
|
|
||||||
await self.async_turn_on()
|
|
||||||
|
|
||||||
async def async_return_to_base(self, **kwargs):
|
|
||||||
"""Set the vacuum cleaner to return to the dock."""
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, "dock")
|
|
||||||
self._is_on = False
|
|
||||||
|
|
||||||
async def async_locate(self, **kwargs):
|
|
||||||
"""Located vacuum."""
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, "find")
|
|
||||||
|
|
||||||
async def async_set_fan_speed(self, fan_speed, **kwargs):
|
|
||||||
"""Set fan speed."""
|
|
||||||
if fan_speed.capitalize() in FAN_SPEEDS:
|
|
||||||
fan_speed = fan_speed.capitalize()
|
|
||||||
_LOGGER.debug("Set fan speed to: %s", fan_speed)
|
|
||||||
high_perf = None
|
|
||||||
carpet_boost = None
|
|
||||||
if fan_speed == FAN_SPEED_AUTOMATIC:
|
|
||||||
high_perf = False
|
|
||||||
carpet_boost = True
|
|
||||||
self._fan_speed = FAN_SPEED_AUTOMATIC
|
|
||||||
elif fan_speed == FAN_SPEED_ECO:
|
|
||||||
high_perf = False
|
|
||||||
carpet_boost = False
|
|
||||||
self._fan_speed = FAN_SPEED_ECO
|
|
||||||
elif fan_speed == FAN_SPEED_PERFORMANCE:
|
|
||||||
high_perf = True
|
|
||||||
carpet_boost = False
|
|
||||||
self._fan_speed = FAN_SPEED_PERFORMANCE
|
|
||||||
else:
|
|
||||||
_LOGGER.error("No such fan speed available: %s", fan_speed)
|
|
||||||
return
|
|
||||||
# The set_preference method does only accept string values
|
|
||||||
await self.hass.async_add_job(
|
|
||||||
self.vacuum.set_preference, "carpetBoost", str(carpet_boost)
|
|
||||||
)
|
|
||||||
await self.hass.async_add_job(
|
|
||||||
self.vacuum.set_preference, "vacHigh", str(high_perf)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_send_command(self, command, params=None, **kwargs):
|
|
||||||
"""Send raw command."""
|
|
||||||
_LOGGER.debug("async_send_command %s (%s), %s", command, params, kwargs)
|
|
||||||
await self.hass.async_add_job(self.vacuum.send_command, command, params)
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def async_update(self):
|
|
||||||
"""Fetch state from the device."""
|
|
||||||
# No data, no update
|
|
||||||
if not self.vacuum.master_state:
|
|
||||||
_LOGGER.debug("Roomba %s has no data yet. Skip update", self.name)
|
|
||||||
return
|
|
||||||
state = self.vacuum.master_state.get("state", {}).get("reported", {})
|
|
||||||
_LOGGER.debug("Got new state from the vacuum: %s", state)
|
|
||||||
self.vacuum_state = state
|
|
||||||
self._available = True
|
|
||||||
|
|
||||||
# Get the capabilities of our unit
|
|
||||||
capabilities = state.get("cap", {})
|
|
||||||
cap_carpet_boost = capabilities.get("carpetBoost")
|
|
||||||
cap_pos = capabilities.get("pose")
|
|
||||||
# Store capabilities
|
|
||||||
self._capabilities = {
|
|
||||||
CAP_CARPET_BOOST: cap_carpet_boost == 1,
|
|
||||||
CAP_POSITION: cap_pos == 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Roomba software version
|
|
||||||
software_version = state.get("softwareVer")
|
|
||||||
|
|
||||||
# Error message in plain english
|
|
||||||
error_msg = "None"
|
|
||||||
if hasattr(self.vacuum, "error_message"):
|
|
||||||
error_msg = self.vacuum.error_message
|
|
||||||
|
|
||||||
self._battery_level = state.get("batPct")
|
|
||||||
self._status = self.vacuum.current_state
|
|
||||||
self._is_on = self._status in ["Running"]
|
|
||||||
|
|
||||||
# Set properties that are to appear in the GUI
|
|
||||||
self._state_attrs = {ATTR_SOFTWARE_VERSION: software_version}
|
|
||||||
|
|
||||||
# Get bin state
|
|
||||||
bin_state = self._get_bin_state(state)
|
|
||||||
self._state_attrs.update(bin_state)
|
|
||||||
|
|
||||||
# Only add cleaning time and cleaned area attrs when the vacuum is
|
|
||||||
# currently on
|
|
||||||
if self._is_on:
|
|
||||||
# Get clean mission status
|
|
||||||
mission_state = state.get("cleanMissionStatus", {})
|
|
||||||
cleaning_time = mission_state.get("mssnM")
|
|
||||||
cleaned_area = mission_state.get("sqft") # Imperial
|
|
||||||
# Convert to m2 if the unit_system is set to metric
|
|
||||||
if cleaned_area and self.hass.config.units.is_metric:
|
|
||||||
cleaned_area = round(cleaned_area * 0.0929)
|
|
||||||
self._state_attrs[ATTR_CLEANING_TIME] = cleaning_time
|
|
||||||
self._state_attrs[ATTR_CLEANED_AREA] = cleaned_area
|
|
||||||
|
|
||||||
# Skip error attr if there is none
|
|
||||||
if error_msg and error_msg != "None":
|
|
||||||
self._state_attrs[ATTR_ERROR] = error_msg
|
|
||||||
|
|
||||||
# Not all Roombas expose position data
|
|
||||||
# https://github.com/koalazak/dorita980/issues/48
|
|
||||||
if self._capabilities[CAP_POSITION]:
|
|
||||||
pos_state = state.get("pose", {})
|
|
||||||
position = None
|
|
||||||
pos_x = pos_state.get("point", {}).get("x")
|
|
||||||
pos_y = pos_state.get("point", {}).get("y")
|
|
||||||
theta = pos_state.get("theta")
|
|
||||||
if all(item is not None for item in [pos_x, pos_y, theta]):
|
|
||||||
position = f"({pos_x}, {pos_y}, {theta})"
|
|
||||||
self._state_attrs[ATTR_POSITION] = position
|
|
||||||
|
|
||||||
# Fan speed mode (Performance, Automatic or Eco)
|
|
||||||
# Not all Roombas expose carpet boost
|
|
||||||
if self._capabilities[CAP_CARPET_BOOST]:
|
|
||||||
fan_speed = None
|
|
||||||
carpet_boost = state.get("carpetBoost")
|
|
||||||
high_perf = state.get("vacHigh")
|
|
||||||
|
|
||||||
if carpet_boost is not None and high_perf is not None:
|
|
||||||
if carpet_boost:
|
|
||||||
fan_speed = FAN_SPEED_AUTOMATIC
|
|
||||||
elif high_perf:
|
|
||||||
fan_speed = FAN_SPEED_PERFORMANCE
|
|
||||||
else: # carpet_boost and high_perf are False
|
|
||||||
fan_speed = FAN_SPEED_ECO
|
|
||||||
|
|
||||||
self._fan_speed = fan_speed
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_bin_state(state):
|
|
||||||
bin_raw_state = state.get("bin", {})
|
|
||||||
bin_state = {}
|
|
||||||
|
|
||||||
if bin_raw_state.get("present") is not None:
|
|
||||||
bin_state[ATTR_BIN_PRESENT] = bin_raw_state.get("present")
|
|
||||||
|
|
||||||
if bin_raw_state.get("full") is not None:
|
|
||||||
bin_state[ATTR_BIN_FULL] = bin_raw_state.get("full")
|
|
||||||
|
|
||||||
return bin_state
|
|
||||||
|
|
|
@ -1818,7 +1818,7 @@ rocketchat-API==0.6.1
|
||||||
roku==4.1.0
|
roku==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.5.0
|
roombapy==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.rova
|
# homeassistant.components.rova
|
||||||
rova==0.1.0
|
rova==0.1.0
|
||||||
|
|
|
@ -691,7 +691,7 @@ ring_doorbell==0.6.0
|
||||||
roku==4.1.0
|
roku==4.1.0
|
||||||
|
|
||||||
# homeassistant.components.roomba
|
# homeassistant.components.roomba
|
||||||
roombapy==1.5.0
|
roombapy==1.5.1
|
||||||
|
|
||||||
# homeassistant.components.yamaha
|
# homeassistant.components.yamaha
|
||||||
rxv==0.6.0
|
rxv==0.6.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue