238 lines
8.5 KiB
Python
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()
|