lct-08: каркас фронта, типизированный сокет, захват микрофона
Направление канала зашито в тип: у наблюдателя Out = never, отправить нечего и нельзя; у преподавателя нет входящих. Разделение прав архитектурное и на фронте тоже. Пока соединения нет, события не копятся: истина на сервере, после переподключения придёт актуальное состояние. Ресемплинг 48 → 16 кГц усредняет по окну выходного сэмпла: без фильтра всё выше 8 кГц наложилось бы на речь и испортило распознавание. Проверено числами: секунда звука — ровно 50 кадров по 320 сэмплов из 48 и 44.1 кГц, помеха 15 кГц подавлена до 7.8%. Эхоподавление и автоусиление браузера выключены: гарнитура эхо не даёт, а обработка портит речь для STT. Бэкенд принимает бинарные кадры по тому же сокету, что и события; кадр не того размера отбрасывается с одним предупреждением, а не 50 в секунду. Логирование приложения не было настроено: uvicorn настраивает только свои логгеры, и весь INFO модулей app.* молча терялся — нашлось, когда в логе не оказалось строки о принятых кадрах. make lesson запускает занятие и печатает ссылки на экраны — пульта преподавателя ещё нет, а открыть АРМ курсанта без занятия не с чем. Путь из браузера с живым микрофоном не проверен: нужен человек, шаги записаны в карточке.
This commit is contained in:
parent
f01f2dab76
commit
f24031b5b2
21 changed files with 594 additions and 4 deletions
42
backend/scripts/start_lesson.py
Normal file
42
backend/scripts/start_lesson.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""make lesson: запустить занятие и напечатать ссылки на экраны.
|
||||
|
||||
Нужен, пока нет пульта преподавателя (lct-17): без запущенного занятия
|
||||
АРМ курсанта открыть не с чем. Занятие живёт в памяти сервера и после
|
||||
выхода скрипта остаётся запущенным.
|
||||
|
||||
make lesson сценарий по умолчанию, тренировочный режим
|
||||
make lesson s=fire-apartment-l2 m=exam
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
import websockets
|
||||
|
||||
BACKEND = "ws://localhost:8000"
|
||||
FRONTEND = "http://localhost:5173"
|
||||
|
||||
|
||||
async def main(scenario_id: str, mode: str) -> None:
|
||||
session_id = str(uuid.uuid4())
|
||||
async with websockets.connect(f"{BACKEND}/ws/control/{session_id}") as control:
|
||||
await control.send(json.dumps({
|
||||
"type": "scenario.start",
|
||||
"scenario_id": scenario_id,
|
||||
"trainee": "Курсант",
|
||||
"mode": mode,
|
||||
}))
|
||||
await asyncio.sleep(0.5) # дать серверу зарегистрировать занятие до закрытия сокета
|
||||
|
||||
print(f"занятие {session_id} — {scenario_id}, режим {mode}")
|
||||
print(f" курсант: {FRONTEND}/trainee?session={session_id}")
|
||||
print(f" монитор: {FRONTEND}/wall?session={session_id}")
|
||||
print(f" преподаватель: {FRONTEND}/instructor?session={session_id}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scenario = sys.argv[1] if len(sys.argv) > 1 and sys.argv[1] else "fire-apartment-l2"
|
||||
mode = sys.argv[2] if len(sys.argv) > 2 and sys.argv[2] else "training"
|
||||
asyncio.run(main(scenario, mode))
|
||||
Loading…
Reference in a new issue