2020-08-12 07:31:39 -06:00
|
|
|
"""Shark IQ Wrapper."""
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
|
|
|
|
import logging
|
2020-08-12 07:31:39 -06:00
|
|
|
from typing import Dict, Optional
|
|
|
|
|
|
|
|
from sharkiqpy import OperatingModes, PowerModes, Properties, SharkIqVacuum
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
from homeassistant.components.vacuum import (
|
|
|
|
STATE_CLEANING,
|
|
|
|
STATE_DOCKED,
|
|
|
|
STATE_IDLE,
|
|
|
|
STATE_PAUSED,
|
|
|
|
STATE_RETURNING,
|
|
|
|
SUPPORT_BATTERY,
|
|
|
|
SUPPORT_FAN_SPEED,
|
|
|
|
SUPPORT_LOCATE,
|
|
|
|
SUPPORT_PAUSE,
|
|
|
|
SUPPORT_RETURN_HOME,
|
|
|
|
SUPPORT_START,
|
|
|
|
SUPPORT_STATE,
|
|
|
|
SUPPORT_STATUS,
|
|
|
|
SUPPORT_STOP,
|
|
|
|
StateVacuumEntity,
|
|
|
|
)
|
2020-08-12 07:31:39 -06:00
|
|
|
|
2020-07-22 07:34:16 -06:00
|
|
|
from .const import DOMAIN, SHARK
|
2020-08-12 07:31:39 -06:00
|
|
|
from .update_coordinator import SharkIqUpdateCoordinator
|
2020-07-21 08:29:36 -06:00
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
LOGGER = logging.getLogger(__name__)
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
# Supported features
|
|
|
|
SUPPORT_SHARKIQ = (
|
|
|
|
SUPPORT_BATTERY
|
|
|
|
| SUPPORT_FAN_SPEED
|
|
|
|
| SUPPORT_PAUSE
|
|
|
|
| SUPPORT_RETURN_HOME
|
|
|
|
| SUPPORT_START
|
|
|
|
| SUPPORT_STATE
|
|
|
|
| SUPPORT_STATUS
|
|
|
|
| SUPPORT_STOP
|
|
|
|
| SUPPORT_LOCATE
|
|
|
|
)
|
|
|
|
|
|
|
|
OPERATING_STATE_MAP = {
|
|
|
|
OperatingModes.PAUSE: STATE_PAUSED,
|
|
|
|
OperatingModes.START: STATE_CLEANING,
|
|
|
|
OperatingModes.STOP: STATE_IDLE,
|
|
|
|
OperatingModes.RETURN: STATE_RETURNING,
|
|
|
|
}
|
|
|
|
|
|
|
|
FAN_SPEEDS_MAP = {
|
|
|
|
"Eco": PowerModes.ECO,
|
|
|
|
"Normal": PowerModes.NORMAL,
|
|
|
|
"Max": PowerModes.MAX,
|
|
|
|
}
|
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
STATE_RECHARGING_TO_RESUME = "recharging_to_resume"
|
2020-07-22 07:34:16 -06:00
|
|
|
|
2020-07-22 19:16:09 -06:00
|
|
|
# Attributes to expose
|
2020-08-12 07:31:39 -06:00
|
|
|
ATTR_ERROR_CODE = "last_error_code"
|
|
|
|
ATTR_ERROR_MSG = "last_error_message"
|
|
|
|
ATTR_LOW_LIGHT = "low_light"
|
2020-07-22 19:16:09 -06:00
|
|
|
ATTR_RECHARGE_RESUME = "recharge_and_resume"
|
|
|
|
ATTR_RSSI = "rssi"
|
|
|
|
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
class SharkVacuumEntity(StateVacuumEntity):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Shark IQ vacuum entity."""
|
|
|
|
|
|
|
|
def __init__(self, sharkiq: SharkIqVacuum, coordinator: SharkIqUpdateCoordinator):
|
|
|
|
"""Create a new SharkVacuumEntity."""
|
|
|
|
if sharkiq.serial_number not in coordinator.shark_vacs:
|
|
|
|
raise RuntimeError(
|
|
|
|
f"Shark IQ robot {sharkiq.serial_number} is not known to the coordinator"
|
|
|
|
)
|
|
|
|
self.coordinator = coordinator
|
2020-07-21 08:29:36 -06:00
|
|
|
self.sharkiq = sharkiq
|
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
@property
|
|
|
|
def should_poll(self):
|
|
|
|
"""Don't poll this entity. Polling is done via the coordinator."""
|
|
|
|
return False
|
|
|
|
|
2020-07-21 08:29:36 -06:00
|
|
|
def clean_spot(self, **kwargs):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Clean a spot. Not yet implemented."""
|
2020-07-21 08:29:36 -06:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def send_command(self, command, params=None, **kwargs):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Send a command to the vacuum. Not yet implemented."""
|
2020-07-21 08:29:36 -06:00
|
|
|
raise NotImplementedError()
|
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
@property
|
|
|
|
def is_online(self) -> bool:
|
|
|
|
"""Tell us if the device is online."""
|
|
|
|
return self.coordinator.is_online(self.sharkiq.serial_number)
|
|
|
|
|
2020-07-21 08:29:36 -06:00
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Device name."""
|
2020-07-21 08:29:36 -06:00
|
|
|
return self.sharkiq.name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def serial_number(self) -> str:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Vacuum API serial number (DSN)."""
|
2020-07-21 08:29:36 -06:00
|
|
|
return self.sharkiq.serial_number
|
|
|
|
|
2020-07-22 07:34:16 -06:00
|
|
|
@property
|
|
|
|
def model(self) -> str:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Vacuum model number."""
|
2020-07-22 07:37:10 -06:00
|
|
|
if self.sharkiq.vac_model_number:
|
2020-07-22 07:34:16 -06:00
|
|
|
return self.sharkiq.vac_model_number
|
2020-08-12 07:31:39 -06:00
|
|
|
return self.sharkiq.oem_model_number
|
2020-07-22 07:34:16 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def device_info(self) -> Dict:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Device info dictionary."""
|
2020-07-22 07:34:16 -06:00
|
|
|
return {
|
2020-08-12 07:31:39 -06:00
|
|
|
"identifiers": {(DOMAIN, self.serial_number)},
|
2020-07-22 07:34:16 -06:00
|
|
|
"name": self.name,
|
|
|
|
"manufacturer": SHARK,
|
|
|
|
"model": self.model,
|
2020-08-12 07:31:39 -06:00
|
|
|
"sw_version": self.sharkiq.get_property_value(
|
|
|
|
Properties.ROBOT_FIRMWARE_VERSION
|
|
|
|
),
|
2020-07-22 07:34:16 -06:00
|
|
|
}
|
|
|
|
|
2020-07-21 08:29:36 -06:00
|
|
|
@property
|
|
|
|
def supported_features(self) -> int:
|
|
|
|
"""Flag vacuum cleaner robot features that are supported."""
|
|
|
|
return SUPPORT_SHARKIQ
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_docked(self) -> Optional[bool]:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Is vacuum docked."""
|
2020-07-22 08:30:57 -06:00
|
|
|
return self.sharkiq.get_property_value(Properties.DOCKED_STATUS)
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def error_code(self) -> Optional[int]:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Return the last observed error code (or None)."""
|
|
|
|
return self.sharkiq.error_code
|
|
|
|
|
|
|
|
@property
|
|
|
|
def error_message(self) -> Optional[str]:
|
|
|
|
"""Return the last observed error message (or None)."""
|
|
|
|
if not self.error_code:
|
2020-07-22 07:34:16 -06:00
|
|
|
return None
|
2020-08-12 07:31:39 -06:00
|
|
|
return self.sharkiq.error_code
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def operating_mode(self) -> Optional[str]:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Operating mode.."""
|
2020-07-21 08:29:36 -06:00
|
|
|
op_mode = self.sharkiq.get_property_value(Properties.OPERATING_MODE)
|
|
|
|
return OPERATING_STATE_MAP.get(op_mode)
|
|
|
|
|
2020-07-22 07:34:16 -06:00
|
|
|
@property
|
|
|
|
def recharging_to_resume(self) -> Optional[int]:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Return True if vacuum set to recharge and resume cleaning."""
|
2020-07-22 07:34:16 -06:00
|
|
|
return self.sharkiq.get_property_value(Properties.RECHARGING_TO_RESUME)
|
|
|
|
|
2020-07-21 08:29:36 -06:00
|
|
|
@property
|
|
|
|
def state(self):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""
|
|
|
|
Get the current vacuum state.
|
|
|
|
|
|
|
|
NB: Currently, we do not return an error state because they can be very, very stale.
|
|
|
|
In the app, these are (usually) handled by showing the robot as stopped and sending the
|
|
|
|
user a notification.
|
|
|
|
"""
|
2020-07-22 07:34:16 -06:00
|
|
|
if self.recharging_to_resume:
|
|
|
|
return STATE_RECHARGING_TO_RESUME
|
2020-08-12 07:31:39 -06:00
|
|
|
if self.is_docked:
|
2020-07-21 08:29:36 -06:00
|
|
|
return STATE_DOCKED
|
2020-08-12 07:31:39 -06:00
|
|
|
return self.operating_mode
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def unique_id(self) -> str:
|
|
|
|
"""Return the unique id of the vacuum cleaner."""
|
2020-08-12 07:31:39 -06:00
|
|
|
return f"sharkiq-{self.serial_number:s}-vacuum"
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self) -> bool:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Determine if the sensor is available based on API results."""
|
|
|
|
# If the last update was successful...
|
|
|
|
if self.coordinator.last_update_success and self.is_online:
|
|
|
|
return True
|
|
|
|
|
|
|
|
# Otherwise, we are not.
|
|
|
|
return False
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def battery_level(self):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Get the current battery level."""
|
2020-07-21 08:29:36 -06:00
|
|
|
return self.sharkiq.get_property_value(Properties.BATTERY_CAPACITY)
|
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
async def async_update(self):
|
|
|
|
"""Update the known properties asynchronously."""
|
|
|
|
await self.coordinator.async_request_refresh()
|
2020-07-21 08:29:36 -06:00
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
async def async_added_to_hass(self) -> None:
|
|
|
|
"""Connect to dispatcher listening for entity data notifications."""
|
|
|
|
self.async_on_remove(
|
|
|
|
self.coordinator.async_add_listener(self.async_write_ha_state)
|
|
|
|
)
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
async def async_return_to_base(self, **kwargs):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Have the device return to base."""
|
2020-07-21 08:29:36 -06:00
|
|
|
await self.sharkiq.async_set_operating_mode(OperatingModes.RETURN)
|
2020-08-12 07:31:39 -06:00
|
|
|
await self.coordinator.async_refresh()
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
async def async_pause(self):
|
|
|
|
"""Pause the cleaning task."""
|
|
|
|
await self.sharkiq.async_set_operating_mode(OperatingModes.PAUSE)
|
2020-08-12 07:31:39 -06:00
|
|
|
await self.coordinator.async_refresh()
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
async def async_start(self):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Start the device."""
|
2020-07-21 08:29:36 -06:00
|
|
|
await self.sharkiq.async_set_operating_mode(OperatingModes.START)
|
2020-08-12 07:31:39 -06:00
|
|
|
await self.coordinator.async_refresh()
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
async def async_stop(self, **kwargs):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Stop the device."""
|
2020-07-21 08:29:36 -06:00
|
|
|
await self.sharkiq.async_set_operating_mode(OperatingModes.STOP)
|
2020-08-12 07:31:39 -06:00
|
|
|
await self.coordinator.async_refresh()
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
async def async_locate(self, **kwargs):
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Cause the device to generate a loud chirp."""
|
2020-07-21 08:29:36 -06:00
|
|
|
await self.sharkiq.async_find_device()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def fan_speed(self) -> str:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Return the current fan speed."""
|
2020-07-21 08:29:36 -06:00
|
|
|
fan_speed = None
|
|
|
|
speed_level = self.sharkiq.get_property_value(Properties.POWER_MODE)
|
|
|
|
for k, val in FAN_SPEEDS_MAP.items():
|
|
|
|
if val == speed_level:
|
|
|
|
fan_speed = k
|
|
|
|
return fan_speed
|
|
|
|
|
2020-08-12 07:31:39 -06:00
|
|
|
async def async_set_fan_speed(self, fan_speed: str, **kwargs):
|
|
|
|
"""Set the fan speed."""
|
|
|
|
await self.sharkiq.async_set_property_value(
|
2020-07-21 08:29:36 -06:00
|
|
|
Properties.POWER_MODE, FAN_SPEEDS_MAP.get(fan_speed.capitalize())
|
|
|
|
)
|
2020-08-12 07:31:39 -06:00
|
|
|
await self.coordinator.async_refresh()
|
2020-07-21 08:29:36 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def fan_speed_list(self):
|
|
|
|
"""Get the list of available fan speed steps of the vacuum cleaner."""
|
|
|
|
return list(FAN_SPEEDS_MAP.keys())
|
2020-07-22 08:30:57 -06:00
|
|
|
|
|
|
|
# Various attributes we want to expose
|
|
|
|
@property
|
|
|
|
def recharge_resume(self) -> Optional[bool]:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Recharge and resume mode active."""
|
2020-07-22 08:30:57 -06:00
|
|
|
return self.sharkiq.get_property_value(Properties.RECHARGE_RESUME)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def rssi(self) -> Optional[int]:
|
2020-08-12 07:31:39 -06:00
|
|
|
"""Get the WiFi RSSI."""
|
2020-07-22 08:30:57 -06:00
|
|
|
return self.sharkiq.get_property_value(Properties.RSSI)
|
2020-07-22 19:16:09 -06:00
|
|
|
|
|
|
|
@property
|
2020-08-12 07:31:39 -06:00
|
|
|
def low_light(self):
|
|
|
|
"""Let us know if the robot is operating in low-light mode."""
|
|
|
|
return self.sharkiq.get_property_value(Properties.LOW_LIGHT_MISSION)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def shark_state_attributes(self) -> Dict:
|
|
|
|
"""Return a dictionary of device state attributes specific to sharkiq."""
|
|
|
|
data = {
|
|
|
|
ATTR_ERROR_CODE: self.error_code,
|
|
|
|
ATTR_ERROR_MSG: self.sharkiq.error_text,
|
|
|
|
ATTR_LOW_LIGHT: self.low_light,
|
|
|
|
ATTR_RECHARGE_RESUME: self.recharge_resume,
|
|
|
|
ATTR_RSSI: self.rssi,
|
|
|
|
}
|
2020-07-22 19:16:09 -06:00
|
|
|
return data
|
2020-08-12 07:31:39 -06:00
|
|
|
|
|
|
|
@property
|
|
|
|
def state_attributes(self) -> Dict:
|
|
|
|
"""Return a dictionary of device state attributes."""
|
|
|
|
return dict(**super().state_attributes, **self.shark_state_attributes)
|