Files
banban/talkingq-url/banban/service/device.py

238 lines
8.5 KiB
Python

from collections.abc import Mapping
from datetime import time
from typing import Any, List, Literal
from services.database_service_base import DatabaseServiceBase
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):
def __init__(self):
super().__init__(service_name="device_service")
async def ensure_device_access(self, *, device_id: str, user_id: int) -> None:
db_session = await self.get_session()
try:
dao = DeviceDAO(db_session)
await dao.ensure_device_access(device_id=device_id, user_id=user_id)
finally:
await db_session.close()
async def list_device_messages(
self,
*,
device_id: str,
user_id: int,
cursor: int | None,
limit: int,
) -> List[Mapping[str, Any]]:
db_session = await self.get_session()
try:
dao = DeviceDAO(db_session)
await dao.ensure_device_access(device_id=device_id, user_id=user_id)
return await dao.list_device_messages(
device_id=device_id,
cursor=cursor,
limit=limit,
)
finally:
await db_session.close()
async def list_device_ai_conversations(
self,
*,
device_id: str,
user_id: int,
cursor: int | None,
limit: int,
) -> List[Mapping[str, Any]]:
db_session = await self.get_session()
try:
dao = DeviceDAO(db_session)
await dao.ensure_device_access(device_id=device_id, user_id=user_id)
return await dao.list_device_ai_conversations(
device_id=device_id,
cursor=cursor,
limit=limit,
)
finally:
await db_session.close()
async def get_device_status(
self,
*,
device_id: str,
user_id: int,
) -> Mapping[str, Any]:
db_session = await self.get_session()
try:
dao = DeviceDAO(db_session)
return await dao.get_device_status(device_id=device_id, user_id=user_id)
finally:
await db_session.close()
async def set_device_volume(
self,
*,
device_id: str,
user_id: int,
level: int,
) -> str:
await self.ensure_device_access(device_id=device_id, user_id=user_id)
from handlers.mqtt_handler import TalkingQMQTTService
service = await TalkingQMQTTService.get_instance()
if service is None:
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
return await service.send_volume_command(device_id, level)
async def set_sleep_schedule(
self,
*,
device_id: str,
user_id: int,
start: time,
end: time,
timezone: str,
) -> str:
await self.ensure_device_access(device_id=device_id, user_id=user_id)
from handlers.mqtt_handler import TalkingQMQTTService
service = await TalkingQMQTTService.get_instance()
if service is None:
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
start_text = start.strftime("%H:%M")
end_text = end.strftime("%H:%M")
msg_id = await service.send_sleep_schedule_command(device_id, start_text, end_text)
await device_setting_service.upsert_sleep_schedule(
device_id=device_id,
sleep_mode=1,
disable_time_start=start,
disable_time_end=end,
timezone=timezone,
)
return msg_id
async def set_remote_sleep_wake(
self,
*,
device_id: str,
user_id: int,
switch: Literal["on", "off"],
) -> str:
await self.ensure_device_access(device_id=device_id, user_id=user_id)
from handlers.mqtt_handler import TalkingQMQTTService
service = await TalkingQMQTTService.get_instance()
if service is None:
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()