Implement Enhanced Error Handling with Repair Flows
🎯 Objective
Implement Home Assistant's repair flow system to provide actionable solutions for common TryFi integration issues. This will guide users through fixing problems without needing technical knowledge.
📋 Requirements
- Implement repair flows for common issues
- Provide clear, actionable fix instructions
- Auto-detect problems and suggest repairs
- Support both automatic and manual repairs
- Integrate with Home Assistant's repair system
🔧 Implementation Plan
1. Create Repair Flow Handler
Create repairs.py
"""Repairs support for TryFi."""
from __future__ import annotations
import logging
from typing import Any
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.repairs import RepairsFlow
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import issue_registry as ir
from .const import DOMAIN
from .pytryfi import PyTryFi
_LOGGER = logging.getLogger(__name__)
# Issue IDs
ISSUE_INVALID_AUTH = "invalid_authentication"
ISSUE_NO_PETS_FOUND = "no_pets_found"
ISSUE_BASE_OFFLINE = "base_station_offline"
ISSUE_COLLAR_NOT_RESPONDING = "collar_not_responding"
ISSUE_API_RATE_LIMITED = "api_rate_limited"
ISSUE_OLD_FIRMWARE = "outdated_firmware"
class TryFiRepairFlow(RepairsFlow):
"""Handler for TryFi repair flows."""
def __init__(self, issue_id: str, data: dict[str, Any]) -> None:
"""Initialize the repair flow."""
self.issue_id = issue_id
self.data = data
self.entry: ConfigEntry | None = None
async def async_step_init(self) -> FlowResult:
"""Handle the initial step."""
if self.issue_id == ISSUE_INVALID_AUTH:
return await self.async_step_reauth()
elif self.issue_id == ISSUE_NO_PETS_FOUND:
return await self.async_step_no_pets()
elif self.issue_id == ISSUE_BASE_OFFLINE:
return await self.async_step_base_offline()
elif self.issue_id == ISSUE_COLLAR_NOT_RESPONDING:
return await self.async_step_collar_issue()
elif self.issue_id == ISSUE_API_RATE_LIMITED:
return await self.async_step_rate_limited()
return self.async_show_form(step_id="init")
async def async_step_reauth(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle re-authentication."""
errors = {}
if user_input is not None:
try:
# Test new credentials
tryfi = await self.hass.async_add_executor_job(
PyTryFi,
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
# Update config entry
self.entry = self.hass.config_entries.async_get_entry(
self.data["entry_id"]
)
if self.entry:
self.hass.config_entries.async_update_entry(
self.entry,
data={**self.entry.data, **user_input},
)
# Clear the issue
ir.async_delete_issue(
self.hass, DOMAIN, f"{ISSUE_INVALID_AUTH}_{self.entry.entry_id}"
)
return self.async_create_entry(
title="",
data={},
)
except Exception:
errors["base"] = "invalid_auth"
return self.async_show_form(
step_id="reauth",
data_schema=vol.Schema({
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}),
errors=errors,
description_placeholders={
"error": "Your TryFi credentials are no longer valid.",
},
)
async def async_step_no_pets(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle no pets found issue."""
if user_input is not None:
# Just acknowledge and close
ir.async_delete_issue(
self.hass, DOMAIN, f"{ISSUE_NO_PETS_FOUND}_{self.data['entry_id']}"
)
return self.async_create_entry(title="", data={})
return self.async_show_form(
step_id="no_pets",
description_placeholders={
"instructions": (
"No pets with collars were found in your TryFi account.\n\n"
"Please:\n"
"1. Open the TryFi app\n"
"2. Ensure your pet has a collar assigned\n"
"3. Verify the collar is activated\n"
"4. Wait 5 minutes and reload this integration"
),
},
)
async def async_step_base_offline(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle offline base station."""
if user_input is not None:
# Acknowledge the steps were taken
ir.async_delete_issue(
self.hass, DOMAIN, f"{ISSUE_BASE_OFFLINE}_{self.data['base_id']}"
)
return self.async_create_entry(title="", data={})
return self.async_show_form(
step_id="base_offline",
description_placeholders={
"base_name": self.data.get("base_name", "Unknown"),
"instructions": (
f"The base station '{self.data.get('base_name', 'Unknown')}' is offline.\n\n"
"To fix this:\n"
"1. Check that the base station is plugged in\n"
"2. Verify the LED is solid blue (not blinking)\n"
"3. Check your WiFi is working\n"
"4. Try unplugging for 10 seconds and reconnecting\n"
"5. If still offline, use TryFi app to reconfigure WiFi"
),
},
)
async def async_step_collar_issue(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle collar not responding."""
if user_input is not None:
ir.async_delete_issue(
self.hass, DOMAIN, f"{ISSUE_COLLAR_NOT_RESPONDING}_{self.data['pet_id']}"
)
return self.async_create_entry(title="", data={})
return self.async_show_form(
step_id="collar_issue",
description_placeholders={
"pet_name": self.data.get("pet_name", "Unknown"),
"instructions": (
f"{self.data.get('pet_name', 'Your pet')}'s collar is not responding.\n\n"
"Try these steps:\n"
"1. Ensure collar is charged (charge for 2 hours)\n"
"2. Check collar LED - should blink when pressed\n"
"3. Move pet near a base station\n"
"4. Restart collar: hold button for 10 seconds\n"
"5. If still not working, contact TryFi support"
),
},
)
async def async_step_rate_limited(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle API rate limiting."""
if user_input is not None:
# Update polling rate
new_rate = user_input.get("polling_rate", 30)
self.entry = self.hass.config_entries.async_get_entry(
self.data["entry_id"]
)
if self.entry:
self.hass.config_entries.async_update_entry(
self.entry,
options={**self.entry.options, "polling_rate": new_rate},
)
ir.async_delete_issue(
self.hass, DOMAIN, f"{ISSUE_API_RATE_LIMITED}_{self.data['entry_id']}"
)
return self.async_create_entry(title="", data={})
return self.async_show_form(
step_id="rate_limited",
data_schema=vol.Schema({
vol.Optional("polling_rate", default=30): vol.All(
vol.Coerce(int),
vol.Range(min=10, max=300),
),
}),
description_placeholders={
"current_rate": str(self.data.get("current_rate", 10)),
"instructions": (
"TryFi API is rate limiting requests.\n\n"
f"Current polling rate: {self.data.get('current_rate', 10)} seconds\n"
"Recommended: 30 seconds or higher\n\n"
"Increase the polling interval to reduce API calls."
),
},
)
async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, Any] | None = None,
) -> RepairsFlow:
"""Create a repair flow."""
return TryFiRepairFlow(issue_id, data or {})
2. Create Issue Detection
Update __init__.py to detect and create issues:
# Add to coordinator's _async_update_data method
async def _async_update_data(self) -> PyTryFi:
"""Fetch data from TryFi API."""
try:
await self.hass.async_add_executor_job(self.tryfi.update)
# Check for issues after successful update
await self._check_for_issues()
except InvalidAuthError as err:
# Create repair issue for auth failure
ir.async_create_issue(
self.hass,
DOMAIN,
f"{ISSUE_INVALID_AUTH}_{self.config_entry.entry_id}",
is_fixable=True,
severity=ir.IssueSeverity.ERROR,
translation_key="invalid_authentication",
data={"entry_id": self.config_entry.entry_id},
)
raise UpdateFailed("Invalid authentication") from err
async def _check_for_issues(self) -> None:
"""Check for issues that need repair."""
# Check for no pets
if not self.tryfi.pets:
ir.async_create_issue(
self.hass,
DOMAIN,
f"{ISSUE_NO_PETS_FOUND}_{self.config_entry.entry_id}",
is_fixable=True,
severity=ir.IssueSeverity.WARNING,
translation_key="no_pets_found",
data={"entry_id": self.config_entry.entry_id},
)
else:
# Clear if pets found
ir.async_delete_issue(
self.hass,
DOMAIN,
f"{ISSUE_NO_PETS_FOUND}_{self.config_entry.entry_id}",
)
# Check for offline bases
for base in self.tryfi.bases:
if not base.online:
ir.async_create_issue(
self.hass,
DOMAIN,
f"{ISSUE_BASE_OFFLINE}_{base.baseId}",
is_fixable=True,
severity=ir.IssueSeverity.WARNING,
translation_key="base_station_offline",
data={
"base_id": base.baseId,
"base_name": base.name,
},
)
else:
ir.async_delete_issue(
self.hass,
DOMAIN,
f"{ISSUE_BASE_OFFLINE}_{base.baseId}",
)
# Check for collar issues
for pet in self.tryfi.pets:
if pet.device and hasattr(pet.device, "connectionStateType"):
# Check if collar hasn't connected in 24 hours
last_connection = getattr(pet.device, "connectionStateDate", None)
if last_connection:
time_since = datetime.now() - last_connection
if time_since.days >= 1:
ir.async_create_issue(
self.hass,
DOMAIN,
f"{ISSUE_COLLAR_NOT_RESPONDING}_{pet.petId}",
is_fixable=True,
severity=ir.IssueSeverity.WARNING,
translation_key="collar_not_responding",
data={
"pet_id": pet.petId,
"pet_name": pet.name,
},
)
else:
ir.async_delete_issue(
self.hass,
DOMAIN,
f"{ISSUE_COLLAR_NOT_RESPONDING}_{pet.petId}",
)
3. Update strings.json for Repairs
Add repair translations:
{
"issues": {
"invalid_authentication": {
"title": "TryFi Authentication Failed",
"description": "Your TryFi login credentials are no longer valid. Please re-enter your credentials to fix this issue."
},
"no_pets_found": {
"title": "No Pets Found",
"description": "No pets with collars were found in your TryFi account. Please ensure your pets have collars assigned in the TryFi app."
},
"base_station_offline": {
"title": "Base Station Offline: {base_name}",
"description": "The base station '{base_name}' is offline and cannot communicate with collars."
},
"collar_not_responding": {
"title": "Collar Not Responding: {pet_name}",
"description": "{pet_name}'s collar hasn't connected in over 24 hours."
},
"api_rate_limited": {
"title": "TryFi API Rate Limited",
"description": "The TryFi API is rate limiting requests. Consider increasing the polling interval."
}
}
}
4. Register Repair Flow
Update __init__.py:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up TryFi from a config entry."""
# ... existing setup code ...
# Register repairs
await repairs.async_setup(hass)
return True
📝 Checklist
🧪 Testing
- Test each repair flow manually
- Verify issues appear in Settings → Repairs
- Test fix flows complete successfully
- Verify issues clear after fixes
- Test with various error conditions
- Check translations work properly
📚 Resources
⏱️ Estimated Time
- Implementation: 12 hours
- Testing: 4 hours
- Documentation: 2 hours
🏷️ Labels
enhancement, platinum-tier, repairs, user-experience
Implement Enhanced Error Handling with Repair Flows
🎯 Objective
Implement Home Assistant's repair flow system to provide actionable solutions for common TryFi integration issues. This will guide users through fixing problems without needing technical knowledge.
📋 Requirements
🔧 Implementation Plan
1. Create Repair Flow Handler
Create
repairs.py2. Create Issue Detection
Update
__init__.pyto detect and create issues:3. Update strings.json for Repairs
Add repair translations:
{ "issues": { "invalid_authentication": { "title": "TryFi Authentication Failed", "description": "Your TryFi login credentials are no longer valid. Please re-enter your credentials to fix this issue." }, "no_pets_found": { "title": "No Pets Found", "description": "No pets with collars were found in your TryFi account. Please ensure your pets have collars assigned in the TryFi app." }, "base_station_offline": { "title": "Base Station Offline: {base_name}", "description": "The base station '{base_name}' is offline and cannot communicate with collars." }, "collar_not_responding": { "title": "Collar Not Responding: {pet_name}", "description": "{pet_name}'s collar hasn't connected in over 24 hours." }, "api_rate_limited": { "title": "TryFi API Rate Limited", "description": "The TryFi API is rate limiting requests. Consider increasing the polling interval." } } }4. Register Repair Flow
Update
__init__.py:📝 Checklist
repairs.pywith repair flow handler🧪 Testing
📚 Resources
⏱️ Estimated Time
🏷️ Labels
enhancement,platinum-tier,repairs,user-experience