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:
sudoxnym 2025-10-01 19:37:34 -06:00 committed by GitHub
commit b3da854baf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 132 additions and 25 deletions

View file

@ -4,7 +4,7 @@ import logging
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.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@ -172,13 +172,18 @@ async def _async_setup_cloud(
# Use stored BLID from config entry
entry.runtime_data.robot_blid = entry.data["robot_blid"]
# Only forward setups if the entry is in LOADED state
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
_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(

View file

@ -28,6 +28,57 @@ SUPPORT_ROBOT = (
| 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(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities
@ -39,7 +90,7 @@ async def async_setup_entry(
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:
"""Setup the robot."""
@ -51,29 +102,70 @@ class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
self._attr_name = entry.title
def _handle_coordinator_update(self):
"""Update all attributes."""
"""Update all attributes with enhanced i3 status handling."""
data = self.coordinator.data or {}
status = data.get("cleanMissionStatus", {})
# Get all relevant status fields
cycle = status.get("cycle")
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
if cycle == "none" and not_ready == 39:
self._attr_activity = VacuumActivity.IDLE
if not_ready and not_ready > 0:
# Enhanced status determination logic
try:
# Check for error conditions first
if error_code > 0 or not_ready > 0:
self._attr_activity = VacuumActivity.ERROR
if cycle in ["clean", "quick", "spot", "train"] or phase in {"hwMidMsn"}:
_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
if cycle in ["evac", "dock"] or phase in {
"charge",
}: # Emptying Roomba Bin to Dock, Entering Dock
elif cycle in ["evac", "dock"]:
self._attr_activity = VacuumActivity.DOCKED
if phase in {
"hmUsrDock",
"hmPostMsn",
}: # Sent Home, Mid Dock, Final Dock
self._attr_activity = VacuumActivity.RETURNING
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_available = data != {}
self._attr_extra_state_attributes = createExtendedAttributes(self)
@ -83,11 +175,21 @@ class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
def device_info(self) -> DeviceInfo:
"""Return the Roomba's device information."""
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(
identifiers={(DOMAIN, self._entry.unique_id)},
name=data.get("name", "Roomba"),
manufacturer="iRobot",
model="Roomba",
model=model,
model_id=data.get("sku"),
sw_version=data.get("softwareVer"),
)