from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List, Optional from datetime import datetime import secrets from database import get_db from schemas import Device as DeviceSchema, DeviceCreate, DeviceUpdate, BootstrapResponse from models import Device as DeviceModel from database import Content as ContentModel from mqtt_manager import mqtt_manager router = APIRouter( prefix="/api/devices", tags=["devices"] ) @router.post("/", response_model=DeviceSchema, status_code=status.HTTP_201_CREATED) async def create_device(device: DeviceCreate, db: Session = Depends(get_db)): """ 注册新设备 """ # 检查设备ID是否已存在 db_device = db.query(DeviceModel).filter(DeviceModel.device_id == device.device_id).first() if db_device: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="设备ID已存在" ) # 创建新设备 secret = device.secret if device.secret else secrets.token_urlsafe(32) db_device = DeviceModel( device_id=device.device_id, secret=secret, name=device.name, scene=device.scene ) db.add(db_device) db.commit() db.refresh(db_device) # 订阅设备状态 mqtt_manager.subscribe_to_device_status(device.device_id) return db_device @router.get("/", response_model=List[DeviceSchema]) async def list_devices( skip: int = 0, limit: int = 100, scene: Optional[str] = None, is_active: Optional[bool] = None, db: Session = Depends(get_db) ): """ 获取设备列表 """ query = db.query(DeviceModel) if scene: query = query.filter(DeviceModel.scene == scene) if is_active is not None: query = query.filter(DeviceModel.is_active == is_active) devices = query.offset(skip).limit(limit).all() return devices @router.get("/{device_id}", response_model=DeviceSchema) async def get_device(device_id: str, db: Session = Depends(get_db)): """ 获取设备详情 """ device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) return device @router.put("/{device_id}", response_model=DeviceSchema) async def update_device( device_id: str, device_update: DeviceUpdate, db: Session = Depends(get_db) ): """ 更新设备信息 """ device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) # 更新设备信息 update_data = device_update.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(device, field, value) device.updated_at = datetime.utcnow() db.commit() db.refresh(device) return device @router.delete("/{device_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_device(device_id: str, db: Session = Depends(get_db)): """ 删除设备 """ device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) # 取消订阅设备状态 mqtt_manager.unsubscribe_from_device_status(device_id) db.delete(device) db.commit() @router.get("/{device_id}/bootstrap", response_model=BootstrapResponse) async def device_bootstrap(device_id: str, db: Session = Depends(get_db)): """ 设备启动获取当前版本信息 """ device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) # 更新设备最后在线时间 device.last_online = datetime.utcnow() db.commit() # 获取最新的活跃内容 latest_content = db.query(ContentModel).filter( ContentModel.device_id == device_id, ContentModel.is_active == True ).order_by(ContentModel.version.desc()).first() response = BootstrapResponse( device_id=device_id, timezone=latest_content.timezone if latest_content else "Asia/Shanghai", time_format=latest_content.time_format if latest_content else "%Y-%m-%d %H:%M" ) if latest_content: response.content_version = latest_content.version response.last_updated = latest_content.created_at return response @router.get("/{device_id}/status") async def get_device_status(device_id: str, db: Session = Depends(get_db)): """ 获取设备状态 """ device = db.query(DeviceModel).filter(DeviceModel.device_id == device_id).first() if not device: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="设备不存在" ) return { "device_id": device_id, "name": device.name, "scene": device.scene, "is_active": device.is_active, "last_online": device.last_online, "created_at": device.created_at, "updated_at": device.updated_at }