合入小程序修改,新增告警功能
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user