xiaohongshu
xiaohongshu
ha-integration
Develop custom Home Assistant integrations, config flows, entities, and platforms. Use when working with manifest.json, custom components, config_flow.py, entity base classes, or device registry. Activates on keywords: integration, custom component, config flow, entity, platform, manifest.json, d...
Full skill instructions
Create professional-grade custom Home Assistant integrations with complete config flows and entity implementations.
This skill prevents 8 common integration errors and saves ~40% implementation time.
| Metric | Without Skill | With Skill |
|---|---|---|
| Setup Time | 45 minutes | 12 minutes |
| Common Errors | 8 | 0 |
| Config Flow Issues | 5+ | 0 |
| Entity Registration Bugs | 4+ | 0 |
{
"domain": "my_integration",
"name": "My Integration",
"codeowners": ["@username"],
"config_flow": true,
"documentation": "https://github.com/username/ha-my-integration",
"requirements": [],
"version": "0.0.1"
}
Why this matters: The manifest.json defines integration metadata, declares dependencies, and enables config flow UI in Home Assistant.
import asyncio
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .coordinator import MyDataUpdateCoordinator
DOMAIN = "my_integration"
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the integration from config entry."""
hass.data.setdefault(DOMAIN, {})
# Create coordinator
coordinator = MyDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator
# Forward setup to platforms
await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload the integration."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, ["sensor"])
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
Why this matters: Proper async initialization ensures Home Assistant waits for data loading and platform setup completes before continuing.
from typing import Any, Dict, Optional
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigEntry
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from .const import DOMAIN
class MyIntegrationConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle config flow for my_integration."""
async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
"""Handle user initiation of config flow."""
errors = {}
if user_input is not None:
# Validate user input
try:
# Validate connection or API call
pass
except Exception as exc:
errors["base"] = "invalid_auth"
if not errors:
# Create unique entry
await self.async_set_unique_id(user_input.get("host"))
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_input.get("name"),
data=user_input
)
# Show form
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
vol.Required("name"): str,
vol.Required("host"): str,
}),
errors=errors
)
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry):
"""Return options flow for this integration."""
return MyIntegrationOptionsFlow(config_entry)
Why this matters: Config flows provide user-friendly setup UI and validate input before creating config entries.
❌ Wrong:
# Synchronous network call - blocks event loop
import requests
data = requests.get("https://api.example.com/data").json()
# No unique_id - duplicate entities on restart
class MySensor(SensorEntity):
pass
# Missing await
coordinator.async_refresh()
✅ Correct:
# Async network call - doesn't block
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com/data") as resp:
data = await resp.json()
# Proper unique_id generation
class MySensor(SensorEntity):
@property
def unique_id(self) -> str:
return f"{self.coordinator.data['id']}_sensor"
# Proper await
await coordinator.async_request_refresh()
Why: Synchronous calls block Home Assistant's event loop, causing UI freezes. Missing unique_id causes entity duplicates. Missing await means code continues before async operation completes.
| Issue | Root Cause | Solution |
|---|---|---|
| Duplicate entities on restart | No unique_id set | Implement unique_id property with stable identifier |
| Config flow validation fails silently | Missing error handling in async_step_user | Wrap validation in try/except, set errors dict |
| Entity state doesn't update | Coordinator not refreshing or entity not subscribed | Use @callback decorator for update listeners |
| Device not appearing | Missing device_info or device_identifier mismatch | Set device_info with identifiers matching registry |
| UI freezes during setup | Synchronous network calls in async_setup_entry | Use aiohttp for all async network operations |
| Platform imports fail | Importing platform files in init.py | Let Home Assistant handle via async_forward_entry_setups |
{
"domain": "integration_name",
"name": "Integration Display Name",
"codeowners": ["@github_username"],
"config_flow": true,
"documentation": "https://github.com/username/repo",
"homeassistant": "2024.1.0",
"requirements": ["requests>=2.25.0"],
"version": "1.0.0",
"issue_tracker": "https://github.com/username/repo/issues"
}
Key settings:
domain: Unique identifier (alphanumeric, underscores, lowercase)config_flow: Set to true to enable config UIrequirements: List of PyPI packages needed (e.g., ["requests>=2.25.0"])homeassistant: Minimum Home Assistant version requiredvol.Schema({
vol.Required("host"): vol.All(str, vol.Length(min=5)),
vol.Required("port", default=8080): int,
vol.Optional("api_key"): str,
})
async def async_step_reauth(self, user_input: Dict[str, Any] | None = None) -> FlowResult:
"""Handle reauth upon an API authentication error."""
config_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
if user_input is not None:
config_entry.data = {**config_entry.data, **user_input}
self.hass.config_entries.async_update_entry(config_entry)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth",
data_schema=vol.Schema({vol.Required("api_key"): str})
)
from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.const import UnitOfTemperature
class TemperatureSensor(SensorEntity):
"""Temperature sensor entity."""
_attr_device_class = "temperature"
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
def __init__(self, coordinator, idx):
"""Initialize sensor."""
self.coordinator = coordinator
self._idx = idx
@property
def unique_id(self) -> str:
"""Return unique ID."""
return f"{self.coordinator.data['id']}_temp_{self._idx}"
@property
def device_info(self) -> DeviceInfo:
"""Return device information."""
return DeviceInfo(
identifiers={(DOMAIN, self.coordinator.data['id'])},
name=self.coordinator.data['name'],
manufacturer="My Company",
)
@property
def native_value(self) -> float | None:
"""Return sensor value."""
try:
return float(self.coordinator.data['temperature'])
except (KeyError, TypeError):
return None
async def async_added_to_hass(self) -> None:
"""Connect to coordinator when added."""
await super().async_added_to_hass()
self.async_on_remove(
self.coordinator.async_add_listener(self._handle_coordinator_update)
)
@callback
def _handle_coordinator_update(self) -> None:
"""Update when coordinator updates."""
self.async_write_ha_state()
from homeassistant.components.binary_sensor import BinarySensorEntity, BinarySensorDeviceClass
class MotionSensor(BinarySensorEntity):
"""Motion detection sensor."""
_attr_device_class = BinarySensorDeviceClass.MOTION
@property
def is_on(self) -> bool | None:
"""Return True if motion detected."""
return self.coordinator.data.get('motion', False)
from datetime import timedelta
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator,
UpdateFailed,
)
import logging
_LOGGER = logging.getLogger(__name__)
class MyDataUpdateCoordinator(DataUpdateCoordinator):
"""Coordinator for fetching data."""
def __init__(self, hass, entry):
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
name="My Integration",
update_interval=timedelta(minutes=5),
)
self.entry = entry
async def _async_update_data(self):
"""Fetch data from API."""
try:
async with aiohttp.ClientSession() as session:
async with session.get(
f"https://api.example.com/data",
headers={"Authorization": f"Bearer {self.entry.data['api_key']}"}
) as resp:
if resp.status == 401:
raise ConfigEntryAuthFailed("Invalid API key")
return await resp.json()
except asyncio.TimeoutError as err:
raise UpdateFailed("API timeout") from err
except Exception as err:
raise UpdateFailed(f"API error: {err}") from err
from homeassistant.helpers.device_registry import DeviceInfo
device_info = DeviceInfo(
identifiers={(DOMAIN, "device_unique_id")},
name="Device Name",
manufacturer="Manufacturer",
model="Model Name",
sw_version="1.0.0",
via_device=(DOMAIN, "parent_device_id"), # For child devices
)
device_info = DeviceInfo(
identifiers={(DOMAIN, device_id)},
serial_number="SERIAL123",
connections={(dr.CONNECTION_NETWORK_MAC, "aa:bb:cc:dd:ee:ff")},
)
class MyIntegration:
def __init__(self, hass: HomeAssistant, entry: ConfigEntry):
self.hass = hass
self.entry = entry
self.api_key = entry.data.get("api_key")
self.host = entry.data.get("host")
async def async_step_init(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
"""Manage integration options."""
if user_input is not None:
return self.async_create_entry(
title="",
data=user_input
)
current_options = self.config_entry.options
return self.async_show_form(
step_id="init",
data_schema=vol.Schema({
vol.Optional("refresh_rate", default=current_options.get("refresh_rate", 5)): int,
})
)
Located in references/:
manifest-reference.md - Complete manifest.json field referenceentity-base-classes.md - Entity implementation base classes and propertiesconfig-flow-patterns.md - Advanced config flow patterns and validationLocated in assets/:
manifest.json - Starter manifest.json templateconfig_flow.py - Basic config flow boilerplate__init__.py - Component initialization templatecoordinator.py - DataUpdateCoordinator templateNote: For deep dives on specific topics, see the reference files above.
| Package | Version | Purpose |
|---|---|---|
| homeassistant | >=2024.1.0 | Home Assistant core |
| voluptuous | >=0.13.0 | Config validation schemas |
| Package | Version | Purpose |
|---|---|---|
| aiohttp | >=3.8.0 | Async HTTP requests (for API integrations) |
| pyyaml | >=5.4 | YAML parsing (for config file integrations) |
Symptoms: Same sensor/switch/light appears 2+ times in Home Assistant after reboot
Solution:
# Add unique_id property to entity class
@property
def unique_id(self) -> str:
return f"{self.coordinator.data['id']}_{self.platform}_{self._attr_name}"
Symptoms: Form hangs when submitting, no error displayed
Solution:
# Ensure all async operations are awaited and errors caught
async def async_step_user(self, user_input=None):
errors = {}
if user_input is not None:
try:
await self._validate_input(user_input) # ← Add await
except Exception as e:
errors["base"] = "validation_error" # ← Set error
if not errors:
return self.async_create_entry(...)
Symptoms: All entities turn unavailable after coordinator update
Solution:
# Handle coordinator errors gracefully
async def _async_update_data(self):
try:
return await self.api.fetch_data()
except Exception as err:
raise UpdateFailed(f"Error: {err}") from err # ← Raises UpdateFailed, not Exception
Symptoms: Device created but not visible in Home Assistant devices
Solution:
# Ensure device_info is returned by ALL entities for the device
@property
def device_info(self) -> DeviceInfo:
return DeviceInfo(
identifiers={(DOMAIN, self.coordinator.data['id'])}, # ← Must be consistent
name=self.coordinator.data['name'],
manufacturer="Manufacturer",
)
Before implementing a new integration, verify:
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience