home assistant integration knowledge

- Integration code: ./homeassistant/components/<integration_domain>/

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "home assistant integration knowledge" with this command: npx skills add home-assistant/core/home-assistant-core-home-assistant-integration-knowledge

File Locations

  • Integration code: ./homeassistant/components/<integration_domain>/

  • Integration tests: ./tests/components/<integration_domain>/

Integration Templates

Standard Integration Structure

homeassistant/components/my_integration/ ├── 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 (if needed) ├── entity.py # Base entity class (if shared patterns) ├── sensor.py # Sensor platform ├── strings.json # User-facing text and translations ├── services.yaml # Service definitions (if applicable) └── quality_scale.yaml # Quality scale rule status

An integration can have platforms as needed (e.g., sensor.py , switch.py , etc.). The following platforms have extra guidelines:

  • Diagnostics: platform-diagnostics.md for diagnostic data collection

  • Repairs: platform-repairs.md for user-actionable repair issues

Minimal Integration Checklist

  • manifest.json with required fields (domain, name, codeowners, etc.)

  • init.py with async_setup_entry and async_unload_entry

  • config_flow.py with UI configuration support

  • const.py with DOMAIN constant

  • strings.json with at least config flow text

  • Platform files (sensor.py , etc.) as needed

  • quality_scale.yaml with rule status tracking

Integration Quality Scale

Home Assistant uses an Integration Quality Scale to ensure code quality and consistency. The quality level determines which rules apply:

Quality Scale Levels

  • Bronze: Basic requirements (ALL Bronze rules are mandatory)

  • Silver: Enhanced functionality

  • Gold: Advanced features

  • Platinum: Highest quality standards

Quality Scale Progression

  • Bronze → Silver: Add entity unavailability, parallel updates, auth flows

  • Silver → Gold: Add device management, diagnostics, translations

  • Gold → Platinum: Add strict typing, async dependencies, websession injection

How Rules Apply

  • Check manifest.json : Look for "quality_scale" key to determine integration level

  • Bronze Rules: Always required for any integration with quality scale

  • Higher Tier Rules: Only apply if integration targets that tier or higher

  • Rule Status: Check quality_scale.yaml in integration folder for:

  • done : Rule implemented

  • exempt : Rule doesn't apply (with reason in comment)

  • todo : Rule needs implementation

Example quality_scale.yaml Structure

rules:

Bronze (mandatory)

config-flow: done entity-unique-id: done action-setup: status: exempt comment: Integration does not register custom actions.

Silver (if targeting Silver+)

entity-unavailable: done parallel-updates: done

Gold (if targeting Gold+)

devices: done diagnostics: done

Platinum (if targeting Platinum)

strict-typing: done

When Reviewing/Creating Code: Always check the integration's quality scale level and exemption status before applying rules.

Code Organization

Core Locations

  • Shared constants: homeassistant/const.py (use these instead of hardcoding)

  • Integration structure:

  • homeassistant/components/{domain}/const.py

  • Constants

  • homeassistant/components/{domain}/models.py

  • Data models

  • homeassistant/components/{domain}/coordinator.py

  • Update coordinator

  • homeassistant/components/{domain}/config_flow.py

  • Configuration flow

  • homeassistant/components/{domain}/{platform}.py

  • Platform implementations

Common Modules

  • coordinator.py: Centralize data fetching logic class MyCoordinator(DataUpdateCoordinator[MyData]): def init(self, hass: HomeAssistant, client: MyClient, config_entry: ConfigEntry) -> None: super().init( hass, logger=LOGGER, name=DOMAIN, update_interval=timedelta(minutes=1), config_entry=config_entry, # ✅ Pass config_entry - it's accepted and recommended )

  • entity.py: Base entity definitions to reduce duplication class MyEntity(CoordinatorEntity[MyCoordinator]): _attr_has_entity_name = True

Runtime Data Storage

  • Use ConfigEntry.runtime_data: Store non-persistent runtime data type MyIntegrationConfigEntry = ConfigEntry[MyClient]

async def async_setup_entry(hass: HomeAssistant, entry: MyIntegrationConfigEntry) -> bool: client = MyClient(entry.data[CONF_HOST]) entry.runtime_data = client

Manifest Requirements

  • Required Fields: domain , name , codeowners , integration_type , documentation , requirements

  • Integration Types: device , hub , service , system , helper

  • IoT Class: Always specify connectivity method (e.g., cloud_polling , local_polling , local_push )

  • Discovery Methods: Add when applicable: zeroconf , dhcp , bluetooth , ssdp , usb

  • Dependencies: Include platform dependencies (e.g., application_credentials , bluetooth_adapters )

Config Flow Patterns

  • Version Control: Always set VERSION = 1 and MINOR_VERSION = 1

  • Unique ID Management: await self.async_set_unique_id(device_unique_id) self._abort_if_unique_id_configured()

  • Error Handling: Define errors in strings.json under config.error

  • Step Methods: Use standard naming (async_step_user , async_step_discovery , etc.)

Integration Ownership

  • manifest.json: Add GitHub usernames to codeowners : { "domain": "my_integration", "name": "My Integration", "codeowners": ["@me"] }

Async Dependencies (Platinum)

  • Requirement: All dependencies must use asyncio

  • Ensures efficient task handling without thread context switching

WebSession Injection (Platinum)

  • Pass WebSession: Support passing web sessions to dependencies async def async_setup_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool: """Set up integration from config entry.""" client = MyClient(entry.data[CONF_HOST], async_get_clientsession(hass))

  • For cookies: Use async_create_clientsession (aiohttp) or create_async_httpx_client (httpx)

Data Update Coordinator

  • Standard Pattern: Use for efficient data management class MyCoordinator(DataUpdateCoordinator): def init(self, hass: HomeAssistant, client: MyClient, config_entry: ConfigEntry) -> None: super().init( hass, logger=LOGGER, name=DOMAIN, update_interval=timedelta(minutes=5), config_entry=config_entry, # ✅ Pass config_entry - it's accepted and recommended ) self.client = client

    async def _async_update_data(self): try: return await self.client.fetch_data() except ApiError as err: raise UpdateFailed(f"API communication error: {err}")

  • Error Types: Use UpdateFailed for API errors, ConfigEntryAuthFailed for auth issues

  • Config Entry: Always pass config_entry parameter to coordinator - it's accepted and recommended

Integration Guidelines

Configuration Flow

  • UI Setup Required: All integrations must support configuration via UI

  • Manifest: Set "config_flow": true in manifest.json

  • Data Storage:

  • Connection-critical config: Store in ConfigEntry.data

  • Non-critical settings: Store in ConfigEntry.options

  • Validation: Always validate user input before creating entries

  • Config Entry Naming:

  • ❌ Do NOT allow users to set config entry names in config flows

  • Names are automatically generated or can be customized later in UI

  • ✅ Exception: Helper integrations MAY allow custom names in config flow

  • Connection Testing: Test device/service connection during config flow: try: await client.get_data() except MyException: errors["base"] = "cannot_connect"

  • Duplicate Prevention: Prevent duplicate configurations:

Using unique ID

await self.async_set_unique_id(identifier) self._abort_if_unique_id_configured()

Using unique data

self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})

Reauthentication Support

  • Required Method: Implement async_step_reauth in config flow

  • Credential Updates: Allow users to update credentials without re-adding

  • Validation: Verify account matches existing unique ID: await self.async_set_unique_id(user_id) self._abort_if_unique_id_mismatch(reason="wrong_account") return self.async_update_reload_and_abort( self._get_reauth_entry(), data_updates={CONF_API_TOKEN: user_input[CONF_API_TOKEN]} )

Reconfiguration Flow

  • Purpose: Allow configuration updates without removing device

  • Implementation: Add async_step_reconfigure method

  • Validation: Prevent changing underlying account with _abort_if_unique_id_mismatch

Device Discovery

  • Manifest Configuration: Add discovery method (zeroconf, dhcp, etc.) { "zeroconf": ["_mydevice._tcp.local."] }

  • Discovery Handler: Implement appropriate async_step_* method: async def async_step_zeroconf(self, discovery_info): """Handle zeroconf discovery.""" await self.async_set_unique_id(discovery_info.properties["serialno"]) self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host})

  • Network Updates: Use discovery to update dynamic IP addresses

Network Discovery Implementation

  • Zeroconf/mDNS: Use async instances aiozc = await zeroconf.async_get_async_instance(hass)

  • SSDP Discovery: Register callbacks with cleanup entry.async_on_unload( ssdp.async_register_callback( hass, _async_discovered_device, {"st": "urn:schemas-upnp-org:device:ZonePlayer:1"} ) )

Bluetooth Integration

  • Manifest Dependencies: Add bluetooth_adapters to dependencies

  • Connectable: Set "connectable": true for connection-required devices

  • Scanner Usage: Always use shared scanner instance scanner = bluetooth.async_get_scanner() entry.async_on_unload( bluetooth.async_register_callback( hass, _async_discovered_device, {"service_uuid": "example_uuid"}, bluetooth.BluetoothScanningMode.ACTIVE ) )

  • Connection Handling: Never reuse BleakClient instances, use 10+ second timeouts

Setup Validation

  • Test Before Setup: Verify integration can be set up in async_setup_entry

  • Exception Handling:

  • ConfigEntryNotReady : Device offline or temporary failure

  • ConfigEntryAuthFailed : Authentication issues

  • ConfigEntryError : Unresolvable setup problems

Config Entry Unloading

  • Required: Implement async_unload_entry for runtime removal/reload

  • Platform Unloading: Use hass.config_entries.async_unload_platforms

  • Cleanup: Register callbacks with entry.async_on_unload : async def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool: """Unload a config entry.""" if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): entry.runtime_data.listener() # Clean up resources return unload_ok

Service Actions

  • Registration: Register all service actions in async_setup , NOT in async_setup_entry

  • Validation: Check config entry existence and loaded state: async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: async def service_action(call: ServiceCall) -> ServiceResponse: if not (entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY_ID])): raise ServiceValidationError("Entry not found") if entry.state is not ConfigEntryState.LOADED: raise ServiceValidationError("Entry not loaded")

  • Exception Handling: Raise appropriate exceptions:

For invalid input

if end_date < start_date: raise ServiceValidationError("End date must be after start date")

For service errors

try: await client.set_schedule(start_date, end_date) except MyConnectionError as err: raise HomeAssistantError("Could not connect to the schedule") from err

Service Registration Patterns

  • Entity Services: Register on platform setup platform.async_register_entity_service( "my_entity_service", {vol.Required("parameter"): cv.string}, "handle_service_method" )

  • Service Schema: Always validate input SERVICE_SCHEMA = vol.Schema({ vol.Required("entity_id"): cv.entity_ids, vol.Required("parameter"): cv.string, vol.Optional("timeout", default=30): cv.positive_int, })

  • Services File: Create services.yaml with descriptions and field definitions

Polling

  • Use update coordinator pattern when possible

  • Polling intervals are NOT user-configurable: Never add scan_interval, update_interval, or polling frequency options to config flows or config entries

  • Integration determines intervals: Set update_interval programmatically based on integration logic, not user input

  • Minimum Intervals:

  • Local network: 5 seconds

  • Cloud services: 60 seconds

  • Parallel Updates: Specify number of concurrent updates: PARALLEL_UPDATES = 1 # Serialize updates to prevent overwhelming device

OR

PARALLEL_UPDATES = 0 # Unlimited (for coordinator-based or read-only)

Entity Development

Unique IDs

  • Required: Every entity must have a unique ID for registry tracking

  • Must be unique per platform (not per integration)

  • Don't include integration domain or platform in ID

  • Implementation: class MySensor(SensorEntity): def init(self, device_id: str) -> None: self._attr_unique_id = f"{device_id}_temperature"

Acceptable ID Sources:

  • Device serial numbers

  • MAC addresses (formatted using format_mac from device registry)

  • Physical identifiers (printed/EEPROM)

  • Config entry ID as last resort: f"{entry.entry_id}-battery"

Never Use:

  • IP addresses, hostnames, URLs

  • Device names

  • Email addresses, usernames

Entity Descriptions

  • Lambda/Anonymous Functions: Often used in EntityDescription for value transformation

  • Multiline Lambdas: When lambdas exceed line length, wrap in parentheses for readability

  • Bad pattern: SensorEntityDescription( key="temperature", name="Temperature", value_fn=lambda data: round(data["temp_value"] * 1.8 + 32, 1) if data.get("temp_value") is not None else None, # ❌ Too long )

  • Good pattern: SensorEntityDescription( key="temperature", name="Temperature", value_fn=lambda data: ( # ✅ Parenthesis on same line as lambda round(data["temp_value"] * 1.8 + 32, 1) if data.get("temp_value") is not None else None ), )

Entity Naming

  • Use has_entity_name: Set _attr_has_entity_name = True

  • For specific fields: class MySensor(SensorEntity): _attr_has_entity_name = True def init(self, device: Device, field: str) -> None: self._attr_device_info = DeviceInfo( identifiers={(DOMAIN, device.id)}, name=device.name, ) self._attr_name = field # e.g., "temperature", "humidity"

  • For device itself: Set _attr_name = None

Event Lifecycle Management

  • Subscribe in async_added_to_hass : async def async_added_to_hass(self) -> None: """Subscribe to events.""" self.async_on_remove( self.client.events.subscribe("my_event", self._handle_event) )

  • Unsubscribe in async_will_remove_from_hass if not using async_on_remove

  • Never subscribe in init or other methods

State Handling

  • Unknown values: Use None (not "unknown" or "unavailable")

  • Availability: Implement available() property instead of using "unavailable" state

Entity Availability

  • Mark Unavailable: When data cannot be fetched from device/service

  • Coordinator Pattern: @property def available(self) -> bool: """Return if entity is available.""" return super().available and self.identifier in self.coordinator.data

  • Direct Update Pattern: async def async_update(self) -> None: """Update entity.""" try: data = await self.client.get_data() except MyException: self._attr_available = False else: self._attr_available = True self._attr_native_value = data.value

Extra State Attributes

  • All attribute keys must always be present

  • Unknown values: Use None

  • Provide descriptive attributes

Device Management

Device Registry

  • Create Devices: Group related entities under devices

  • Device Info: Provide comprehensive metadata: _attr_device_info = DeviceInfo( connections={(CONNECTION_NETWORK_MAC, device.mac)}, identifiers={(DOMAIN, device.id)}, name=device.name, manufacturer="My Company", model="My Sensor", sw_version=device.version, )

  • For services: Add entry_type=DeviceEntryType.SERVICE

Dynamic Device Addition

  • Auto-detect New Devices: After initial setup

  • Implementation Pattern: def _check_device() -> None: current_devices = set(coordinator.data) new_devices = current_devices - known_devices if new_devices: known_devices.update(new_devices) async_add_entities([MySensor(coordinator, device_id) for device_id in new_devices])

entry.async_on_unload(coordinator.async_add_listener(_check_device))

Stale Device Removal

  • Auto-remove: When devices disappear from hub/account

  • Device Registry Update: device_registry.async_update_device( device_id=device.id, remove_config_entry_id=self.config_entry.entry_id, )

  • Manual Deletion: Implement async_remove_config_entry_device when needed

Entity Categories

  • Required: Assign appropriate category to entities

  • Implementation: Set _attr_entity_category

class MySensor(SensorEntity): _attr_entity_category = EntityCategory.DIAGNOSTIC

  • Categories include: DIAGNOSTIC for system/technical information

Device Classes

  • Use When Available: Set appropriate device class for entity type class MyTemperatureSensor(SensorEntity): _attr_device_class = SensorDeviceClass.TEMPERATURE

  • Provides context for: unit conversion, voice control, UI representation

Disabled by Default

  • Disable Noisy/Less Popular Entities: Reduce resource usage class MySignalStrengthSensor(SensorEntity): _attr_entity_registry_enabled_default = False

  • Target: frequently changing states, technical diagnostics

Entity Translations

  • Required with has_entity_name: Support international users

  • Implementation: class MySensor(SensorEntity): _attr_has_entity_name = True _attr_translation_key = "phase_voltage"

  • Create strings.json with translations: { "entity": { "sensor": { "phase_voltage": { "name": "Phase voltage" } } } }

Exception Translations (Gold)

  • Translatable Errors: Use translation keys for user-facing exceptions

  • Implementation: raise ServiceValidationError( translation_domain=DOMAIN, translation_key="end_date_before_start_date", )

  • Add to strings.json : { "exceptions": { "end_date_before_start_date": { "message": "The end date cannot be before the start date." } } }

Icon Translations (Gold)

  • Dynamic Icons: Support state and range-based icon selection

  • State-based Icons: { "entity": { "sensor": { "tree_pollen": { "default": "mdi:tree", "state": { "high": "mdi:tree-outline" } } } } }

  • Range-based Icons (for numeric values): { "entity": { "sensor": { "battery_level": { "default": "mdi:battery-unknown", "range": { "0": "mdi:battery-outline", "90": "mdi:battery-90", "100": "mdi:battery" } } } } }

Testing Requirements

  • Location: tests/components/{domain}/

  • Coverage Requirement: Above 95% test coverage for all modules

  • Best Practices:

  • Use pytest fixtures from tests.common

  • Mock all external dependencies

  • Use snapshots for complex data structures

  • Follow existing test patterns

Config Flow Testing

  • 100% Coverage Required: All config flow paths must be tested

  • Test Scenarios:

  • All flow initiation methods (user, discovery, import)

  • Successful configuration paths

  • Error recovery scenarios

  • Prevention of duplicate entries

  • Flow completion after errors

Testing

  • Integration-specific tests (recommended): pytest ./tests/components/<integration_domain>
    --cov=homeassistant.components.<integration_domain>
    --cov-report term-missing
    --durations-min=1
    --durations=0
    --numprocesses=auto

Testing Best Practices

  • Never access hass.data directly - Use fixtures and proper integration setup instead

  • Use snapshot testing - For verifying entity states and attributes

  • Test through integration setup - Don't test entities in isolation

  • Mock external APIs - Use fixtures with realistic JSON data

  • Verify registries - Ensure entities are properly registered with devices

Config Flow Testing Template

async def test_user_flow_success(hass, mock_api): """Test successful user flow.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) assert result["type"] == FlowResultType.FORM assert result["step_id"] == "user"

# Test form submission
result = await hass.config_entries.flow.async_configure(
    result["flow_id"], user_input=TEST_USER_INPUT
)
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "My Device"
assert result["data"] == TEST_USER_INPUT

async def test_flow_connection_error(hass, mock_api_error): """Test connection error handling.""" result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) result = await hass.config_entries.flow.async_configure( result["flow_id"], user_input=TEST_USER_INPUT ) assert result["type"] == FlowResultType.FORM assert result["errors"] == {"base": "cannot_connect"}

Entity Testing Patterns

@pytest.fixture def platforms() -> list[Platform]: """Overridden fixture to specify platforms to test.""" return [Platform.SENSOR] # Or another specific platform as needed.

@pytest.mark.usefixtures("entity_registry_enabled_by_default", "init_integration") async def test_entities( hass: HomeAssistant, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry, device_registry: dr.DeviceRegistry, mock_config_entry: MockConfigEntry, ) -> None: """Test the sensor entities.""" await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

# Ensure entities are correctly assigned to device
device_entry = device_registry.async_get_device(
    identifiers={(DOMAIN, "device_unique_id")}
)
assert device_entry
entity_entries = er.async_entries_for_config_entry(
    entity_registry, mock_config_entry.entry_id
)
for entity_entry in entity_entries:
    assert entity_entry.device_id == device_entry.id

Mock Patterns

Modern integration fixture setup

@pytest.fixture def mock_config_entry() -> MockConfigEntry: """Return the default mocked config entry.""" return MockConfigEntry( title="My Integration", domain=DOMAIN, data={CONF_HOST: "127.0.0.1", CONF_API_KEY: "test_key"}, unique_id="device_unique_id", )

@pytest.fixture def mock_device_api() -> Generator[MagicMock]: """Return a mocked device API.""" with patch("homeassistant.components.my_integration.MyDeviceAPI", autospec=True) as api_mock: api = api_mock.return_value api.get_data.return_value = MyDeviceData.from_json( load_fixture("device_data.json", DOMAIN) ) yield api

@pytest.fixture def platforms() -> list[Platform]: """Fixture to specify platforms to test.""" return PLATFORMS

@pytest.fixture async def init_integration( hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_device_api: MagicMock, platforms: list[Platform], ) -> MockConfigEntry: """Set up the integration for testing.""" mock_config_entry.add_to_hass(hass)

with patch("homeassistant.components.my_integration.PLATFORMS", platforms):
    await hass.config_entries.async_setup(mock_config_entry.entry_id)
    await hass.async_block_till_done()

return mock_config_entry

Debugging & Troubleshooting

Common Issues & Solutions

  • Integration won't load: Check manifest.json syntax and required fields

  • Entities not appearing: Verify unique_id and has_entity_name implementation

  • Config flow errors: Check strings.json entries and error handling

  • Discovery not working: Verify manifest discovery configuration and callbacks

  • Tests failing: Check mock setup and async context

Debug Logging Setup

Enable debug logging in tests

caplog.set_level(logging.DEBUG, logger="my_integration")

In integration code - use proper logging

_LOGGER = logging.getLogger(name) _LOGGER.debug("Processing data: %s", data) # Use lazy logging

Validation Commands

Check specific integration

python -m script.hassfest --integration-path homeassistant/components/my_integration

Validate quality scale

Check quality_scale.yaml against current rules

Run integration tests with coverage

pytest ./tests/components/my_integration
--cov=homeassistant.components.my_integration
--cov-report term-missing

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

core

No summary provided by upstream source.

Repository SourceNeeds Review
General

home-assistant

No summary provided by upstream source.

Repository SourceNeeds Review
General

mf-integrate

No summary provided by upstream source.

Repository SourceNeeds Review
General

core

No summary provided by upstream source.

Repository SourceNeeds Review