Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions homeassistant/components/litterrobot/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ async def async_step_user(self, user_input=None):
hub = LitterRobotHub(self.hass, user_input)
try:
await hub.login()
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=user_input
)
except LitterRobotLoginException:
errors["base"] = "invalid_auth"
except LitterRobotException:
Expand All @@ -46,6 +43,11 @@ async def async_step_user(self, user_input=None):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"

if not errors:
return self.async_create_entry(
title=user_input[CONF_USERNAME], data=user_input
)

return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
21 changes: 10 additions & 11 deletions homeassistant/components/litterrobot/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from types import MethodType
from typing import Any, Optional

from pylitterbot import Account, Robot
import pylitterbot
from pylitterbot.exceptions import LitterRobotException, LitterRobotLoginException

from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
Expand Down Expand Up @@ -49,7 +49,7 @@ async def _async_update_data():
async def login(self, load_robots: bool = False):
"""Login to Litter-Robot."""
self.logged_in = False
self.account = Account()
self.account = pylitterbot.Account()
try:
await self.account.connect(
username=self._data[CONF_USERNAME],
Expand All @@ -69,11 +69,11 @@ async def login(self, load_robots: bool = False):
class LitterRobotEntity(CoordinatorEntity):
"""Generic Litter-Robot entity representing common data and methods."""

def __init__(self, robot: Robot, entity_type: str, hub: LitterRobotHub):
def __init__(self, robot: pylitterbot.Robot, entity_type: str, hub: LitterRobotHub):
"""Pass coordinator to CoordinatorEntity."""
super().__init__(hub.coordinator)
self.robot = robot
self.entity_type = entity_type if entity_type else ""
self.entity_type = entity_type
self.hub = hub

@property
Expand All @@ -89,22 +89,21 @@ def unique_id(self):
@property
def device_info(self):
"""Return the device information for a Litter-Robot."""
model = "Litter-Robot 3 Connect"
if not self.robot.serial.startswith("LR3C"):
model = "Other Litter-Robot Connected Device"
return {
"identifiers": {(DOMAIN, self.robot.serial)},
"name": self.robot.name,
"manufacturer": "Litter-Robot",
"model": model,
"model": self.robot.model,
}

async def perform_action_and_refresh(self, action: MethodType, *args: Any):
"""Perform an action and initiates a refresh of the robot data after a few seconds."""

async def async_call_later_callback(*_) -> None:
await self.hub.coordinator.async_request_refresh()

await action(*args)
async_call_later(
self.hass, REFRESH_WAIT_TIME, self.hub.coordinator.async_request_refresh
)
async_call_later(self.hass, REFRESH_WAIT_TIME, async_call_later_callback)
Comment thread
natekspencer marked this conversation as resolved.

@staticmethod
def parse_time_at_default_timezone(time_str: str) -> Optional[time]:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/litterrobot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"name": "Litter-Robot",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/litterrobot",
"requirements": ["pylitterbot==2021.2.5"],
"requirements": ["pylitterbot==2021.2.8"],
"codeowners": ["@natekspencer"]
}
84 changes: 52 additions & 32 deletions homeassistant/components/litterrobot/sensor.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
"""Support for Litter-Robot sensors."""
from homeassistant.const import PERCENTAGE
from pylitterbot.robot import Robot

from homeassistant.const import DEVICE_CLASS_TIMESTAMP, PERCENTAGE
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.icon import icon_for_gauge_level

from .const import DOMAIN
from .hub import LitterRobotEntity

WASTE_DRAWER = "Waste Drawer"


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Litter-Robot sensors using config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id]

entities = []
for robot in hub.account.robots:
entities.append(LitterRobotSensor(robot, WASTE_DRAWER, hub))
from .hub import LitterRobotEntity, LitterRobotHub

if entities:
async_add_entities(entities, True)

class LitterRobotPropertySensor(LitterRobotEntity, Entity):
"""Litter-Robot property sensors."""

class LitterRobotSensor(LitterRobotEntity, Entity):
"""Litter-Robot sensors."""
def __init__(
self, robot: Robot, entity_type: str, hub: LitterRobotHub, sensor_attribute: str
):
"""Pass coordinator to CoordinatorEntity."""
super().__init__(robot, entity_type, hub)
self.sensor_attribute = sensor_attribute

@property
def state(self):
"""Return the state."""
return self.robot.waste_drawer_gauge
return getattr(self.robot, self.sensor_attribute)


class LitterRobotWasteSensor(LitterRobotPropertySensor, Entity):
"""Litter-Robot sensors."""

@property
def unit_of_measurement(self):
Expand All @@ -36,19 +36,39 @@ def unit_of_measurement(self):
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
if self.robot.waste_drawer_gauge <= 10:
return "mdi:gauge-empty"
if self.robot.waste_drawer_gauge < 50:
return "mdi:gauge-low"
if self.robot.waste_drawer_gauge <= 90:
return "mdi:gauge"
return "mdi:gauge-full"
return icon_for_gauge_level(self.state, 10)


class LitterRobotSleepTimeSensor(LitterRobotPropertySensor, Entity):
"""Litter-Robot sleep time sensors."""

@property
def device_state_attributes(self):
"""Return device specific state attributes."""
return {
"cycle_count": self.robot.cycle_count,
"cycle_capacity": self.robot.cycle_capacity,
"cycles_after_drawer_full": self.robot.cycles_after_drawer_full,
}
def state(self):
"""Return the state."""
if self.robot.sleep_mode_active:
return super().state
Comment thread
natekspencer marked this conversation as resolved.
Outdated

@property
def device_class(self):
"""Return the device class, if any."""
return DEVICE_CLASS_TIMESTAMP


ROBOT_SENSORS = [
(LitterRobotWasteSensor, "Waste Drawer", "waste_drawer_gauge"),
(LitterRobotSleepTimeSensor, "Sleep Mode Start Time", "sleep_mode_start_time"),
(LitterRobotSleepTimeSensor, "Sleep Mode End Time", "sleep_mode_end_time"),
]


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Litter-Robot sensors using config entry."""
hub = hass.data[DOMAIN][config_entry.entry_id]

entities = []
for robot in hub.account.robots:
for (sensor_class, entity_type, sensor_attribute) in ROBOT_SENSORS:
entities.append(sensor_class(robot, entity_type, hub, sensor_attribute))

if entities:
async_add_entities(entities, True)
6 changes: 3 additions & 3 deletions homeassistant/components/litterrobot/switch.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Support for Litter-Robot switches."""
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.switch import SwitchEntity

from .const import DOMAIN
from .hub import LitterRobotEntity


class LitterRobotNightLightModeSwitch(LitterRobotEntity, ToggleEntity):
class LitterRobotNightLightModeSwitch(LitterRobotEntity, SwitchEntity):
"""Litter-Robot Night Light Mode Switch."""

@property
Expand All @@ -27,7 +27,7 @@ async def async_turn_off(self, **kwargs):
await self.perform_action_and_refresh(self.robot.set_night_light, False)


class LitterRobotPanelLockoutSwitch(LitterRobotEntity, ToggleEntity):
class LitterRobotPanelLockoutSwitch(LitterRobotEntity, SwitchEntity):
"""Litter-Robot Panel Lockout Switch."""

@property
Expand Down
37 changes: 10 additions & 27 deletions homeassistant/components/litterrobot/vacuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
VacuumEntity,
)
from homeassistant.const import STATE_OFF
import homeassistant.util.dt as dt_util

from .const import DOMAIN
from .hub import LitterRobotEntity
Expand Down Expand Up @@ -54,27 +53,22 @@ def supported_features(self):
def state(self):
"""Return the state of the cleaner."""
switcher = {
Robot.UnitStatus.CCP: STATE_CLEANING,
Robot.UnitStatus.EC: STATE_CLEANING,
Robot.UnitStatus.CCC: STATE_DOCKED,
Robot.UnitStatus.CST: STATE_DOCKED,
Robot.UnitStatus.DF1: STATE_DOCKED,
Robot.UnitStatus.DF2: STATE_DOCKED,
Robot.UnitStatus.RDY: STATE_DOCKED,
Robot.UnitStatus.CLEAN_CYCLE: STATE_CLEANING,
Robot.UnitStatus.EMPTY_CYCLE: STATE_CLEANING,
Robot.UnitStatus.CLEAN_CYCLE_COMPLETE: STATE_DOCKED,
Robot.UnitStatus.CAT_SENSOR_TIMING: STATE_DOCKED,
Robot.UnitStatus.DRAWER_FULL_1: STATE_DOCKED,
Robot.UnitStatus.DRAWER_FULL_2: STATE_DOCKED,
Robot.UnitStatus.READY: STATE_DOCKED,
Robot.UnitStatus.OFF: STATE_OFF,
}

return switcher.get(self.robot.unit_status, STATE_ERROR)

@property
def error(self):
"""Return the error associated with the current state, if any."""
return self.robot.unit_status.value

@property
def status(self):
"""Return the status of the cleaner."""
return f"{self.robot.unit_status.value}{' (Sleeping)' if self.robot.is_sleeping else ''}"
return f"{self.robot.unit_status.label}{' (Sleeping)' if self.robot.is_sleeping else ''}"

async def async_turn_on(self, **kwargs):
"""Turn the cleaner on, starting a clean cycle."""
Expand Down Expand Up @@ -119,22 +113,11 @@ async def async_send_command(self, command, params=None, **kwargs):
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
[sleep_mode_start_time, sleep_mode_end_time] = [None, None]

if self.robot.sleep_mode_active:
sleep_mode_start_time = dt_util.as_local(
self.robot.sleep_mode_start_time
).strftime("%H:%M:00")
sleep_mode_end_time = dt_util.as_local(
self.robot.sleep_mode_end_time
).strftime("%H:%M:00")

return {
"clean_cycle_wait_time_minutes": self.robot.clean_cycle_wait_time_minutes,
"is_sleeping": self.robot.is_sleeping,
"sleep_mode_start_time": sleep_mode_start_time,
"sleep_mode_end_time": sleep_mode_end_time,
"sleep_mode_active": self.robot.sleep_mode_active,
"power_status": self.robot.power_status,
"unit_status_code": self.robot.unit_status.name,
"unit_status_code": self.robot.unit_status.value,
"last_seen": self.robot.last_seen,
}
11 changes: 11 additions & 0 deletions homeassistant/helpers/icon.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,14 @@ def icon_for_signal_level(signal_level: Optional[int] = None) -> str:
if signal_level > 30:
return "mdi:signal-cellular-2"
return "mdi:signal-cellular-1"


def icon_for_gauge_level(gauge_level: Optional[int] = None, offset: int = 0) -> str:
Comment thread
natekspencer marked this conversation as resolved.
Outdated
"""Return a gauge icon valid identifier."""
if gauge_level is None or gauge_level <= 0 + offset:
return "mdi:gauge-empty"
if gauge_level > 70 + offset:
return "mdi:gauge-full"
if gauge_level > 30 + offset:
return "mdi:gauge"
return "mdi:gauge-low"
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,7 @@ pylibrespot-java==0.1.0
pylitejet==0.3.0

# homeassistant.components.litterrobot
pylitterbot==2021.2.5
pylitterbot==2021.2.8

# homeassistant.components.loopenergy
pyloopenergy==0.2.1
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -794,7 +794,7 @@ pylibrespot-java==0.1.0
pylitejet==0.3.0

# homeassistant.components.litterrobot
pylitterbot==2021.2.5
pylitterbot==2021.2.8

# homeassistant.components.lutron_caseta
pylutron-caseta==0.9.0
Expand Down
Loading