mirror of
https://github.com/sudoxnym/roomba_rest980.git
synced 2026-04-14 11:37:46 +00:00
Merge pull request #1 from sudoxnym/fix/i3-status-unknown-issue
Fix Roomba i3 status showing 'unknown' - Enhanced status mapping and error handling
This commit is contained in:
commit
b3da854baf
2 changed files with 132 additions and 25 deletions
|
|
@ -4,7 +4,7 @@ import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
@ -172,13 +172,18 @@ async def _async_setup_cloud(
|
||||||
# Use stored BLID from config entry
|
# Use stored BLID from config entry
|
||||||
entry.runtime_data.robot_blid = entry.data["robot_blid"]
|
entry.runtime_data.robot_blid = entry.data["robot_blid"]
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(
|
# Only forward setups if the entry is in LOADED state
|
||||||
entry, ["switch", "button", "camera"]
|
if entry.state == ConfigEntryState.LOADED:
|
||||||
)
|
await hass.config_entries.async_forward_entry_setups(
|
||||||
|
entry, ["switch", "button", "camera"]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.warning("Config entry not in LOADED state, skipping cloud platform setup")
|
||||||
|
|
||||||
except Exception as e: # pylint: disable=broad-except
|
except Exception as e: # pylint: disable=broad-except
|
||||||
_LOGGER.error("Failed to set up cloud coordinator: %s", e)
|
_LOGGER.error("Failed to set up cloud coordinator: %s", e)
|
||||||
cloud_coordinator = None
|
# Don't let cloud coordinator failure prevent main integration from working
|
||||||
|
entry.runtime_data.cloud_coordinator = None
|
||||||
|
|
||||||
|
|
||||||
async def _async_match_blid(
|
async def _async_match_blid(
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,57 @@ SUPPORT_ROBOT = (
|
||||||
| VacuumEntityFeature.PAUSE
|
| VacuumEntityFeature.PAUSE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Enhanced status mapping for i3 model support
|
||||||
|
I3_STATUS_MAPPING = {
|
||||||
|
# Idle/Ready states
|
||||||
|
0: VacuumActivity.IDLE, # Ready
|
||||||
|
1: VacuumActivity.IDLE, # Idle
|
||||||
|
2: VacuumActivity.IDLE, # Run
|
||||||
|
|
||||||
|
# Cleaning states
|
||||||
|
3: VacuumActivity.CLEANING, # Clean
|
||||||
|
4: VacuumActivity.CLEANING, # Spot cleaning
|
||||||
|
5: VacuumActivity.CLEANING, # Edge cleaning
|
||||||
|
|
||||||
|
# Docking/Charging states
|
||||||
|
6: VacuumActivity.RETURNING, # Seeking dock
|
||||||
|
7: VacuumActivity.DOCKED, # Charging
|
||||||
|
8: VacuumActivity.DOCKED, # Docked
|
||||||
|
|
||||||
|
# Error states
|
||||||
|
9: VacuumActivity.ERROR, # Error
|
||||||
|
10: VacuumActivity.ERROR, # Stuck
|
||||||
|
11: VacuumActivity.ERROR, # Picked up
|
||||||
|
|
||||||
|
# Additional i3 specific states
|
||||||
|
12: VacuumActivity.IDLE, # Stopped
|
||||||
|
13: VacuumActivity.PAUSED, # Paused
|
||||||
|
14: VacuumActivity.CLEANING, # Training
|
||||||
|
15: VacuumActivity.CLEANING, # Mapping
|
||||||
|
16: VacuumActivity.IDLE, # Manual
|
||||||
|
17: VacuumActivity.RETURNING,# Recharging
|
||||||
|
18: VacuumActivity.DOCKED, # Evacuating
|
||||||
|
19: VacuumActivity.CLEANING, # Smart cleaning
|
||||||
|
20: VacuumActivity.CLEANING, # Room cleaning
|
||||||
|
}
|
||||||
|
|
||||||
|
# Phase to activity mapping for better status detection
|
||||||
|
PHASE_MAPPING = {
|
||||||
|
"run": VacuumActivity.CLEANING,
|
||||||
|
"stop": VacuumActivity.IDLE,
|
||||||
|
"pause": VacuumActivity.PAUSED,
|
||||||
|
"charge": VacuumActivity.DOCKED,
|
||||||
|
"stuck": VacuumActivity.ERROR,
|
||||||
|
"hmUsrDock": VacuumActivity.RETURNING,
|
||||||
|
"hmPostMsn": VacuumActivity.RETURNING,
|
||||||
|
"hwMidMsn": VacuumActivity.CLEANING,
|
||||||
|
"evac": VacuumActivity.DOCKED,
|
||||||
|
"dock": VacuumActivity.DOCKED,
|
||||||
|
"charging": VacuumActivity.DOCKED,
|
||||||
|
"train": VacuumActivity.CLEANING,
|
||||||
|
"spot": VacuumActivity.CLEANING,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
|
||||||
|
|
@ -39,7 +90,7 @@ async def async_setup_entry(
|
||||||
|
|
||||||
|
|
||||||
class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
|
class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
|
||||||
"""The Rest980 controlled vacuum."""
|
"""The Rest980 controlled vacuum with enhanced i3 support."""
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistant, coordinator, entry: ConfigEntry) -> None:
|
def __init__(self, hass: HomeAssistant, coordinator, entry: ConfigEntry) -> None:
|
||||||
"""Setup the robot."""
|
"""Setup the robot."""
|
||||||
|
|
@ -51,29 +102,70 @@ class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
|
||||||
self._attr_name = entry.title
|
self._attr_name = entry.title
|
||||||
|
|
||||||
def _handle_coordinator_update(self):
|
def _handle_coordinator_update(self):
|
||||||
"""Update all attributes."""
|
"""Update all attributes with enhanced i3 status handling."""
|
||||||
data = self.coordinator.data or {}
|
data = self.coordinator.data or {}
|
||||||
status = data.get("cleanMissionStatus", {})
|
status = data.get("cleanMissionStatus", {})
|
||||||
|
|
||||||
|
# Get all relevant status fields
|
||||||
cycle = status.get("cycle")
|
cycle = status.get("cycle")
|
||||||
phase = status.get("phase")
|
phase = status.get("phase")
|
||||||
not_ready = status.get("notReady")
|
not_ready = status.get("notReady", 0)
|
||||||
|
mission_state = status.get("mssnStrtTm") # Mission start time
|
||||||
|
error_code = status.get("error", 0)
|
||||||
|
|
||||||
|
# Get robot model info for model-specific handling
|
||||||
|
robot_name = data.get("name", "").lower()
|
||||||
|
is_i3_model = "i3" in robot_name or data.get("sku", "").startswith("R3")
|
||||||
|
|
||||||
|
# Default to IDLE
|
||||||
self._attr_activity = VacuumActivity.IDLE
|
self._attr_activity = VacuumActivity.IDLE
|
||||||
if cycle == "none" and not_ready == 39:
|
|
||||||
|
# Enhanced status determination logic
|
||||||
|
try:
|
||||||
|
# Check for error conditions first
|
||||||
|
if error_code > 0 or not_ready > 0:
|
||||||
|
self._attr_activity = VacuumActivity.ERROR
|
||||||
|
_LOGGER.debug(f"Vacuum in error state: error={error_code}, not_ready={not_ready}")
|
||||||
|
|
||||||
|
# Check phase mapping first (most reliable for i3)
|
||||||
|
elif phase and phase in PHASE_MAPPING:
|
||||||
|
self._attr_activity = PHASE_MAPPING[phase]
|
||||||
|
_LOGGER.debug(f"Status from phase mapping: phase={phase}, activity={self._attr_activity}")
|
||||||
|
|
||||||
|
# Check cycle-based status
|
||||||
|
elif cycle:
|
||||||
|
if cycle == "none":
|
||||||
|
if not_ready == 39:
|
||||||
|
self._attr_activity = VacuumActivity.IDLE
|
||||||
|
else:
|
||||||
|
self._attr_activity = VacuumActivity.IDLE
|
||||||
|
elif cycle in ["clean", "quick", "spot", "train"]:
|
||||||
|
self._attr_activity = VacuumActivity.CLEANING
|
||||||
|
elif cycle in ["evac", "dock"]:
|
||||||
|
self._attr_activity = VacuumActivity.DOCKED
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(f"Unknown cycle: {cycle}")
|
||||||
|
|
||||||
|
# For i3 models, check additional status fields
|
||||||
|
if is_i3_model:
|
||||||
|
# i3 models might have different status reporting
|
||||||
|
battery_percent = data.get("batPct", 100)
|
||||||
|
if battery_percent < 20 and phase in ["charge", "charging"]:
|
||||||
|
self._attr_activity = VacuumActivity.DOCKED
|
||||||
|
|
||||||
|
# Check if mission is active
|
||||||
|
if mission_state and cycle in ["clean", "spot", "quick"]:
|
||||||
|
self._attr_activity = VacuumActivity.CLEANING
|
||||||
|
|
||||||
|
# Final fallback - if we still don't have a proper state
|
||||||
|
if self._attr_activity == VacuumActivity.IDLE and cycle and cycle != "none":
|
||||||
|
_LOGGER.warning(f"Unknown status combination: cycle={cycle}, phase={phase}, not_ready={not_ready}")
|
||||||
|
# Log for debugging to help identify new status combinations
|
||||||
|
_LOGGER.warning(f"Full status data: {status}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_LOGGER.error(f"Error determining vacuum activity: {e}")
|
||||||
self._attr_activity = VacuumActivity.IDLE
|
self._attr_activity = VacuumActivity.IDLE
|
||||||
if not_ready and not_ready > 0:
|
|
||||||
self._attr_activity = VacuumActivity.ERROR
|
|
||||||
if cycle in ["clean", "quick", "spot", "train"] or phase in {"hwMidMsn"}:
|
|
||||||
self._attr_activity = VacuumActivity.CLEANING
|
|
||||||
if cycle in ["evac", "dock"] or phase in {
|
|
||||||
"charge",
|
|
||||||
}: # Emptying Roomba Bin to Dock, Entering Dock
|
|
||||||
self._attr_activity = VacuumActivity.DOCKED
|
|
||||||
if phase in {
|
|
||||||
"hmUsrDock",
|
|
||||||
"hmPostMsn",
|
|
||||||
}: # Sent Home, Mid Dock, Final Dock
|
|
||||||
self._attr_activity = VacuumActivity.RETURNING
|
|
||||||
|
|
||||||
self._attr_available = data != {}
|
self._attr_available = data != {}
|
||||||
self._attr_extra_state_attributes = createExtendedAttributes(self)
|
self._attr_extra_state_attributes = createExtendedAttributes(self)
|
||||||
|
|
@ -83,11 +175,21 @@ class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return the Roomba's device information."""
|
"""Return the Roomba's device information."""
|
||||||
data = self.coordinator.data or {}
|
data = self.coordinator.data or {}
|
||||||
|
model = data.get("sku", "Roomba")
|
||||||
|
|
||||||
|
# Enhanced model detection for i3
|
||||||
|
if model.startswith("R3") or "i3" in data.get("name", "").lower():
|
||||||
|
model = "Roomba i3"
|
||||||
|
elif model.startswith("R7"):
|
||||||
|
model = "Roomba i7"
|
||||||
|
elif model.startswith("R9"):
|
||||||
|
model = "Roomba s9"
|
||||||
|
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
identifiers={(DOMAIN, self._entry.unique_id)},
|
identifiers={(DOMAIN, self._entry.unique_id)},
|
||||||
name=data.get("name", "Roomba"),
|
name=data.get("name", "Roomba"),
|
||||||
manufacturer="iRobot",
|
manufacturer="iRobot",
|
||||||
model="Roomba",
|
model=model,
|
||||||
model_id=data.get("sku"),
|
model_id=data.get("sku"),
|
||||||
sw_version=data.get("softwareVer"),
|
sw_version=data.get("softwareVer"),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue