Entity Platforms
Entity platforms are the core building blocks that represent controllable or observable elements in Home Assistant. This guide covers implementing different entity types for your integration.
Platforms are separate Python modules within your integration, one for each entity type you support:
homeassistant/components/your_integration/
├── __init__.py
├── manifest.json
├── light.py # Light platform
├── sensor.py # Sensor platform
├── switch.py # Switch platform
└── climate.py # Climate platform
From homeassistant.const.Platform:
light - Lights
switch - Switches
sensor - Sensors
binary_sensor - Binary sensors
climate - Climate devices (thermostats)
cover - Covers (blinds, garage doors)
fan - Fans
lock - Locks
media_player - Media players
camera - Cameras
vacuum - Vacuum cleaners
water_heater - Water heaters
And many more…
Every platform follows the same pattern:
"""Platform implementation."""
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up the platform from a config entry."""
# Get data from main integration
# Create entities
# Add entities to Home Assistant
pass
Entity Base Class
All entities inherit from Entity (from homeassistant/helpers/entity.py):
from homeassistant.helpers.entity import Entity
class MyEntity ( Entity ):
"""Representation of my entity."""
_attr_has_entity_name = True
_attr_name = None
def __init__ ( self ) -> None :
"""Initialize the entity."""
self ._attr_unique_id = "unique_id_here"
Using _attr_* attributes is the modern approach. They’re automatically exposed as properties.
Basic Light Implementation
"""Light platform for My Integration."""
from __future__ import annotations
from typing import Any
from homeassistant.components.light import (
ATTR_BRIGHTNESS ,
ColorMode,
LightEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up light platform."""
# Get stored data
data = hass.data[ DOMAIN ][entry.entry_id]
# Create light entities
async_add_entities([
MyLight( "Living Room" , "light_1" ),
MyLight( "Bedroom" , "light_2" ),
])
class MyLight ( LightEntity ):
"""Representation of a Light."""
_attr_has_entity_name = True
_attr_color_mode = ColorMode. BRIGHTNESS
_attr_supported_color_modes = {ColorMode. BRIGHTNESS }
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the light."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_is_on = False
self ._attr_brightness = 255
async def async_turn_on ( self , ** kwargs : Any) -> None :
"""Turn the light on."""
if ATTR_BRIGHTNESS in kwargs:
self ._attr_brightness = kwargs[ ATTR_BRIGHTNESS ]
self ._attr_is_on = True
self .async_write_ha_state()
async def async_turn_off ( self , ** kwargs : Any) -> None :
"""Turn the light off."""
self ._attr_is_on = False
self .async_write_ha_state()
Color Support
For RGB/RGBW lights:
from homeassistant.components.light import (
ATTR_HS_COLOR ,
ATTR_RGBW_COLOR ,
ColorMode,
LightEntity,
)
class MyRGBLight ( LightEntity ):
"""RGB light."""
_attr_color_mode = ColorMode. HS
_attr_supported_color_modes = {ColorMode. HS }
def __init__ ( self ) -> None :
"""Initialize."""
self ._attr_hs_color = ( 0 , 0 )
async def async_turn_on ( self , ** kwargs : Any) -> None :
"""Turn on."""
if ATTR_HS_COLOR in kwargs:
self ._attr_hs_color = kwargs[ ATTR_HS_COLOR ]
self ._attr_is_on = True
self .async_write_ha_state()
Real example from Demo integration (homeassistant/components/demo/light.py:94):
class DemoLight ( LightEntity ):
"""Representation of a demo light."""
_attr_has_entity_name = True
_attr_name = None
_attr_should_poll = False
Basic Sensor
"""Sensor platform for My Integration."""
from __future__ import annotations
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up sensor platform."""
async_add_entities([
MyTemperatureSensor( "Living Room" , "temp_1" ),
MyHumiditySensor( "Living Room" , "humidity_1" ),
])
class MyTemperatureSensor ( SensorEntity ):
"""Temperature sensor."""
_attr_has_entity_name = True
_attr_device_class = SensorDeviceClass. TEMPERATURE
_attr_state_class = SensorStateClass. MEASUREMENT
_attr_native_unit_of_measurement = UnitOfTemperature. CELSIUS
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the sensor."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_native_value = None
async def async_update ( self ) -> None :
"""Fetch new state data."""
# Fetch from API
self ._attr_native_value = 22.5
Device Class : Indicates the type of sensor (temperature, humidity, etc.)State Class : For statistics and long-term data (measurement, total, total_increasing)Native Value : The sensor’s value in its native unit
Statistics and Units
class MyEnergySensor ( SensorEntity ):
"""Energy sensor with statistics."""
_attr_device_class = SensorDeviceClass. ENERGY
_attr_state_class = SensorStateClass. TOTAL_INCREASING
_attr_native_unit_of_measurement = UnitOfEnergy. KILO_WATT_HOUR
_attr_suggested_display_precision = 2
Home Assistant automatically handles:
Unit conversion
Statistics collection
Long-term storage
Graphing
"""Switch platform for My Integration."""
from __future__ import annotations
from typing import Any
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up switch platform."""
async_add_entities([
MySwitch( "Outlet 1" , "switch_1" ),
])
class MySwitch ( SwitchEntity ):
"""Representation of a Switch."""
_attr_has_entity_name = True
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the switch."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_is_on = False
async def async_turn_on ( self , ** kwargs : Any) -> None :
"""Turn the switch on."""
self ._attr_is_on = True
self .async_write_ha_state()
async def async_turn_off ( self , ** kwargs : Any) -> None :
"""Turn the switch off."""
self ._attr_is_on = False
self .async_write_ha_state()
"""Binary sensor platform for My Integration."""
from __future__ import annotations
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up binary sensor platform."""
async_add_entities([
MyMotionSensor( "Living Room" , "motion_1" ),
MyDoorSensor( "Front Door" , "door_1" ),
])
class MyMotionSensor ( BinarySensorEntity ):
"""Motion sensor."""
_attr_has_entity_name = True
_attr_device_class = BinarySensorDeviceClass. MOTION
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the sensor."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_is_on = False
async def async_update ( self ) -> None :
"""Update the sensor."""
# Fetch from API
self ._attr_is_on = False # No motion
class MyDoorSensor ( BinarySensorEntity ):
"""Door sensor."""
_attr_has_entity_name = True
_attr_device_class = BinarySensorDeviceClass. DOOR
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the sensor."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_is_on = False # Closed
"""Climate platform for My Integration."""
from __future__ import annotations
from typing import Any
from homeassistant.components.climate import (
ATTR_TEMPERATURE ,
ClimateEntity,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE , UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import DOMAIN
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up climate platform."""
async_add_entities([
MyThermostat( "Living Room" , "thermostat_1" ),
])
class MyThermostat ( ClimateEntity ):
"""Thermostat entity."""
_attr_has_entity_name = True
_attr_temperature_unit = UnitOfTemperature. CELSIUS
_attr_hvac_modes = [HVACMode. OFF , HVACMode. HEAT , HVACMode. COOL , HVACMode. AUTO ]
_attr_supported_features = (
ClimateEntityFeature. TARGET_TEMPERATURE
| ClimateEntityFeature. TURN_ON
| ClimateEntityFeature. TURN_OFF
)
def __init__ ( self , name : str , unique_id : str ) -> None :
"""Initialize the thermostat."""
self ._attr_name = name
self ._attr_unique_id = unique_id
self ._attr_hvac_mode = HVACMode. OFF
self ._attr_current_temperature = 20.0
self ._attr_target_temperature = 22.0
async def async_set_temperature ( self , ** kwargs : Any) -> None :
"""Set new target temperature."""
if (temperature := kwargs.get( ATTR_TEMPERATURE )) is not None :
self ._attr_target_temperature = temperature
self .async_write_ha_state()
async def async_set_hvac_mode ( self , hvac_mode : HVACMode) -> None :
"""Set new target hvac mode."""
self ._attr_hvac_mode = hvac_mode
self .async_write_ha_state()
Entity Features
Unique ID
Every entity should have a unique ID:
def __init__ ( self ) -> None :
"""Initialize."""
self ._attr_unique_id = f " { device_id } _ { entity_type } "
Unique IDs must be stable across restarts and unique across all integrations.
Device Info
Group entities under devices:
from homeassistant.helpers.device_registry import DeviceInfo
class MyEntity ( SensorEntity ):
"""Entity with device info."""
def __init__ ( self ) -> None :
"""Initialize."""
self ._attr_device_info = DeviceInfo(
identifiers = {( DOMAIN , "device_id" )},
name = "My Device" ,
manufacturer = "My Company" ,
model = "Model X" ,
sw_version = "1.0.0" ,
)
Availability
Indicate when entities are unavailable:
async def async_update ( self ) -> None :
"""Update the entity."""
try :
data = await self .api.get_data()
self ._attr_native_value = data
self ._attr_available = True
except ConnectionError :
self ._attr_available = False
Entity Category
Categorize entities:
from homeassistant.const import EntityCategory
class MyDiagnosticSensor ( SensorEntity ):
"""Diagnostic sensor."""
_attr_entity_category = EntityCategory. DIAGNOSTIC
Categories:
CONFIG - Configuration entities
DIAGNOSTIC - Diagnostic information
None - Regular entities (default)
State Updates
For entities that receive push updates:
class MyPushEntity ( SensorEntity ):
"""Entity with push updates."""
_attr_should_poll = False
async def async_added_to_hass ( self ) -> None :
"""Register callbacks."""
self .api.register_callback( self ._handle_update)
def _handle_update ( self , data : Any) -> None :
"""Handle push update."""
self ._attr_native_value = data
self .async_write_ha_state()
For entities that need polling:
class MyPollingEntity ( SensorEntity ):
"""Entity with polling."""
_attr_should_poll = True
async def async_update ( self ) -> None :
"""Poll for updates."""
self ._attr_native_value = await self .api.get_value()
From homeassistant/helpers/entity_platform.py:96:
class EntityPlatformModule ( Protocol ):
"""Protocol type for entity platform modules."""
async def async_setup_platform (
self ,
hass : HomeAssistant,
config : ConfigType,
async_add_entities : AddEntitiesCallback,
discovery_info : DiscoveryInfoType | None = None ,
) -> None :
"""Set up an integration platform async."""
async def async_setup_entry (
self ,
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up an integration platform from a config entry."""
Use async_setup_entry for modern config-entry-based integrations. Use async_setup_platform only for legacy YAML-based platforms.
Best Practices
Use Async Methods
# Good
async def async_turn_on ( self ) -> None :
await self .api.turn_on()
# Bad
def turn_on ( self ) -> None :
self .api.turn_on() # Blocking!
Batch Entity Creation
async def async_setup_entry (
hass : HomeAssistant,
entry : ConfigEntry,
async_add_entities : AddConfigEntryEntitiesCallback,
) -> None :
"""Set up platform."""
# Good - add all at once
entities = [
MyEntity(device)
for device in await api.get_devices()
]
async_add_entities(entities)
# Bad - multiple calls
for device in await api.get_devices():
async_add_entities([MyEntity(device)])
Handle Missing Data
@ property
def native_value ( self ) -> float | None :
"""Return the value."""
if self ._value is None :
return None
return float ( self ._value)
Next Steps
Integration Overview Understand the full integration architecture
Config Flow Add UI configuration to your integration