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:
"""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:
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:
"""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
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
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:
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
| Platform | Base Class | Key Property | Device Classes |
|---|---|---|---|
sensor | SensorEntity | native_value | TEMPERATURE, HUMIDITY, POWER, ENERGY, BATTERY, ... |
binary_sensor | BinarySensorEntity | is_on | MOTION, DOOR, WINDOW, SMOKE, MOISTURE, ... |
switch | SwitchEntity | is_on + turn_on/off | SWITCH, OUTLET |
light | LightEntity | is_on + brightness | — |
cover | CoverEntity | is_closed + open/close/stop | BLIND, GARAGE, SHUTTER, ... |
climate | ClimateEntity | hvac_mode + temperature | — |
button | ButtonEntity | async_press() | RESTART, UPDATE, IDENTIFY |
number | NumberEntity | native_value + set_value | — |
select | SelectEntity | current_option + select_option | — |
lock | LockEntity | is_locked + lock/unlock | — |
update | UpdateEntity | installed_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
- •Always set
_attr_has_entity_name = True— entity names are derived from device name + entity name - •Always provide
unique_id— must be stable across restarts - •Always provide
device_info— groups entities under a device in the UI - •Use
translation_keyfor entity names, not hardcoded_attr_name - •Add
state_classto numeric sensors (MEASUREMENT, TOTAL, TOTAL_INCREASING) for statistics - •Use
native_unit_of_measurementinstead ofunit_of_measurement - •Call
async_request_refresh()after commands to get updated state