休眠时段操作小程序加提示,博士角色语速调快一点,
刷自己卡没有留言情况下播放最后一条留言,增加角色切换提示音。
This commit is contained in:
BIN
talkingq-url/assets/audio/switch_role.mp3
Normal file
BIN
talkingq-url/assets/audio/switch_role.mp3
Normal file
Binary file not shown.
@@ -1,12 +1,12 @@
|
|||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime, time, timedelta
|
||||||
from fastapi import FastAPI, HTTPException, Request
|
from fastapi import FastAPI, HTTPException, Request
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
from database.connection import get_db_manager
|
from database.connection import get_db_manager
|
||||||
from banban.security import auth_error_response, decode_access_token
|
from banban.security import auth_error_response, decode_access_token
|
||||||
|
from banban.service.device import device_service
|
||||||
|
|
||||||
NO_AUTH_PATH_PREFIXES = (
|
NO_AUTH_PATH_PREFIXES = (
|
||||||
"/banban/auth/login",
|
"/banban/auth/login",
|
||||||
@@ -34,6 +34,28 @@ def _is_no_auth_path(path: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
return False
|
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:
|
def install_auth_middleware(app: FastAPI) -> None:
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
@@ -122,4 +144,53 @@ def install_auth_middleware(app: FastAPI) -> None:
|
|||||||
await session.close()
|
await session.close()
|
||||||
|
|
||||||
request.state.user_id = user_id
|
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)
|
return await call_next(request)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from handlers.prompt_sound_handler import send_welcome_sound
|
|||||||
from services.conversation_history import DeviceConversationHistory, conversation_history_manager
|
from services.conversation_history import DeviceConversationHistory, conversation_history_manager
|
||||||
from services.device_config import DeviceConfig, device_config_manager
|
from services.device_config import DeviceConfig, device_config_manager
|
||||||
from services.role_manager import role_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"])
|
router = APIRouter(prefix="/roles", tags=["banban-roles"])
|
||||||
|
|
||||||
@@ -121,8 +122,13 @@ async def update_device_role(
|
|||||||
role_key,
|
role_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
if payload.play_welcome:
|
# if payload.play_welcome:
|
||||||
await send_welcome_sound(device_id, role_key, language)
|
# 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(
|
return DeviceRoleResponse(
|
||||||
device_id=device_id,
|
device_id=device_id,
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ class TalkingQMQTTService:
|
|||||||
params = {f"url_{k}": audio_url for k, audio_url in enumerate(audio_urls, start=1)}
|
params = {f"url_{k}": audio_url for k, audio_url in enumerate(audio_urls, start=1)}
|
||||||
payload = {"msg_id": "005", "type": 0, "params": params}
|
payload = {"msg_id": "005", "type": 0, "params": params}
|
||||||
await self._publish(topic, payload)
|
await self._publish(topic, payload)
|
||||||
|
await offline_audio_cache.add_audio_url(device_id, audio_urls[-1])
|
||||||
return
|
return
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
|
|||||||
@@ -112,14 +112,16 @@ class MiniMaxTTS(TTS, BaseService):
|
|||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
speed = 1.0
|
||||||
|
if self.selected_role and "boshi" in self.selected_role:
|
||||||
|
speed = 2.0
|
||||||
payload = {
|
payload = {
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"text": text,
|
"text": text,
|
||||||
"stream": True,
|
"stream": True,
|
||||||
"voice_setting": {
|
"voice_setting": {
|
||||||
"voice_id": self.voice_id,
|
"voice_id": self.voice_id,
|
||||||
"speed": 1.0,
|
"speed": speed,
|
||||||
"vol": 2.0,
|
"vol": 2.0,
|
||||||
"pitch": 0
|
"pitch": 0
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,8 +18,11 @@ class OfflineAudioCache:
|
|||||||
return list(self.offline_audio.get(device_id, []))
|
return list(self.offline_audio.get(device_id, []))
|
||||||
|
|
||||||
async def pop_audio_urls(self, device_id: str) -> List[str]:
|
async def pop_audio_urls(self, device_id: str) -> List[str]:
|
||||||
async with self.lock:
|
urls = await self.get_audio_urls(device_id)
|
||||||
return self.offline_audio.pop(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 def clear_audio_urls(self, device_id: str):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
|
|||||||
Reference in New Issue
Block a user