582 lines
22 KiB
Python
582 lines
22 KiB
Python
from operator import and_, ge
|
|
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, Body
|
|
# from paddle import normal
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from app.core.database import get_db
|
|
from app.crud.event import event
|
|
from app.models.models import Robot, Message, RobotInfo,Group, GroupRobot, Event
|
|
from app.schemas.event import EventList, EventDetail, EventUpdate, EventQuery, BackStageEvent, BackStageEventDto, BackStageEventDetail, EditTemperatureDto, OcrAlertMessage, OcrAlertMessageDto, GetRobotDto, CommonAlertMessage, GetAllMessageDto
|
|
from app.util.httpResponse import BaseResponse
|
|
from app.util.status import EventType, etypeNameDict
|
|
from sqlalchemy import func, select
|
|
from app.services.robot_sync_service import robot_sync_service
|
|
|
|
# import datetime
|
|
|
|
router = APIRouter()
|
|
|
|
# @router.get("/events", response_model=List[EventList])
|
|
# async def get_events(
|
|
# db: AsyncSession = Depends(get_db),
|
|
# start_time: Optional[str] = None,
|
|
# end_time: Optional[str] = None,
|
|
# etypeName: Optional[str] = None,
|
|
# area: Optional[str] = None,
|
|
# skip: int = Query(0, ge=0),
|
|
# limit: int = Query(100, ge=1, le=1000)
|
|
# ):
|
|
# """
|
|
# 获取事件列表
|
|
# - 支持时间范围查询
|
|
# - 支持事件类型和区域筛选
|
|
# - 支持分页
|
|
# """
|
|
# query = EventQuery(
|
|
# start_time=start_time,
|
|
# end_time=end_time,
|
|
# etypeName=etypeName,
|
|
# area=area,
|
|
# skip=skip,
|
|
# limit=limit
|
|
# )
|
|
# events = await event.get_multi_with_query(db, query=query)
|
|
# return [EventList.model_validate(event) for event in events]
|
|
|
|
# 后台获取事件列表
|
|
# @router.post("/backstageEventlist", response_model=List[BackStageEvent])
|
|
@router.post("/backstageEventlist", response_model=BaseResponse[List[BackStageEvent]])
|
|
async def get_events_backstage(
|
|
db: AsyncSession = Depends(get_db),
|
|
query: BackStageEventDto = Body(...)
|
|
|
|
):
|
|
|
|
events = await event.get_multi_backstage_events(db, query=query)
|
|
return BaseResponse(code=200, msg="success", data=events)
|
|
|
|
# 后台获取事件详情
|
|
@router.get("/backstageEventDetail/{eventId}", response_model=BaseResponse[BackStageEventDetail])
|
|
async def get_event_detail(
|
|
eventId: str,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
back = await event.get_event_detail(db=db, eventId=eventId)
|
|
return BaseResponse(code = 200, msg="success", data=back)
|
|
|
|
|
|
|
|
# @router.get("/events/{event_id}", response_model=EventDetail)
|
|
# async def get_event(
|
|
# event_id: str,
|
|
# db: AsyncSession = Depends(get_db)
|
|
# ):
|
|
# """
|
|
# 获取事件详情
|
|
# """
|
|
# event_obj = await event.get_by_id(db, event_id=event_id)
|
|
# if not event_obj:
|
|
# raise HTTPException(status_code=404, detail="事件不存在")
|
|
# return EventDetail.model_validate(event_obj)
|
|
|
|
@router.put("/events/{event_id}", response_model=BaseResponse)
|
|
async def update_event(
|
|
event_id: str,
|
|
event_in: EditTemperatureDto,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
更新事件信息
|
|
"""
|
|
event_obj = await event.update_event(
|
|
db,
|
|
event_id=event_id,
|
|
obj_in=event_in
|
|
)
|
|
if not event_obj:
|
|
raise BaseResponse(code=404, msg="事件不存在")
|
|
return BaseResponse(code=200, msg="success")
|
|
|
|
@router.delete("/events/{event_id}", response_model=BaseResponse[EventDetail])
|
|
async def delete_event(
|
|
event_id: str,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
删除事件
|
|
"""
|
|
event_obj = await event.delete_event(db, event_id=event_id)
|
|
if not event_obj:
|
|
return BaseResponse(code=404,msg="事件不存在")
|
|
return BaseResponse(code=200, msg="success", data=EventDetail.model_validate(event_obj))
|
|
|
|
@router.post("/event/getRobotList", response_model=BaseResponse)
|
|
async def get_robot_list(
|
|
get_robot_dto: Optional[GetRobotDto] = Body(...),
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
获取机器人列表
|
|
"""
|
|
try:
|
|
|
|
# 查询所有分组
|
|
conditions = []
|
|
if get_robot_dto.tenantInfoId and len(get_robot_dto.tenantInfoId) > 0:
|
|
conditions.append(Group.tenantInfoId == get_robot_dto.tenantInfoId)
|
|
|
|
group_query = (
|
|
select(Group.name)
|
|
.where(*conditions)
|
|
)
|
|
|
|
group_list = await db.execute(group_query)
|
|
group_list = group_list.scalars().all()
|
|
|
|
result_dict = dict()
|
|
|
|
# 分组查询
|
|
for group_name in group_list:
|
|
|
|
# 构建查询条件
|
|
conditions = []
|
|
conditions.append(Group.name == group_name)
|
|
|
|
if get_robot_dto.number and len(get_robot_dto.number) > 0:
|
|
conditions.append(Robot.number.like(f"%{get_robot_dto.number}%"))
|
|
if get_robot_dto.status and len(get_robot_dto.status) > 0:
|
|
conditions.append(Robot.status == get_robot_dto.status)
|
|
if get_robot_dto.onlineStatus and len(get_robot_dto.onlineStatus) > 0:
|
|
conditions.append(Robot.onlineStatus == get_robot_dto.onlineStatus)
|
|
# if get_robot_dto.name and len(get_robot_dto.name) > 0:
|
|
# conditions.append(Group.name == get_robot_dto.name)
|
|
|
|
# 查询机器人列表
|
|
query = (
|
|
select(Robot, RobotInfo, Group)
|
|
.outerjoin(RobotInfo, Robot.robotId == RobotInfo.robotId)
|
|
.outerjoin(GroupRobot, Robot.robotId == GroupRobot.robotId)
|
|
.outerjoin(Group, GroupRobot.groupingId == Group.groupingId)
|
|
)
|
|
|
|
if conditions:
|
|
query = query.where(*conditions)
|
|
|
|
result = await db.execute(query)
|
|
robots = result.all()
|
|
|
|
# 格式化返回数据
|
|
robot_list = []
|
|
for robot, robot_info, group in robots:
|
|
|
|
# 查询告警数量, 高温感知报警为一级报警, 其余为二级报警.
|
|
count_1 = 0
|
|
query_1 = (
|
|
select(func.count())
|
|
.select_from(Message)
|
|
.outerjoin(Event, Message.eventId == Event.eventId)
|
|
.where(Message.handle=="0", Message.eventType=="1", Event.number == robot.number)
|
|
)
|
|
|
|
count_1 = await db.execute(query_1)
|
|
count_1 = count_1.scalar_one_or_none()
|
|
|
|
count_o = 0
|
|
if count_1 == 0:
|
|
query_o = (
|
|
select(func.count())
|
|
.select_from(Message)
|
|
.outerjoin(Event, Message.eventId == Event.eventId)
|
|
.where(Message.handle=="0", Message.eventType!="1", Event.number == robot.number)
|
|
)
|
|
|
|
count_o = await db.execute(query_o)
|
|
count_o = count_o.scalar_one_or_none()
|
|
|
|
if count_1 > 0:
|
|
tag = 1
|
|
elif count_o > 0:
|
|
tag = 2
|
|
else:
|
|
tag = 0
|
|
|
|
robot_dict = {
|
|
"robotId": robot.robotId,
|
|
"number": robot.number,
|
|
"groupingId": robot.groupingId,
|
|
"onlineStatus": robot.onlineStatus,
|
|
"status": robot.status,
|
|
"groupName": group.name if group else None,
|
|
"robotInfo": {
|
|
"temperature": robot_info.temperature if robot_info else None,
|
|
"humidity": robot_info.humidity if robot_info else None,
|
|
"power": robot_info.power if robot_info else None,
|
|
"mileage": robot_info.mileage if robot_info else None,
|
|
"area": robot_info.area if robot_info else None,
|
|
"floor": robot_info.floor if robot_info else None,
|
|
"map": robot_info.map if robot_info else None,
|
|
"positon": robot_info.positon if robot_info else None,
|
|
"status": robot_info.status if robot_info else None,
|
|
"ststusName": robot_info.ststusName if robot_info else None,
|
|
"updateTime": robot_info.updateTime if robot_info else None,
|
|
"tag": tag
|
|
}
|
|
}
|
|
robot_list.append(robot_dict)
|
|
|
|
result_dict[group_name] = robot_list
|
|
|
|
return BaseResponse(code=200, msg="success", data=result_dict)
|
|
|
|
except Exception as e:
|
|
print(f"获取机器人列表失败: {str(e)}")
|
|
return BaseResponse(code=500, msg="获取机器人列表失败")
|
|
|
|
@router.post("/event/refreshRobotList", response_model=BaseResponse)
|
|
async def refresh_robot_list(
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""
|
|
刷新机器人列表
|
|
"""
|
|
try:
|
|
# 手动触发数据同步
|
|
success = await robot_sync_service.sync_robot_data(db)
|
|
if success:
|
|
return BaseResponse(code=200, msg="刷新成功")
|
|
return BaseResponse(code=500, msg="刷新失败")
|
|
|
|
except Exception as e:
|
|
print(f"刷新机器人列表失败: {str(e)}")
|
|
return BaseResponse(code=500, msg="刷新失败")
|
|
|
|
# 获取未处理告警消息列表
|
|
@router.get("/events/messagesUnhandled",
|
|
# response_model=BaseResponse[dict]
|
|
)
|
|
async def get_messages_unhandle(
|
|
number: Optional[str]=None,
|
|
etypeName: Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(100, ge=1, le=1000)
|
|
):
|
|
"""_summary_
|
|
获取告警消息列表
|
|
|
|
"""
|
|
data = await get_message_by_event_type(db, "0", skip, limit, number, etypeName)
|
|
|
|
return BaseResponse(code=200, msg="success", data=data)
|
|
|
|
|
|
# 获取已处理告警消息列表
|
|
@router.get("/events/messagesHandled",
|
|
# response_model=BaseResponse[dict]
|
|
)
|
|
async def get_messages_handle_robot(
|
|
number: Optional[str]=None,
|
|
etypeName: Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(100, ge=1, le=1000)
|
|
):
|
|
data = await get_message_by_event_type(db, "1", skip, limit, number, etypeName)
|
|
return BaseResponse(code=200, msg="success", data=data)
|
|
|
|
|
|
# 批量处理告警数据--传递messageId List
|
|
@router.post("/events/handleOcrAlerts", response_model=BaseResponse)
|
|
async def handle_ocr_alerts(
|
|
number:Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
messageIdList: List[int] = Body(...)
|
|
):
|
|
"""_summary_
|
|
一建处理告警
|
|
"""
|
|
|
|
flag = await event.handle_ocr_alerts(db, messageIdList=messageIdList)
|
|
if flag:
|
|
return BaseResponse(code=200, msg="success")
|
|
return BaseResponse(code=500, msg="fail to update data")
|
|
|
|
# 批量处理告警数据--直接将 handle=0的置为1
|
|
@router.get("/events/handleOcrAlerts", response_model=BaseResponse)
|
|
async def handle_ocr_alerts_get(
|
|
number:Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
"""_summary_
|
|
一键处理告警
|
|
Args:
|
|
db (AsyncSession, optional): _description_. Defaults to Depends(get_db).
|
|
|
|
Returns:
|
|
_type_: _description_
|
|
"""
|
|
flag = await event.handle_ocr_alerts_get(db, number)
|
|
if flag:
|
|
return BaseResponse(code=200, msg="success")
|
|
return BaseResponse(code=500, msg="fail to update data")
|
|
|
|
# 处理单个告警数据
|
|
@router.post("/events/handleOcrAlert", response_model=BaseResponse)
|
|
async def handle_ocr_alert(
|
|
db: AsyncSession = Depends(get_db),
|
|
ocrAlertMessageDto: OcrAlertMessageDto = Body(...)
|
|
):
|
|
"""
|
|
处理单个警告
|
|
"""
|
|
flag = await event.handle_ocr_alert(db, ocrAlertMessageDto=ocrAlertMessageDto)
|
|
if flag:
|
|
return BaseResponse(code=200, msg="success")
|
|
return BaseResponse(code=500, msg="fail to update data")
|
|
|
|
# 获取消息的统计数据
|
|
@router.get("/events/stMessages/{day}", )
|
|
async def get_messages_st(
|
|
day: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
data = await event.get_messages_st(db, day)
|
|
return BaseResponse(code=200, msg="success", data=data)
|
|
|
|
|
|
# 查看告警详情
|
|
@router.get("/events/alert/{messageId}", response_model=BaseResponse[OcrAlertMessage])
|
|
async def get_alert_detail(
|
|
messageId:int,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
|
|
# alert_message = await event.get_alert_detail(db, messageId=messageId)
|
|
alert_message = await event.get_messages(db, skip=0, limit=1, condition=[Message.messageId==messageId])
|
|
|
|
return BaseResponse(code=200, msg="success", data=alert_message[0])
|
|
|
|
# 前端查看告警详情
|
|
@router.get("/events/alertFront/{messageId}", response_model=BaseResponse[OcrAlertMessage])
|
|
async def get_alert_front_detail(
|
|
messageId:int,
|
|
# number: Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
|
|
# alert_message = await event.get_alert_detail(db, messageId=messageId)
|
|
alert_message = await event.get_messages_front(db, skip=0, limit=1, condition=[Message.messageId==messageId])
|
|
|
|
return BaseResponse(code=200, msg="success", data=alert_message[0])
|
|
|
|
# 查看全部告警事件详情--属于该机器人的
|
|
@router.get("/events/getAllAlertMessage/{number}")
|
|
async def get_all_alert_message(
|
|
number: str,
|
|
db: AsyncSession = Depends(get_db),
|
|
skip: int = Query(0, ge=0),
|
|
limit: int = Query(100, ge=1, le=1000)
|
|
):
|
|
data = await event.get_all_alert_message(db, number, skip, limit)
|
|
return BaseResponse(
|
|
code = 200,
|
|
msg = "success",
|
|
data = data
|
|
)
|
|
|
|
# 获取未处理告警消息数量
|
|
@router.get("/events/alertCount", response_model=BaseResponse)
|
|
async def get_alert_count(
|
|
number: Optional[str] = None,
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
# 未处理告警数量
|
|
alert_count = await event.get_alert_count(db, handle="0", number=number)
|
|
# 已处理告警数量
|
|
processed_count = await event.get_alert_count(db, handle="1", number=number)
|
|
# 所有告警数量
|
|
all_count = alert_count + processed_count
|
|
return BaseResponse(code=200, msg="success", data={
|
|
"alert_count": alert_count,
|
|
"processed_count": processed_count,
|
|
"all_count": all_count
|
|
})
|
|
# 获取厂区及机器人实时监控
|
|
@router.get("/events/getMonitor")
|
|
async def get_monitor():
|
|
|
|
|
|
t = {
|
|
"厂区监控1":{
|
|
"A区厂区监控":[
|
|
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
"https://play.livejinan.cn/21ZoxrLa/45ee42a7b9d64400963d6bacc9a75867.m3u8?auth_key=1893340800-0-0-3522915cd5471437682295596e73cac2"
|
|
],
|
|
"B区厂区监控":[
|
|
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
""
|
|
],
|
|
"C区厂区监控":[
|
|
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
"https://play.livejinan.cn/21ZoxrLa/45ee42a7b9d64400963d6bacc9a75867.m3u8?auth_key=1893340800-0-0-3522915cd5471437682295596e73cac2"
|
|
]
|
|
},
|
|
"厂区监控2":{
|
|
"D区厂区监控":[
|
|
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
"https://play.livejinan.cn/21ZoxrLa/45ee42a7b9d64400963d6bacc9a75867.m3u8?auth_key=1893340800-0-0-3522915cd5471437682295596e73cac2"
|
|
],
|
|
"E区厂区监控":[
|
|
"",
|
|
"https://play.livejinan.cn/21ZoxrLa/45ee42a7b9d64400963d6bacc9a75867.m3u8?auth_key=1893340800-0-0-3522915cd5471437682295596e73cac2"
|
|
]
|
|
},
|
|
"厂区监控3":{
|
|
"F区厂区监控":[
|
|
"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
"https://play.livejinan.cn/21ZoxrLa/45ee42a7b9d64400963d6bacc9a75867.m3u8?auth_key=1893340800-0-0-3522915cd5471437682295596e73cac2"
|
|
],
|
|
"G区厂区监控":[
|
|
"",
|
|
"https://play.livejinan.cn/21ZoxrLa/45ee42a7b9d64400963d6bacc9a75867.m3u8?auth_key=1893340800-0-0-3522915cd5471437682295596e73cac2"
|
|
]
|
|
},
|
|
"室外巡检":{
|
|
"robot1":[
|
|
"webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041301_34020000001320000001",
|
|
"webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041302_34020000001320000002",
|
|
"webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041302_34020000001320000001"
|
|
],
|
|
"robot2":[
|
|
"webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041301_34020000001320000001",
|
|
"webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041302_34020000001320000002",
|
|
"webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041302_34020000001320000001"
|
|
]
|
|
},
|
|
|
|
}
|
|
|
|
return BaseResponse(code=200, msg="success",data=t)
|
|
|
|
# 获取机器人统计信息
|
|
@router.get("/events/robotST", response_model=BaseResponse[dict])
|
|
async def get_robot_st(
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
# 机器人总数
|
|
t_count = await event.get_robot_count(db, status=[])
|
|
# 在线总数
|
|
online_count = await event.get_robot_count(db, status=[Robot.onlineStatus=="1", Robot.status=="0"])
|
|
# 离线总数
|
|
offline_count = await event.get_robot_count(db, status=[Robot.onlineStatus=="2", Robot.status=="0"])
|
|
# 故障总数
|
|
disable_count = await event.get_robot_count(db, status=[Robot.status=="1"])
|
|
|
|
return BaseResponse(code=200, msg="success",
|
|
data = {
|
|
"t_count": t_count,
|
|
"online_count": online_count,
|
|
"offline_count": offline_count,
|
|
"disable_count": disable_count
|
|
})
|
|
|
|
# 获取事件类型下拉列表
|
|
@router.get("/events/getEtypeNameList")
|
|
async def get_etype_name_list(
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
etype_name_list = await event.get_etype_name_list(db)
|
|
return BaseResponse(code=200, msg="success", data=etype_name_list)
|
|
|
|
# 获取仪表识别结果
|
|
@router.get("/events/getOcrResult")
|
|
async def get_ocr_result(
|
|
db: AsyncSession = Depends(get_db)
|
|
):
|
|
ocr_result = await event.get_ocr_result(db)
|
|
return BaseResponse(code=200, msg="success", data=ocr_result)
|
|
|
|
# 获取机器人任务列表
|
|
@router.get("/events/robotTask")
|
|
async def get_robot_task(
|
|
db: AsyncSession=Depends(get_db),
|
|
robotId: Optional[str]=None
|
|
):
|
|
# print("robotId: ", robotId)
|
|
task_list = await event.get_robot_task(db, robotId)
|
|
return BaseResponse(code="200", msg="success", data = task_list)
|
|
|
|
async def get_message_by_event_type(
|
|
db:AsyncSession ,
|
|
handle: str ,
|
|
skip: int ,
|
|
limit: int,
|
|
number: Optional[str]=None,
|
|
etypeName: Optional[str] = None
|
|
):
|
|
condition_robotId = []
|
|
if number and len(number) > 0:
|
|
condition_robotId.append( Event.number== number)
|
|
if etypeName and len(etypeName) > 0:
|
|
if etypeName in etypeNameDict:
|
|
print(etypeName, etypeNameDict[etypeName])
|
|
query_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==etypeNameDict[etypeName]] + condition_robotId)
|
|
return {
|
|
"query_message": query_message
|
|
}
|
|
else:
|
|
return {}
|
|
|
|
|
|
|
|
# 高温报警消息
|
|
high_temperature_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.HightTemperature.value] + condition_robotId)
|
|
smoke_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.SMOKE.value]+ condition_robotId)
|
|
long_stay_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.LONGSTAY.value]+ condition_robotId)
|
|
air_quality_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.AIRQUALITY.value]+ condition_robotId)
|
|
|
|
stop_emergency_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.STOPEMERGENCY.value]+ condition_robotId)
|
|
voice_connect_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.VOICENOTCONNECT.value]+ condition_robotId)
|
|
daily_inspect_message = await event.get_messages(db,
|
|
skip=skip,
|
|
limit=limit,
|
|
condition=[Message.handle==handle, Message.eventType==EventType.DAILYINSPECT.value]+ condition_robotId)
|
|
|
|
return {
|
|
"high_temperature_message": high_temperature_message,
|
|
"smoke_message": smoke_message,
|
|
"long_stay_message": long_stay_message,
|
|
"air_quality_message": air_quality_message,
|
|
"stop_emergency_message": stop_emergency_message,
|
|
"voice_connect_message": voice_connect_message,
|
|
"daily_inspect_message": daily_inspect_message,
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|