mcpField
H
ha-integration

ha-integration

github.com/nodnarbnitram開発1672 回インストール27 週間前に更新
リポジトリ

ha-integration について

ha-integrationは、Home Assistantのカスタム統合開発向けスキルです。設定フローやエンティティの実装に使えるパターン、資料、テンプレートを提供し、初期化や非同期処理、登録で起こりやすい問題への対処を支援します。

主な機能

  1. 01統合のマニフェストと初期化ガイド
  2. 02設定フローの検証パターン
  3. 03エンティティとデバイス登録のパターン
  4. 04DataUpdateCoordinatorの例
  5. 05テンプレートと参考資料

活用シーン

  1. 01カスタム統合のひな型作成
  2. 02設定フローの実装と検証
  3. 03センサーエンティティとデバイスの登録
  4. 04非同期更新とエラーの処理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/nodnarbnitram/claude-code-extensions --skill ha-integration

インストール後、Claude Code で/ha-integrationを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

README.md
assets/
__init__.py
config_flow.py
const.py
coordinator.py
manifest.json
sensor.py
references/
config-flow-patterns.md
entity-base-classes.md
manifest-reference.md

詳細

Home Assistant インテグレーション開発

完全な設定フローとエンティティ実装を備えた、プロフェッショナル品質のカスタム Home Assistant インテグレーションを作成します。

⚠️ 始める前に

このスキルは、よくあるインテグレーションのエラーを8つ防ぎ、実装時間を約40%短縮します。

指標スキルなしスキルあり
セットアップ時間45分12分
よくあるエラー80
設定フローの問題5件以上0
エンティティ登録のバグ4件以上0
このスキルで防げる既知の問題
  1. manifest.json の依存関係の不足 - 必要な Home Assistant コンポーネントの宣言忘れ
  2. async/await の問題 - コーディネーターの更新やエンティティの初期化を適切に待機しない
  3. エンティティの状態クラスの不一致 - センサープラットフォームで誤った STATE_CLASS(measurement と total の使い分け)を使用する
  4. 設定フローのスキーマエラー - 無効な vol.Schema 定義による検証の失敗
  5. デバイス情報の未関連付け - 適切なデバイスレジストリ接続なしでエンティティを作成する
  6. コーディネーターのエラー - データ更新の失敗を適切に処理しない
  7. プラットフォームのインポートタイミング - コンポーネントの初期化前にプラットフォームファイルを読み込む
  8. 一意の ID 生成の不足 - 再起動後に重複するエンティティを作成する

クイックスタート

ステップ1:manifest.json を作成する
{
  "domain": "my_integration",
  "name": "My Integration",
  "codeowners": ["@username"],
  "config_flow": true,
  "documentation": "https://github.com/username/ha-my-integration",
  "requirements": [],
  "version": "0.0.1"
}

重要な理由: manifest.json はインテグレーションのメタデータを定義し、依存関係を宣言し、Home Assistant で設定フロー UI を利用できるようにします。

ステップ2:非同期セットアップを行う init.py を作成する
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

重要な理由: 適切な非同期初期化により、Home Assistant はデータの読み込みとプラットフォームのセットアップが完了してから処理を続行します。

ステップ3:検証機能を備えた config_flow.py を作成する
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)

重要な理由: 設定フローは、ユーザーフレンドリーなセットアップ UI を提供し、設定エントリを作成する前に入力を検証します。

重要なルール

✅ 必ず行うこと
  • ✅ 全体を通して async/await を使用する(async_setup_entry、async_added_to_hass、async_update_data)
  • ✅ 各エンティティに一意の unique_id を生成する(再起動時の重複を防止)
  • ✅ device_info プロパティを使ってエンティティをデバイスに関連付ける
  • ✅ コーディネーターの更新失敗を適切に処理する(ログを記録し、利用不可にする)
  • ✅ 外部依存関係をすべて manifest.json の requirements に宣言する
  • ✅ IDE のサポート向上と Home Assistant の要件への準拠のため、型ヒントを使用する
  • ✅ コーディネーターパターン(DataUpdateCoordinator)を使ってエンティティを登録する
❌ 絶対に行わないこと
  • ❌ 同期ネットワーク呼び出し(requests ライブラリ)を使用する — aiohttp を使用する
  • ❌ コンポーネントレベルでプラットフォームファイルをインポートする — Home Assistant にセットアップの転送を任せる
  • ❌ unique_id なしでエンティティを作成する — 再起動時に重複が発生する
  • ❌ コーディネーターの更新失敗を無視する — エンティティを利用不可にする
  • ❌ API エンドポイントをハードコードする — 設定フローを使って保存する
  • ❌ 複数デバイスのインテグレーションを実装する際に device_info を忘れる
  • ❌ STATE_CLASS を誤って使用する(measurement、total、total_increasing の使い分け)
よくある間違い

❌ 誤り:

# 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()

✅ 正しい例:

# 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()

理由: 同期呼び出しは Home Assistant のイベントループをブロックし、UI のフリーズを引き起こします。unique_id がないとエンティティが重複します。await がないと、非同期処理の完了前にコードが続行されます。

既知の問題の予防

問題根本原因解決策
再起動後にエンティティが重複するunique_id が設定されていない安定した識別子を持つ unique_id プロパティを実装します
設定フローの検証がエラーなく失敗するasync_step_user にエラーハンドリングがない検証を try/except で囲み、errors 辞書を設定します
エンティティの状態が更新されないコーディネーターが更新されていない、またはエンティティが購読していない更新リスナーに @callback デコレーターを使用します
デバイスが表示されないdevice_info がない、または device_identifier が一致しないレジストリの識別子と一致する識別子を持つ device_info を設定します
セットアップ中に UI がフリーズするasync_setup_entry 内で同期ネットワーク呼び出しを実行しているすべての非同期ネットワーク操作に aiohttp を使用します
プラットフォームのインポートに失敗するinit.py でプラットフォームファイルをインポートしているHome Assistant が async_forward_entry_setups 経由で処理するようにします

マニフェスト設定のリファレンス

manifest.json
{
  "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"
}

主な設定:

  • domain:一意の識別子(英数字、アンダースコア、小文字)
  • config_flow:設定 UI を有効にするには true に設定します
  • requirements:必要な PyPI パッケージの一覧(例:["requests>=2.25.0"])
  • homeassistant:必要な Home Assistant の最小バージョン

設定フローのパターン

検証に vol.All を使用するスキーマ
vol.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)

DataUpdateCoordinator のパターン

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,
        })
    )

同梱リソース

リファレンス

references/ にあります:

テンプレート

assets/ にあります:

  • manifest.json - manifest.json のスターターテンプレート
  • config_flow.py - 基本的な設定フローのボイラープレート
  • __init__.py - コンポーネント初期化テンプレート
  • coordinator.py - DataUpdateCoordinator のテンプレート

注: 特定のトピックについて詳しく知りたい場合は、上記のリファレンスファイルを参照してください。

依存関係

必須
パッケージバージョン用途
homeassistant>=2024.1.0Home Assistant コア
voluptuous>=0.13.0設定検証スキーマ
任意
パッケージバージョン用途
aiohttp>=3.8.0非同期 HTTP リクエスト(API インテグレーション用)
pyyaml>=5.4YAML 解析(設定ファイルインテグレーション用)

公式ドキュメント

トラブルシューティング

再起動後にエンティティが複数回表示される

症状: 再起動後、同じセンサー/スイッチ/ライトが Home Assistant に 2 回以上表示される

解決策:

# Add unique_id property to entity class
@property
def unique_id(self) -> str:
    return f"{self.coordinator.data['id']}_{self.platform}_{self._attr_name}"
設定フローの検証が完了しない

症状: 送信時にフォームが停止し、エラーが表示されない

解決策:

# 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(...)
更新後にエンティティが利用不可と表示される

症状: コーディネーターの更新後、すべてのエンティティが利用不可になる

解決策:

# 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
デバイスレジストリにデバイスが表示されない

症状: デバイスは作成されていますが、Home Assistant のデバイスに表示されません

解決策:

# 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",
    )

セットアップチェックリスト

新しいインテグレーションを実装する前に、次の項目を確認してください。

  • ドメイン名が一意で、小文字とアンダースコアを使う規則に従っている
  • ドメイン、名前、コードオーナーを含む manifest.json が作成されている
  • 設定フローまたは手動設定方法が実装されている
  • すべての async 関数で適切に await している
  • すべてのエンティティに一意の ID が生成されている(重複を防止)
  • 複数デバイスのインテグレーションの場合、デバイス情報が関連付けられている
  • DataUpdateCoordinator または同等のポーリングパターンを使用している
  • UpdateFailed 例外を使ってエラー処理を行っている
  • すべての関数シグネチャに型ヒントがある
  • 設定フローの検証テストが作成されている
  • manifest のドキュメント URL が有効な場所を指している

コメント