一键导入
homeassistant-quality-and-testing
High-level Home Assistant integration best practices, quality scale cues, and testing/CI expectations for custom components
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
High-level Home Assistant integration best practices, quality scale cues, and testing/CI expectations for custom components
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
How to create, update, and manage Changesets for release preparation in this repository
Guidance for using correct energy and power metrics in Home Assistant energy features
Patterns for Home Assistant config flows, discovery handlers, options flows, and reauth for custom components
Project-specific patterns for the Marstek integration (config flow, coordinator, scanner, entities, translations)
Practical test/CI playbook for Home Assistant custom components (config flow, coordinator, entities, diagnostics)
Repo-specific guidance for maintaining the Marstek integration (UDP Open API, polling, discovery/scanner) in a Home Assistant-friendly way
| name | homeassistant-quality-and-testing |
| description | High-level Home Assistant integration best practices, quality scale cues, and testing/CI expectations for custom components |
Use this skill to align changes with Home Assistant best practices and the Integration Quality Scale expectations.
hass.async_add_executor_job.DataUpdateCoordinator; entities consume coordinator.data.requirements) for raw API/UDP/HTTP handling.from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import UpdateFailed
async def _async_update_data(self):
try:
return await self.api.fetch_data()
except AuthError as err:
# Triggers reauth flow automatically
raise ConfigEntryAuthFailed("Authentication failed") from err
except RateLimitError:
# Backoff with retry_after (seconds until retry)
raise UpdateFailed(retry_after=60)
except ConnectionError as err:
raise UpdateFailed(f"Connection failed: {err}")
_async_setup() for one-time initialization during first refreshalways_update=False if your data supports __eq__ comparison (avoids unnecessary entity updates)async_contexts() to track which entities are actively listeningconfig_entry to coordinator constructor for automatic linkingconfig_flow.py with selectors where helpful.entry.add_update_listener to reload on options change.entry.async_start_reauth; ask only for the changed credential and update the existing entry.unique_id early and call _abort_if_unique_id_configured() for user/discovery steps._attr_has_entity_name = True is MANDATORY for new integrations; entity name should be capability-only (device name is prepended by HA)._attr_name = None to use only the device name.device_info with stable identifiers (prefer MAC/serial over IP); all entities for a device must share the same identifiers set.unique_id stable and predictable (e.g., {ble_mac}_{key}); changing it breaks history and customizations.unavailable noise._attr_* class/instance attributes pattern for cleaner code:
class MySensor(SensorEntity):
_attr_has_entity_name = True
_attr_device_class = SensorDeviceClass.POWER
_attr_native_unit_of_measurement = UnitOfPower.WATT
For multiple similar entities, use EntityDescription for declarative definitions:
@dataclass(kw_only=True)
class MySensorEntityDescription(SensorEntityDescription):
value_fn: Callable[[DeviceData], StateType]
exists_fn: Callable[[DeviceData], bool] = lambda _: True
SENSORS: tuple[MySensorEntityDescription, ...] = (
MySensorEntityDescription(
key="power",
device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.WATT,
value_fn=lambda data: data.power,
),
)
homeassistant.const for units:
UnitOfEnergy.WATT_HOUR not "Wh"UnitOfPower.WATT not "W"UnitOfTemperature.CELSIUS not "°C"homeassistant.components.sensor:
SensorDeviceClass.BATTERY for battery level sensorsSensorDeviceClass.POWER for power sensorsSensorDeviceClass.ENERGY for energy sensorsSensorDeviceClass.ENERGY_STORAGE for stored energy (battery capacity in Wh)SensorDeviceClass.TEMPERATURE for temperature sensorsSensorStateClass.MEASUREMENT for instantaneous values (power, temperature)SensorStateClass.TOTAL for values that can increase/decrease (net energy)SensorStateClass.TOTAL_INCREASING for cumulative counters that only increaseEntityCategory.DIAGNOSTIC for non-primary sensors (WiFi RSSI, temperatures)entity_registry_enabled_default = False for diagnostic or rarely-used sensorssuggested_display_precision to control decimal places shown in UI_unrecorded_attributes frozenset to exclude high-frequency attributes from recorderUse RestoreSensor (not RestoreEntity) to restore sensor state after restart:
from homeassistant.components.sensor import RestoreSensor
class MyEnergySensor(RestoreSensor):
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
if (last := await self.async_get_last_sensor_data()):
self._attr_native_value = last.native_value
diagnostics.py with async_get_config_entry_diagnostics functionTO_REDACT list pattern for consistency:
TO_REDACT = {"host", "ip", "mac", "ble_mac", "wifi_mac", "wifi_name", "SSID", "bleMac"}
def _redact_dict(data: dict) -> dict:
return {k: "**REDACTED**" if k in TO_REDACT else v for k, v in data.items()}
Integrations working toward Bronze/Silver/Gold should maintain a quality_scale.yaml:
rules:
config_flow: done
test_before_setup: done
unique_config_entry: done
diagnostics:
status: done
comment: Added in v0.2.0
reauthentication-flow:
status: exempt
comment: Device has no authentication
Statuses: done, todo, exempt (with comment explaining why).
pymarstek==x.y.z) to avoid breaking upgrades.version, config_flow: true, iot_class, and codeowners; keep documentation and issue tracker URLs current.hacs.json if distribution via HACS.strings.json; mirror to translations/en.json.cannot_connect, invalid_auth, already_configured).{ip_address}) to keep translations flexible.pytest with pytest-homeassistant-custom-component; pin test deps in requirements_test.txt.tests/ mirrors component files (test_config_flow.py, test_init.py, test_sensor.py, etc.); put shared fixtures in tests/conftest.py (enable custom integrations).UpdateFailed and surface unavailable states; gate entities on data keys.syrupy HA extension for diagnostics/device registry dumps; redact sensitive fields.--cov-fail-under=95); test latest supported Python versions.After every code change, run both checks before considering the work complete:
# 1. Type checking (strict mode)
python3 -m mypy --strict custom_components/<domain>/
# 2. Tests with coverage
pytest tests/ -q --cov=custom_components/<domain> --cov-fail-under=95
Both must pass. Fix any errors and re-run until clean.
coordinator.async_request_refresh().