合入小程序修改,新增告警功能

This commit is contained in:
HycJack
2026-05-09 09:46:06 +08:00
parent 46cc5272f3
commit be4d276557
10 changed files with 800 additions and 28 deletions

View File

@@ -7,6 +7,8 @@ from fastapi import HTTPException
from banban.dao.device import DeviceDAO
from banban.service.device_setting import device_setting_service
from services.device_update_manager import device_firmware_update_manager
from services.system_config_manager import system_config_manager
class DeviceService(DatabaseServiceBase):
@@ -115,6 +117,101 @@ class DeviceService(DatabaseServiceBase):
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
return await service.send_remote_sleep_wake_command(device_id, switch)
def _compare_versions(self, current_version: str | None, latest_version: str | None) -> bool:
current = (current_version or "").strip()
latest = (latest_version or "").strip()
if not latest:
return False
if not current or current in {"unknown", "0.0.0"}:
return True
try:
current_parts = [int(part) for part in current.split(".")]
latest_parts = [int(part) for part in latest.split(".")]
except ValueError:
return current != latest
max_len = max(len(current_parts), len(latest_parts))
current_parts.extend([0] * (max_len - len(current_parts)))
latest_parts.extend([0] * (max_len - len(latest_parts)))
return latest_parts > current_parts
async def get_firmware_status(
self,
*,
device_id: str,
user_id: int,
) -> dict[str, Any]:
status_row = await self.get_device_status(device_id=device_id, user_id=user_id)
current_version = status_row.get("version")
latest_version_config = await system_config_manager.get_config("latest_firmware_version")
firmware_url_config = await system_config_manager.get_config("update_firmware_url")
latest_version = latest_version_config.config_value if latest_version_config else None
firmware_url = firmware_url_config.config_value if firmware_url_config else None
update_available = self._compare_versions(current_version, latest_version)
update_record = await device_firmware_update_manager.get_firmware_update_dict(device_id)
update_status = update_record.get("update_status") if update_record else "idle"
progress = update_record.get("progress") if update_record else 0.0
target_version = update_record.get("firmware_version") if update_record else None
updated_at = update_record.get("updated_at") if update_record else None
return {
"device_id": device_id,
"current_version": current_version,
"latest_version": latest_version,
"update_available": update_available,
"update_status": update_status or "idle",
"progress": float(progress or 0.0),
"target_version": target_version,
"updated_at": updated_at,
"can_update": bool(update_available and latest_version and firmware_url),
}
async def start_firmware_update(
self,
*,
device_id: str,
user_id: int,
) -> dict[str, Any]:
firmware_status = await self.get_firmware_status(device_id=device_id, user_id=user_id)
if not firmware_status.get("update_available"):
raise HTTPException(status_code=409, detail="device firmware is already up to date")
latest_version = firmware_status.get("latest_version")
if not latest_version:
raise HTTPException(status_code=404, detail="latest firmware version not configured")
firmware_url_config = await system_config_manager.get_config("update_firmware_url")
firmware_url = firmware_url_config.config_value if firmware_url_config else None
if not firmware_url:
raise HTTPException(status_code=404, detail="firmware url not configured")
from handlers.mqtt_handler import TalkingQMQTTService
service = await TalkingQMQTTService.get_instance()
if service is None:
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
await device_firmware_update_manager.update_firmware_update(
device_id=device_id,
firmware_version=latest_version,
update_status="sent",
progress=0.0,
)
msg_id = await service.send_ota_command(device_id, firmware_url, latest_version)
return {
**firmware_status,
"latest_version": latest_version,
"target_version": latest_version,
"update_status": "sent",
"progress": 0.0,
"msg_id": msg_id,
"can_update": bool(firmware_status.get("can_update")),
}
# 创建全局 DeviceService 实例
device_service = DeviceService()

View File

@@ -71,6 +71,12 @@ def build_device_audio_client_msg_id(*, device_id: str, target_device_id: str, a
return f"device-audio-{digest[:32]}"
def build_device_parent_leave_message_client_msg_id(*, device_id: str, media_file_key: str) -> str:
raw = f"{device_id}|parent|{media_file_key}"
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()
return f"device-parent-{digest[:32]}"
def normalize_content_json(value: Any) -> dict[str, Any] | None:
if value is None:
return None
@@ -409,6 +415,72 @@ class ImService(DatabaseServiceBase):
finally:
await db_session.close()
async def create_device_parent_leave_message(
self,
*,
device_id: str,
media_file_key: str,
media_duration_ms: int | None = None,
media_mime_type: str | None = None,
media_size_bytes: int | None = None,
media_transcript_text: str | None = None,
client_msg_id: str | None = None,
ext_json: dict[str, Any] | None = None,
) -> tuple[DeviceIdentity, ConversationMessageCreateResult]:
normalized_media_file_key = str(media_file_key or "").strip()
if not normalized_media_file_key:
raise HTTPException(status_code=400, detail="media_file_key is required")
db_session = await self.get_session()
try:
dao = ImDAO(db_session)
owner_identity = await dao.get_bound_device_owner_identity(device_id=device_id)
device_identity = DeviceIdentity(
device_id=owner_identity.device_id,
child_id=owner_identity.child_id,
child_name=owner_identity.child_name,
)
if ext_json:
resolved_ext_json = dict(ext_json)
else:
resolved_ext_json = {}
resolved_ext_json.update(
{
"message_kind": "leave_message",
"source": "device_mqtt_011",
"source_device_id": device_id,
}
)
payload = DeviceMessageCreateRequest(
conversation_type=PARENT_CHILD_CONVERSATION_TYPE,
parent_user_id=owner_identity.owner_user_id,
content_type=2,
media_file_key=normalized_media_file_key,
media_duration_ms=media_duration_ms,
media_mime_type=(media_mime_type or "").strip() or "audio/mpeg",
media_size_bytes=media_size_bytes,
media_transcript_text=(media_transcript_text or "").strip() or None,
client_msg_id=(client_msg_id or "").strip()
or build_device_parent_leave_message_client_msg_id(
device_id=device_id,
media_file_key=normalized_media_file_key,
),
ext_json=resolved_ext_json,
)
result = await self._create_device_message_with_payload(
dao=dao,
device_identity=device_identity,
payload=payload,
)
return device_identity, result
except Exception:
await db_session.rollback()
raise
finally:
await db_session.close()
async def assert_child_exists(self, *, child_id: int) -> Mapping[str, Any]:
db_session = await self.get_session()
try: