休眠时段操作小程序加提示,博士角色语速调快一点,

刷自己卡没有留言情况下播放最后一条留言,增加角色切换提示音。
This commit is contained in:
HycJack
2026-05-26 01:50:23 +08:00
parent be58ca65b0
commit f9f1183e4b
6 changed files with 93 additions and 10 deletions

Binary file not shown.

View File

@@ -1,12 +1,12 @@
from collections.abc import Awaitable, Callable
import logging
from datetime import datetime, time, timedelta
from fastapi import FastAPI, HTTPException, Request
from sqlalchemy import text
from fastapi.responses import JSONResponse
from database.connection import get_db_manager
from banban.security import auth_error_response, decode_access_token
from banban.service.device import device_service
NO_AUTH_PATH_PREFIXES = (
"/banban/auth/login",
@@ -34,6 +34,28 @@ def _is_no_auth_path(path: str) -> bool:
return True
return False
def convert_to_time(value):
"""将字符串或timedelta转换为time对象"""
if value is None:
return None
if isinstance(value, time):
return value
if isinstance(value, timedelta):
# timedelta 可能表示时间间隔,例如 22:00:00 表示为 22小时
total_seconds = value.total_seconds()
hours = int(total_seconds // 3600)
minutes = int((total_seconds % 3600) // 60)
seconds = int(total_seconds % 60)
return time(hours, minutes, seconds)
if isinstance(value, str):
# 支持 "HH:MM" 或 "HH:MM:SS"
parts = value.split(':')
if len(parts) == 2:
return datetime.strptime(value, "%H:%M").time()
elif len(parts) == 3:
return datetime.strptime(value, "%H:%M:%S").time()
raise ValueError(f"Unsupported type for time conversion: {type(value)}")
def install_auth_middleware(app: FastAPI) -> None:
@app.middleware("http")
@@ -122,4 +144,53 @@ def install_auth_middleware(app: FastAPI) -> None:
await session.close()
request.state.user_id = user_id
async def is_device_sleep(device_id: str) -> bool:
db_manager = await get_db_manager()
session = await db_manager.get_session()
try:
row = await device_service.get_device_status(
device_id=device_id,
user_id=user_id,
)
sleep_start_str=row.get("disable_time_start")
sleep_end_str=row.get("disable_time_end")
print(sleep_start_str, sleep_end_str)
now = datetime.now()
current_time = now.time()
print(current_time)
start_time = convert_to_time(sleep_start_str)
end_time = convert_to_time(sleep_end_str)
print(start_time, end_time)
# 如果任意一个转换后为 None视为未配置睡眠时间
if start_time is None or end_time is None:
return True
now = datetime.now()
current_time = now.time()
if start_time <= end_time:
return not (start_time <= current_time <= end_time)
else:
return not (current_time >= start_time or current_time <= end_time)
finally:
await session.close()
path = request.url.path
# 仅拦截以 /banban/devices/ 开头的路径
if path.startswith("/banban/devices/") and ('volume' in path or 'location' in path or 'firmware/update' in path) :
# 解析 device_id假设路径格式为 /banban/devices/{device_id}/... 或 /banban/devices/{device_id}
parts = path.split("/")
# parts 示例: ['', 'banban', 'devices', 'device_id', 'action', ...]
if len(parts) >= 4:
device_id = parts[3] # 第四个片段即为 device_id
if device_id: # 确保 device_id 非空
if not await is_device_sleep(device_id):
return JSONResponse(
status_code=400, # 或 403 / 409
content={"detail": f"设备 {device_id} 不在线或已配置睡眠时间,请稍后再试"}
)
else:
# 路径格式不符合预期,可以放行或返回错误(根据业务决定)
# 这里选择放行,让后续路由自行处理
pass
return await call_next(request)

View File

@@ -10,7 +10,8 @@ from handlers.prompt_sound_handler import send_welcome_sound
from services.conversation_history import DeviceConversationHistory, conversation_history_manager
from services.device_config import DeviceConfig, device_config_manager
from services.role_manager import role_manager
from handlers.mqtt_handler import TalkingQMQTTService
from config import settings
router = APIRouter(prefix="/roles", tags=["banban-roles"])
@@ -121,8 +122,13 @@ async def update_device_role(
role_key,
)
if payload.play_welcome:
await send_welcome_sound(device_id, role_key, language)
# if payload.play_welcome:
# await send_welcome_sound(device_id, role_key, language)
service = await TalkingQMQTTService.get_instance()
if service is None:
raise HTTPException(status_code=503, detail="MQTT 服务未初始化")
audio_url = f"http://{settings.server_host}:{settings.server_port}/assets/audio/switch_role.mp3"
msg_id = await service.send_nfc_notice(device_id, audio_url)
return DeviceRoleResponse(
device_id=device_id,

View File

@@ -454,6 +454,7 @@ class TalkingQMQTTService:
params = {f"url_{k}": audio_url for k, audio_url in enumerate(audio_urls, start=1)}
payload = {"msg_id": "005", "type": 0, "params": params}
await self._publish(topic, payload)
await offline_audio_cache.add_audio_url(device_id, audio_urls[-1])
return
payload = {

View File

@@ -112,14 +112,16 @@ class MiniMaxTTS(TTS, BaseService):
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
speed = 1.0
if self.selected_role and "boshi" in self.selected_role:
speed = 2.0
payload = {
"model": self.model,
"text": text,
"stream": True,
"voice_setting": {
"voice_id": self.voice_id,
"speed": 1.0,
"speed": speed,
"vol": 2.0,
"pitch": 0
},

View File

@@ -18,8 +18,11 @@ class OfflineAudioCache:
return list(self.offline_audio.get(device_id, []))
async def pop_audio_urls(self, device_id: str) -> List[str]:
async with self.lock:
return self.offline_audio.pop(device_id, [])
urls = await self.get_audio_urls(device_id)
if urls and len(urls) > 1:
async with self.lock:
return self.offline_audio.pop(device_id, [])
return urls
async def clear_audio_urls(self, device_id: str):
async with self.lock: