| name | homeassistant-integration |
| description | Home Assistant custom integration development patterns, best practices, and conventions. Use when creating or modifying entities, config flows, coordinators, or any Home Assistant integration code. |
Home Assistant Custom Integration Development
This skill provides knowledge for developing Home Assistant custom integrations following best practices.
Project Structure
custom_components/homevolt_local/
โโโ __init__.py # Entry point with async_setup_entry
โโโ manifest.json # Integration metadata and dependencies
โโโ const.py # Domain and constants
โโโ config_flow.py # UI configuration flow
โโโ coordinator.py # Data update coordinator
โโโ models.py # Data models
โโโ sensor.py # Sensor platform
โโโ strings.json # User-facing text and translations
โโโ services.yaml # Service definitions (if applicable)
โโโ translations/ # Language translations
tests/ # Integration tests (at repo root)
โโโ conftest.py # Shared pytest fixtures
โโโ test_*.py # Test files
Core Patterns
Entity Development
class MySensor(CoordinatorEntity, SensorEntity):
"""Sensor entity using coordinator pattern."""
_attr_has_entity_name = True
def __init__(self, coordinator: MyCoordinator) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self._attr_unique_id = f"{coordinator.config_entry.entry_id}_sensor_name"
self._attr_translation_key = "sensor_name"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
name="Device Name",
manufacturer="Manufacturer",
model="Model",
)
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.coordinator.data.some_value
Coordinator Pattern
class MyCoordinator(DataUpdateCoordinator[MyData]):
"""Data update coordinator."""
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=30),
)
self.config_entry = config_entry
async def _async_update_data(self) -> MyData:
"""Fetch data from API."""
try:
return await self._fetch_data()
except ApiConnectionError as err:
raise UpdateFailed(f"Connection error: {err}") from err
except ApiAuthError as err:
raise ConfigEntryAuthFailed(f"Auth error: {err}") from err
Config Flow
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
else:
await self.async_set_unique_id(info["unique_id"])
self._abort_if_unique_id_configured()
return self.async_create_entry(title=info["title"], data=user_input)
return self.async_show_form(
step_id="user",
data_schema=SCHEMA,
errors=errors,
)
Sensor Classifications
Power Sensors
_attr_device_class = SensorDeviceClass.POWER
_attr_native_unit_of_measurement = UnitOfPower.WATT
_attr_state_class = SensorStateClass.MEASUREMENT
_attr_suggested_display_precision = 0
Energy Sensors (Total Increasing)
_attr_device_class = SensorDeviceClass.ENERGY
_attr_native_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
_attr_suggested_display_precision = 2
Battery Percentage
_attr_device_class = SensorDeviceClass.BATTERY
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = SensorStateClass.MEASUREMENT
Common Anti-Patterns to Avoid
data = requests.get(url)
data = await self.api.get_data()
self._attr_name = "Temperature"
Correct Patterns
data = await hass.async_add_executor_job(requests.get, url)
try:
data = await self.api.get_data()
except ApiException as err:
raise UpdateFailed(f"API error: {err}") from err
_attr_translation_key = "temperature"
self._attr_unique_id = f"{entry_id}_temperature"
Testing Patterns
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title="Test Device",
domain=DOMAIN,
data={CONF_HOST: "192.168.1.100"},
unique_id="device_unique_id",
)
async def test_sensor_value(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test sensor returns correct value."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("sensor.device_power")
assert state is not None
assert state.state == "1500"
Error Handling
| Exception | When to Use |
|---|
ConfigEntryNotReady | Temporary setup failure (network issue) |
ConfigEntryAuthFailed | Authentication/authorization failure |
UpdateFailed | Coordinator update error |
Resources