Files
tencent_face_recognition/__init__.py
T
Hermes 2f19148ff1 feat: 添加 HA 管理面板功能
- config_flow.py: 新增人员管理步骤(async_step_manage/list/create)
  支持在 HA 配置 -> 选项 中查看人员列表、创建和删除
- __init__.py: 新增 3 个 WebSocket API 命令(list/create/delete)
  前端可通过 HA WebSocket 直接调用
- blueprints/face_detection_automation.yaml: 自动化蓝图
  支持人脸检测/搜索 + 通知推送
- manifest.json: 版本号 2.2.0

用户现可通过 HA 界面对人员进行可视化管理
2026-07-07 07:27:19 +08:00

230 lines
8.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
腾讯云人脸识别 Home Assistant 插件
"""
from __future__ import annotations
import base64
import logging
from typing import Any, Dict
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.components import websocket_api
from homeassistant.components.camera import async_get_image
from .const import (
CONF_PERSON_GROUP_ID,
CONF_REGION,
CONF_SECRET_ID,
CONF_SECRET_KEY,
DOMAIN,
PLATFORMS,
get_entry_value,
)
from .errors import AuthenticationError, NetworkError
from .face_recognition import FaceRecognition
from .services import async_setup_services, async_unload_services
from .tencent_cloud_client import TencentCloudClient
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""设置配置项。"""
_LOGGER.info("设置腾讯云人脸识别插件: %s", entry.entry_id)
secret_id = entry.data.get(CONF_SECRET_ID)
secret_key = entry.data.get(CONF_SECRET_KEY)
region = get_entry_value(entry, CONF_REGION, "ap-shanghai")
person_group_id = get_entry_value(entry, CONF_PERSON_GROUP_ID, "Hass")
# 1. 创建客户端
try:
client = TencentCloudClient(secret_id, secret_key, region)
except Exception as ex: # noqa: BLE001
_LOGGER.error("创建腾讯云客户端失败: %s", ex)
raise ConfigEntryNotReady(f"创建腾讯云客户端失败: {ex}") from ex
# 2. 验证凭据(失败则让 HA 稍后重试 setup)
try:
await hass.async_add_executor_job(client.verify_credentials)
except (AuthenticationError, NetworkError) as ex:
_LOGGER.error("腾讯云凭据验证失败: %s", ex)
await hass.async_add_executor_job(client.close)
raise ConfigEntryNotReady(f"腾讯云凭据验证失败: {ex}") from ex
except Exception as ex: # noqa: BLE001
_LOGGER.warning("腾讯云凭据验证异常(将继续加载): %s", ex)
# 3. 构建核心功能与存储
face_recognition = FaceRecognition(hass, client)
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
"entry": entry,
"client": client,
"face_recognition": face_recognition,
"secret_id": secret_id,
"region": region,
"person_group_id": person_group_id,
}
# 4. 注册服务
await async_setup_services(hass)
# 5. 注册 WebSocket API 命令
websocket_api.async_register_command(hass, ws_list_persons)
websocket_api.async_register_command(hass, ws_create_person)
websocket_api.async_register_command(hass, ws_delete_person)
# 6. 前向设置平台(sensor
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
# 6. 配置项更新时自动重新加载平台
entry.async_on_unload(entry.add_update_listener(_async_update_listener))
_LOGGER.info("腾讯云人脸识别插件设置完成")
return True
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""配置项选项变更时重新加载。"""
_LOGGER.debug("配置项变更,重新加载: %s", entry.entry_id)
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""卸载配置项。"""
_LOGGER.info("卸载腾讯云人脸识别插件: %s", entry.entry_id)
# 使用统一的卸载 API(兼容新版 HA)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
entry_data: Dict[str, Any] = hass.data.get(DOMAIN, {}).pop(entry.entry_id, None)
if entry_data and "client" in entry_data:
client: TencentCloudClient = entry_data["client"]
try:
await hass.async_add_executor_job(client.close)
except Exception as ex: # noqa: BLE001
_LOGGER.warning("关闭腾讯云客户端时出错: %s", ex)
# 若已无该域的配置项,则卸载服务
if not hass.data.get(DOMAIN):
await async_unload_services(hass)
return unload_ok
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""移除配置项。"""
_LOGGER.info("移除腾讯云人脸识别插件配置: %s", entry.entry_id)
# 客户端资源已在 unload 时释放;这里仅做兜底清理
data = hass.data.get(DOMAIN, {}).pop(entry.entry_id, None)
if data and "client" in data:
try:
await hass.async_add_executor_job(data["client"].close)
except Exception: # noqa: BLE001
pass
# --- WebSocket API ---------------------------------------------------------
@websocket_api.websocket_command({
"type": "tencent_face_recognition/list_persons",
})
@websocket_api.async_response
async def ws_list_persons(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: Dict[str, Any]
) -> None:
"""列出所有人员。"""
entry_data = None
for data in hass.data.get(DOMAIN, {}).values():
if isinstance(data, dict) and "client" in data:
entry_data = data
break
if not entry_data:
connection.send_error(msg["id"], "not_found", "未找到配置")
return
client: TencentCloudClient = entry_data["client"]
group_id = entry_data.get("person_group_id", "Hass")
try:
result = await hass.async_add_executor_job(client.get_person_list_all, group_id)
connection.send_result(msg["id"], result)
except Exception as ex:
_LOGGER.error("列出人员失败: %s", ex)
connection.send_error(msg["id"], "api_error", str(ex))
@websocket_api.websocket_command({
"type": "tencent_face_recognition/create_person",
"data": {
"person_id": str,
"person_name": str,
"camera_entity_id": str,
},
})
@websocket_api.async_response
async def ws_create_person(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: Dict[str, Any]
) -> None:
"""通过摄像头创建人员。"""
entry_data = None
for data in hass.data.get(DOMAIN, {}).values():
if isinstance(data, dict) and "client" in data:
entry_data = data
break
if not entry_data:
connection.send_error(msg["id"], "not_found", "未找到配置")
return
client: TencentCloudClient = entry_data["client"]
group_id = entry_data.get("person_group_id", "Hass")
person_id = msg["data"]["person_id"]
person_name = msg["data"]["person_name"]
camera_entity_id = msg["data"]["camera_entity_id"]
try:
image = await async_get_image(hass, camera_entity_id)
image_base64 = base64.b64encode(image.content).decode("utf-8")
result = await hass.async_add_executor_job(
client.create_person,
person_id, person_name, group_id,
None, None, None, image_base64,
)
connection.send_result(msg["id"], result)
except Exception as ex:
_LOGGER.error("创建人员失败: %s", ex)
connection.send_error(msg["id"], "api_error", str(ex))
@websocket_api.websocket_command({
"type": "tencent_face_recognition/delete_person",
"data": {"person_id": str},
})
@websocket_api.async_response
async def ws_delete_person(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: Dict[str, Any]
) -> None:
"""删除人员。"""
entry_data = None
for data in hass.data.get(DOMAIN, {}).values():
if isinstance(data, dict) and "client" in data:
entry_data = data
break
if not entry_data:
connection.send_error(msg["id"], "not_found", "未找到配置")
return
client: TencentCloudClient = entry_data["client"]
person_id = msg["data"]["person_id"]
try:
result = await hass.async_add_executor_job(client.delete_person, person_id)
connection.send_result(msg["id"], result)
except Exception as ex:
_LOGGER.error("删除人员失败: %s", ex)
connection.send_error(msg["id"], "api_error", str(ex))