add mqtt and device test

This commit is contained in:
HycJack
2026-04-26 20:07:41 +08:00
parent 7c12ca59dd
commit 389f6c4040
22 changed files with 742 additions and 32 deletions

View File

@@ -4,6 +4,7 @@ from api.roles import router as roles_router
from api.auth import router as auth_router
from api.device_control import router as device_control_router
from api.ota import router as ota_router # 新增OTA路由
from api.mqtt_router import router as mqtt_router # 新增MQTT路由
api_router = APIRouter()
api_router.include_router(websocket_router, tags=["WebSocket"])
@@ -11,4 +12,5 @@ api_router.include_router(roles_router, tags=["Roles"])
api_router.include_router(auth_router, tags=["Auth"])
api_router.include_router(device_control_router, tags=["Device Control"])
api_router.include_router(ota_router, tags=["OTA"]) # 注册OTA路由
api_router.include_router(mqtt_router, tags=["MQTT"]) # 注册MQTT路由

View File

@@ -0,0 +1,57 @@
from typing import Optional
from pydantic import BaseModel, Field
class GPSQueryRequest(BaseModel):
device_id: str = Field(..., description="设备ID如 TalkingQ_001")
class VolumeRequest(BaseModel):
device_id: str = Field(..., description="设备ID")
level: int = Field(..., ge=0, le=100, description="音量等级 0-100")
class OTARequest(BaseModel):
device_id: str = Field(..., description="设备ID")
url: str = Field(..., description="固件下载地址")
version: str = Field(..., description="目标版本号")
class NFCNoticeRequest(BaseModel):
device_id: str = Field(..., description="设备ID")
url: str = Field(..., description="提示音地址")
class NFCListenRequest(BaseModel):
device_id: str = Field(..., description="设备ID")
uuid: str = Field(..., description="NFC卡片UUID")
class NFCUnreadRequest(BaseModel):
device_id: str = Field(..., description="设备ID")
url: str = Field(..., description="未读留言音频地址")
class NFCBindRequest(BaseModel):
device_id: str = Field(..., description="设备ID")
uuid: str = Field(..., description="NFC卡片UUID")
class CommandResponse(BaseModel):
code: int = 0
message: str = "success"
msg_id: str = Field(..., description="消息ID")
device_id: str = Field(..., description="设备ID")
class DeviceResponseData(BaseModel):
msg_id: str
status: Optional[str] = None
data: Optional[dict] = None
timestamp: Optional[float] = None
class DeviceStatusResponse(BaseModel):
device_id: str
online: bool
last_seen: Optional[float] = None

View File

@@ -0,0 +1,126 @@
import logging
from typing import Optional
from fastapi import APIRouter, HTTPException, Query
from services.offline_audio_cache import offline_audio_cache
from api.mqtt_models import (
GPSQueryRequest,
VolumeRequest,
OTARequest,
NFCNoticeRequest,
NFCListenRequest,
NFCUnreadRequest,
NFCBindRequest,
CommandResponse,
DeviceResponseData,
DeviceStatusResponse,
)
from handlers.mqtt_handler import TalkingQMQTTService
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/mqtt", tags=["MQTT 服务"])
async def _get_service() -> TalkingQMQTTService:
service = await TalkingQMQTTService.get_instance()
if service is None:
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
return service
@router.post("/gps", response_model=CommandResponse, summary="GPS位置查询")
async def query_gps(req: GPSQueryRequest):
service = await _get_service()
msg_id = await service.send_gps_query(req.device_id)
return CommandResponse(msg_id=msg_id, device_id=req.device_id)
@router.post("/volume", response_model=CommandResponse, summary="音量调节")
async def set_volume(req: VolumeRequest):
service = await _get_service()
msg_id = await service.send_volume_command(req.device_id, req.level)
return CommandResponse(msg_id=msg_id, device_id=req.device_id)
@router.post("/ota", response_model=CommandResponse, summary="OTA升级")
async def start_ota(req: OTARequest):
service = await _get_service()
msg_id = await service.send_ota_command(req.device_id, req.url, req.version)
return CommandResponse(msg_id=msg_id, device_id=req.device_id)
@router.post("/nfc/notice", response_model=CommandResponse, summary="NFC留言下发提示")
async def send_nfc_notice(req: NFCNoticeRequest):
service = await _get_service()
msg_id = await service.send_nfc_notice(req.device_id, req.url)
return CommandResponse(msg_id=msg_id, device_id=req.device_id)
@router.post("/nfc/listen", response_model=CommandResponse, summary="NFC收听留言")
async def nfc_listen(req: NFCListenRequest):
service = await _get_service()
topic = f"device/{req.device_id}/event"
payload = {
"msg_id": "005",
"params": {"uuid": req.uuid}
}
await service._publish(topic, payload)
return CommandResponse(msg_id="005", device_id=req.device_id)
@router.post("/nfc/unread", response_model=CommandResponse, summary="设置NFC未读留言")
async def set_nfc_unread(req: NFCUnreadRequest):
await offline_audio_cache.add_audio_url(req.device_id, req.url)
return CommandResponse(msg_id="", device_id=req.device_id, message="已设置未读留言")
@router.delete("/nfc/unread/{device_id}", response_model=CommandResponse, summary="清除NFC未读留言定时推送")
async def clear_nfc_unread(device_id: str):
await offline_audio_cache.clear_audio_urls(device_id)
return CommandResponse(msg_id="", device_id=device_id, message="已清除未读留言")
@router.post("/nfc/bind", response_model=CommandResponse, summary="NFC绑定卡片")
async def bind_nfc(req: NFCBindRequest):
service = await _get_service()
msg_id = await service.send_bind_nfc_command(req.device_id, req.uuid)
return CommandResponse(msg_id=msg_id, device_id=req.device_id)
# @router.get("/nfc/unread", summary="查询所有未读留言设备列表")
# async def get_nfc_unread_devices():
# service = await _get_service()
# devices = service.get_nfc_unread_devices()
# return {"count": len(devices), "devices": devices}
# @router.get("/device/{device_id}/status", response_model=DeviceStatusResponse, summary="查询设备在线状态")
# async def get_device_status(device_id: str):
# service = await _get_service()
# online = service.is_device_online(device_id)
# last_seen = service.get_device_last_seen(device_id)
# return DeviceStatusResponse(device_id=device_id, online=online, last_seen=last_seen)
# @router.get("/device/{device_id}/responses", summary="查询设备上报记录")
# async def get_device_responses(
# device_id: str,
# msg_id: Optional[str] = Query(None, description="按msg_id过滤"),
# limit: int = Query(20, ge=1, le=100, description="返回条数"),
# ):
# service = await _get_service()
# responses = service.get_device_responses(device_id, msg_id=msg_id, limit=limit)
# return {"device_id": device_id, "count": len(responses), "responses": responses}
# @router.get("/device/{device_id}/wait_response", summary="等待设备响应(同步阻塞)")
# async def wait_device_response(
# device_id: str,
# msg_id: str = Query(..., description="等待的消息ID"),
# timeout: float = Query(10.0, ge=1.0, le=60.0, description="超时时间(秒)"),
# ):
# service = await _get_service()
# response = service.wait_for_response(device_id, msg_id, timeout=timeout)
# if response is None:
# raise HTTPException(status_code=408, detail=f"等待设备 {device_id} 的 msg_id={msg_id} 响应超时")
# return response