AgentSkillsCN

ha-entity-platforms

创建 Home Assistant 实体平台(传感器、二进制传感器、开关、灯、遮阳帘、气候控制等)。当用户要求创建实体、添加传感器平台、实现开关功能,或处理任何 Home Assistant 实体类型时,可使用本技能。此外,当用户提及“SensorEntity”、“SwitchEntity”、“实体平台”、“native_value”、“device_class”,或具体平台名称时,本技能同样适用。

SKILL.md
--- frontmatter
name: ha-entity-platforms
description: Create Home Assistant entity platforms (sensor, binary_sensor, switch, light, cover, climate, etc.). Use when the user asks to create entities, add a sensor platform, implement a switch, or work with any Home Assistant entity type. Also triggers on mentions of "SensorEntity", "SwitchEntity", "entity platform", "native_value", "device_class", or specific platform names.

Home Assistant Entity Platforms

Entity platforms are the files that define what entities your integration exposes (sensors, switches, lights, etc.). Each platform file follows a consistent pattern.

Platform Setup Pattern

Every platform file implements async_setup_entry:

python
"""Sensor platform for {Name}."""
from __future__ import annotations

from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import {Name}ConfigEntry
from .coordinator import {Name}Coordinator


async def async_setup_entry(
    hass: HomeAssistant,
    entry: {Name}ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up sensors from a config entry."""
    coordinator = entry.runtime_data

    entities = []
    for device_id in coordinator.get_device_ids():
        for description in SENSOR_DESCRIPTIONS:
            entities.append({Name}Sensor(coordinator, description, device_id))

    async_add_entities(entities)

Entity Description Pattern (Recommended)

Use EntityDescription dataclasses for clean, declarative entity definitions:

python
from dataclasses import dataclass
from collections.abc import Callable
from typing import Any

from homeassistant.components.sensor import (
    SensorDeviceClass,
    SensorEntity,
    SensorEntityDescription,
    SensorStateClass,
)
from homeassistant.const import PERCENTAGE, UnitOfTemperature


@dataclass(frozen=True, kw_only=True)
class {Name}SensorDescription(SensorEntityDescription):
    """Describe a {Name} sensor."""
    value_fn: Callable[[dict[str, Any]], Any]


SENSOR_DESCRIPTIONS: tuple[{Name}SensorDescription, ...] = (
    {Name}SensorDescription(
        key="temperature",
        translation_key="temperature",
        device_class=SensorDeviceClass.TEMPERATURE,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=UnitOfTemperature.CELSIUS,
        value_fn=lambda data: data.get("temperature"),
    ),
    {Name}SensorDescription(
        key="humidity",
        translation_key="humidity",
        device_class=SensorDeviceClass.HUMIDITY,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PERCENTAGE,
        value_fn=lambda data: data.get("humidity"),
    ),
    {Name}SensorDescription(
        key="battery",
        translation_key="battery",
        device_class=SensorDeviceClass.BATTERY,
        state_class=SensorStateClass.MEASUREMENT,
        native_unit_of_measurement=PERCENTAGE,
        value_fn=lambda data: data.get("battery_level"),
    ),
)

Base Entity Class

Create a shared base class to avoid duplication across platforms:

python
"""Base entity for {Name}."""
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import DOMAIN
from .coordinator import {Name}Coordinator


class {Name}Entity(CoordinatorEntity[{Name}Coordinator]):
    """Base class for {Name} entities."""

    _attr_has_entity_name = True

    def __init__(self, coordinator: {Name}Coordinator, device_id: str) -> None:
        """Initialize."""
        super().__init__(coordinator)
        self._device_id = device_id

    @property
    def device_info(self) -> DeviceInfo:
        """Return device info to group entities under one device."""
        device = self.coordinator.get_device_data(self._device_id)
        return DeviceInfo(
            identifiers={(DOMAIN, self._device_id)},
            name=device.get("name", self._device_id) if device else self._device_id,
            manufacturer="Manufacturer",
            model=device.get("model") if device else None,
            sw_version=device.get("firmware") if device else None,
        )

    @property
    def available(self) -> bool:
        """Available when coordinator is connected and device data exists."""
        return super().available and self.coordinator.get_device_data(self._device_id) is not None

Sensor Entity

python
class {Name}Sensor({Name}Entity, SensorEntity):
    """Sensor entity."""

    entity_description: {Name}SensorDescription

    def __init__(
        self,
        coordinator: {Name}Coordinator,
        description: {Name}SensorDescription,
        device_id: str,
    ) -> None:
        super().__init__(coordinator, device_id)
        self.entity_description = description
        self._attr_unique_id = f"{DOMAIN}_{device_id}_{description.key}"

    @property
    def native_value(self) -> float | str | None:
        device_data = self.coordinator.get_device_data(self._device_id)
        if device_data is None:
            return None
        return self.entity_description.value_fn(device_data)

Binary Sensor Entity

python
from homeassistant.components.binary_sensor import (
    BinarySensorDeviceClass,
    BinarySensorEntity,
    BinarySensorEntityDescription,
)

BINARY_SENSOR_DESCRIPTIONS = (
    BinarySensorEntityDescription(
        key="motion",
        translation_key="motion",
        device_class=BinarySensorDeviceClass.MOTION,
    ),
    BinarySensorEntityDescription(
        key="door",
        translation_key="door",
        device_class=BinarySensorDeviceClass.DOOR,
    ),
)

class {Name}BinarySensor({Name}Entity, BinarySensorEntity):
    """Binary sensor entity."""

    def __init__(self, coordinator, description, device_id):
        super().__init__(coordinator, device_id)
        self.entity_description = description
        self._attr_unique_id = f"{DOMAIN}_{device_id}_{description.key}"

    @property
    def is_on(self) -> bool | None:
        device_data = self.coordinator.get_device_data(self._device_id)
        if device_data is None:
            return None
        return device_data.get(self.entity_description.key)

Switch Entity

Switches require control methods. Request a coordinator refresh after commands:

python
from homeassistant.components.switch import SwitchDeviceClass, SwitchEntity

class {Name}Switch({Name}Entity, SwitchEntity):
    """Switch entity."""

    _attr_device_class = SwitchDeviceClass.SWITCH

    def __init__(self, coordinator, key, device_id):
        super().__init__(coordinator, device_id)
        self._key = key
        self._attr_unique_id = f"{DOMAIN}_{device_id}_{key}"
        self._attr_translation_key = key

    @property
    def is_on(self) -> bool | None:
        device_data = self.coordinator.get_device_data(self._device_id)
        if device_data is None:
            return None
        return device_data.get(self._key)

    async def async_turn_on(self, **kwargs) -> None:
        """Turn the switch on."""
        await self.coordinator.client.async_set_switch(self._device_id, self._key, True)
        await self.coordinator.async_request_refresh()

    async def async_turn_off(self, **kwargs) -> None:
        """Turn the switch off."""
        await self.coordinator.client.async_set_switch(self._device_id, self._key, False)
        await self.coordinator.async_request_refresh()

Entity Platform Quick Reference

PlatformBase ClassKey PropertyDevice Classes
sensorSensorEntitynative_valueTEMPERATURE, HUMIDITY, POWER, ENERGY, BATTERY, ...
binary_sensorBinarySensorEntityis_onMOTION, DOOR, WINDOW, SMOKE, MOISTURE, ...
switchSwitchEntityis_on + turn_on/offSWITCH, OUTLET
lightLightEntityis_on + brightness
coverCoverEntityis_closed + open/close/stopBLIND, GARAGE, SHUTTER, ...
climateClimateEntityhvac_mode + temperature
buttonButtonEntityasync_press()RESTART, UPDATE, IDENTIFY
numberNumberEntitynative_value + set_value
selectSelectEntitycurrent_option + select_option
lockLockEntityis_locked + lock/unlock
updateUpdateEntityinstalled_version

Additional Resources

For a complete reference of all device classes, state classes, and binary sensor device classes, see reference/device-classes.md.

Critical Rules

  1. Always set _attr_has_entity_name = True — entity names are derived from device name + entity name
  2. Always provide unique_id — must be stable across restarts
  3. Always provide device_info — groups entities under a device in the UI
  4. Use translation_key for entity names, not hardcoded _attr_name
  5. Add state_class to numeric sensors (MEASUREMENT, TOTAL, TOTAL_INCREASING) for statistics
  6. Use native_unit_of_measurement instead of unit_of_measurement
  7. Call async_request_refresh() after commands to get updated state