mirror of
https://github.com/sudoxnym/roomba_rest980.git
synced 2026-04-14 03:27:28 +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
|
||||
|
||||
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"]
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
entry, ["switch", "button", "camera"]
|
||||
)
|
||||
# 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(
|
||||
|
|
@ -219,4 +224,4 @@ async def _async_match_blid(
|
|||
_LOGGER.warning("Could not match local Roomba with any cloud robot")
|
||||
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
_LOGGER.error("Error during BLID matching: %s", e)
|
||||
_LOGGER.error("Error during BLID matching: %s", e)
|
||||
|
|
@ -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:
|
||||
|
||||
# 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
|
||||
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_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"),
|
||||
)
|
||||
|
|
@ -203,4 +305,4 @@ class RoombaVacuum(CoordinatorEntity, StateVacuumEntity):
|
|||
"base_url": self._entry.data["base_url"],
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
)
|
||||
Loading…
Reference in a new issue