Home Assistant integration development

SkillDev tools

Home Assistant integration patterns — coordinator usage, entity unique IDs and naming, device registry, exception types, diagnostics redaction, and setup and unload. Use when working anywhere under custom_components/haeo/ outside the standalone core package.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Home Assistant integration development skill

What this skill tells your AI

The instructions your AI receives, as published by hass-energy/haeo in .agents/skills/integration/SKILL.md and read by ahel’s review.

Coordinator pattern

HAEO uses DataUpdateCoordinator for optimization scheduling. The coordinator loads data from sensors, runs optimization, and exposes results.

  • Pass config_entry to coordinator constructor
  • Use UpdateFailed for data loading or optimization errors
  • Integration determines update interval (not user-configurable)

Entity development

Unique IDs

Every entity must have a unique ID constructed from stable identifiers:

self._attr_unique_id = f"{entry.entry_id}-{element_id}-power"

Acceptable sources: config entry ID, subentry ID, device serial numbers. Never use: IP addresses, hostnames, user-provided names.

Entity naming

Use translation keys for all entity names:

class MySensor(SensorEntity):
    _attr_has_entity_name = True
    _attr_translation_key = "battery_power"

State handling

  • Use None for unknown values (not "unknown" string)
  • Implement available property for availability

Event lifecycle

async def async_added_to_hass(self) -> None:
    """Subscribe to events."""
    self.async_on_remove(self.coordinator.async_add_listener(self._handle_update))

Device registry

Group related entities under devices using translation keys:

_attr_device_info = DeviceInfo(
    identifiers={(DOMAIN, device_id)},
    translation_key="battery",  # Use translation key for device name
)

Exception handling

All exceptions that may reach the user must use Home Assistant exception types with translations. Never raise generic exceptions that would show "unknown error" in the UI.

  • ConfigEntryNotReady: Device offline or temporary failure
  • ConfigEntryError: Unresolvable setup problems
  • UpdateFailed: Data loading or optimization errors
  • HomeAssistantError: User-facing errors with translation support

For service calls and user actions, use HomeAssistantError with a translation key:

raise HomeAssistantError(
    translation_domain=DOMAIN,
    translation_key="optimization_failed",
)

Diagnostics

Implement diagnostic data collection with redaction:

TO_REDACT = [CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE]


async def async_get_config_entry_diagnostics(hass: HomeAssistant, entry: ConfigEntry) -> dict[str, Any]:
    return async_redact_data(entry.data, TO_REDACT)

Never expose passwords, tokens, or sensitive coordinates.

Setup and unload

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Set up from config entry."""
    coordinator = MyCoordinator(hass, entry)
    await coordinator.async_config_entry_first_refresh()
    entry.runtime_data = coordinator
    await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
    return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """Unload config entry."""
    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

Signals

GitHub stars
65
Forks
21
Last commit
Aug 2026
Advanced
Catalog kind
skill
Gateway key
integration-hass-energy
Source
github.com/hass-energy/haeo