提交修正方法

This commit is contained in:
root 2025-09-29 08:50:59 +08:00
parent 38fc3abcf2
commit 8b3c1b9ec5
7 changed files with 418 additions and 296 deletions

59
.vscode/launch.json vendored
View File

@ -1,42 +1,6 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Backend Server",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/debug_server.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"env": {
"FLASK_ENV": "development",
"FLASK_DEBUG": "1",
"PYTHONPATH": "${workspaceFolder}"
},
"args": [],
"justMyCode": false,
"stopOnEntry": false,
"showReturnValue": true,
"redirectOutput": true
},
{
"name": "Debug Backend App.py",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/backend/app.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"env": {
"FLASK_ENV": "development",
"FLASK_DEBUG": "1",
"PYTHONPATH": "${workspaceFolder}"
},
"args": [],
"justMyCode": false,
"stopOnEntry": false,
"showReturnValue": true,
"redirectOutput": true
},
"configurations": [
{
"name": "Debug Main.py",
"type": "python",
@ -48,29 +12,14 @@
"PYTHONPATH": "${workspaceFolder}"
},
"args": [
"--mode", "development",
"--log-level", "DEBUG"
"--host", "0.0.0.0",
"--port", "5000"
],
"justMyCode": false,
"stopOnEntry": false,
"showReturnValue": true,
"redirectOutput": true
},
{
"name": "Debug WebSocket Test",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/test_websocket.py",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"args": [],
"justMyCode": false,
"stopOnEntry": false,
"showReturnValue": true,
"redirectOutput": true
}
]
}

View File

@ -15,24 +15,33 @@ backup_interval = 24
max_backups = 7
[CAMERA]
device_index = 1
enabled = True
device_index = 3
width = 1280
height = 720
fps = 30
buffer_size = 1
fourcc = MJPG
backend = directshow
[FEMTOBOLT]
algorithm_type = plt
enabled = True
algorithm_type = opencv
color_resolution = 1080P
depth_mode = NFOV_2X2BINNED
camera_fps = 15
depth_range_min = 1200
depth_range_max = 1500
camera_fps = 20
depth_range_min = 1000
depth_range_max = 1400
fps = 15
synchronized_images_only = False
[DEVICES]
imu_device_type = real
imu_port = COM14
imu_enabled = True
imu_device_type = ble
imu_port = COM9
imu_mac_address = ef:3c:1a:0a:fe:02
imu_baudrate = 9600
pressure_enabled = True
pressure_device_type = real
pressure_use_mock = False
pressure_port = COM5

View File

@ -898,167 +898,23 @@ class CameraManager(BaseDevice):
bool: 相机是否物理连接
"""
try:
if not self.cap:
# 如果相机实例不存在,尝试重新创建
self.logger.info("相机实例不存在,尝试重新创建-----------------")
return self._attempt_device_reconnection()
if not self.is_connected:
self.logger.info("相机未连接,检查连接状态")
return False
if not self.cap.isOpened():
# 相机未打开,尝试重连
self.logger.info("相机未打开,尝试重新连接-----------------")
return self._attempt_device_reconnection()
# 尝试读取一帧
ret, _ = self.cap.read()
if not ret:
self.logger.error("相机连接已断开,读取失败")
return False
# 多层次验证相机连接状态
try:
# 第一步使用grab()方法快速清除所有缓存帧
self.logger.debug("快速清除相机缓存帧...")
try:
# grab()方法只获取帧但不解码,速度更快
# 连续grab多次以清空内部缓冲区
for _ in range(15): # 增加清除次数,确保缓存完全清空
if not self.cap.grab():
break # 如果grab失败说明没有更多缓存帧
except Exception as e:
self.logger.debug(f"清除缓存帧时出现异常: {e}")
# 第二步:严格的连续帧检测
failed_count = 0
total_frames = 15 # 增加检测帧数
consecutive_failures = 0 # 连续失败计数
for i in range(total_frames):
try:
ret, frame = self.cap.read()
if ret and frame is not None:
# 验证帧数据的有效性
if self._validate_frame_data(frame):
del frame
consecutive_failures = 0 # 重置连续失败计数
else:
failed_count += 1
consecutive_failures += 1
del frame
else:
failed_count += 1
consecutive_failures += 1
# 如果连续3帧失败立即判定为断开
if consecutive_failures >= 3:
self.logger.warning(f"相机连接检测失败:连续{consecutive_failures}帧失败")
return False
# 如果总失败帧数超过30%,判定为断开
if failed_count > total_frames * 0.3:
self.logger.warning(f"相机连接检测失败:{failed_count}/{i+1}帧读取失败超过30%阈值")
return False
except Exception as e:
failed_count += 1
consecutive_failures += 1
self.logger.debug(f"读取第{i+1}帧时异常: {e}")
# 连续异常也判定为断开
if consecutive_failures >= 3:
self.logger.warning(f"相机连接检测异常:连续{consecutive_failures}帧异常")
return False
# 短暂延时,避免过快读取
time.sleep(0.005) # 减少延时提高检测速度
# 第三步:最终判断
success_rate = (total_frames - failed_count) / total_frames
if success_rate >= 0.7: # 成功率需要达到70%
self.logger.info(f"相机连接检测成功:{total_frames-failed_count}/{total_frames}帧读取成功,成功率{success_rate:.1%}")
return True
else:
self.logger.warning(f"相机连接检测失败:成功率{success_rate:.1%}低于70%阈值")
return False
except Exception as e:
self.logger.warning(f"相机连接检测过程中发生异常: {e}")
return False
except Exception as e:
self.logger.debug(f"检查相机硬件连接时发生异常: {e}")
return False
def _validate_frame_data(self, frame) -> bool:
"""
验证帧数据的有效性
Args:
frame: 要验证的帧数据
Returns:
bool: 帧数据是否有效
"""
try:
if frame is None:
return False
# 检查帧尺寸
if frame.shape[0] < 10 or frame.shape[1] < 10:
return False
# 检查帧数据是否全为零(可能是无效帧)
if np.all(frame == 0):
return False
# 检查帧数据的方差(全黑或全白帧可能是无效的)
if np.var(frame) < 1.0:
return False
self.logger.info("相机硬件连接正常")
return True
except Exception:
return False
def _attempt_device_reconnection(self) -> bool:
"""
尝试重新连接相机设备
Returns:
bool: 重连是否成功
"""
try:
self.logger.info("检测到相机设备断开,尝试重新连接...")
# 清理旧的相机实例
if self.cap:
try:
self.cap.release()
except Exception as e:
self.logger.debug(f"清理旧相机实例时出错: {e}")
self.cap = None
# 等待设备释放
time.sleep(0.5)
# 重新初始化相机
if self.initialize():
self._notify_status_change(True)
# 重连成功后,确保数据流正在运行
if not self.is_streaming:
self.logger.info("重连成功,启动相机数据流")
self.start_streaming()
# 更新设备信息
self._device_info.update({
'device_index': self.device_index,
'resolution': f"{self.width}x{self.height}",
'fps': self.fps,
'backend': self.cap.getBackendName() if hasattr(self.cap, 'getBackendName') else 'Unknown'
})
return True
else:
self.logger.warning("相机设备重连失败")
return False
except Exception as e:
self.logger.error(f"相机设备重连过程中出错: {e}")
self.cap = None
self.logger.error(f"检查相机硬件连接时发生错误: {e}")
return False
def cleanup(self):
"""
@ -1099,6 +955,6 @@ class CameraManager(BaseDevice):
except Exception as e:
self.logger.error(f"清理相机资源时发生错误: {e}")

View File

@ -491,8 +491,7 @@ class DeviceCoordinator:
def restart_device(self, device_name: str) -> bool:
"""
重启指定设备
彻底重启指定设备停止推流断开连接销毁设备实例重新创建实例初始化恢复推流
Args:
device_name: 设备名称
@ -503,30 +502,155 @@ class DeviceCoordinator:
self.logger.error(f"设备 {device_name} 不存在")
return False
restart_start = time.time()
device = self.devices[device_name]
was_streaming = False
try:
self.logger.info(f"重启设备: {device_name}")
self.logger.info(f"开始彻底重启设备: {device_name}")
device = self.devices[device_name]
# 停止数据流
if hasattr(device, 'stop_streaming'):
device.stop_streaming()
# 第一步:检查并停止数据流
stop_start = time.time()
if hasattr(device, 'is_streaming'):
was_streaming = device.is_streaming
# 清理资源
if hasattr(device, 'stop_streaming') and was_streaming:
self.logger.info(f"正在停止 {device_name} 设备推流...")
try:
if not device.stop_streaming():
self.logger.warning(f"停止 {device_name} 推流失败,继续重启流程")
else:
self.logger.info(f"{device_name} 设备推流已停止")
except Exception as e:
self.logger.warning(f"停止 {device_name} 推流异常: {e},继续重启流程")
stop_time = (time.time() - stop_start) * 1000
# 第二步:断开连接并彻底清理资源
cleanup_start = time.time()
self.logger.info(f"正在彻底清理 {device_name} 设备...")
# 断开连接
if hasattr(device, 'disconnect'):
try:
device.disconnect()
self.logger.info(f"{device_name} 设备连接已断开")
except Exception as e:
self.logger.warning(f"断开 {device_name} 连接异常: {e}")
# 彻底清理资源
if hasattr(device, 'cleanup'):
device.cleanup()
try:
device.cleanup()
self.logger.info(f"{device_name} 设备资源已彻底清理")
except Exception as e:
self.logger.warning(f"清理 {device_name} 资源异常: {e}")
cleanup_time = (time.time() - cleanup_start) * 1000
# 第三步:彻底销毁设备实例
destroy_start = time.time()
self.logger.info(f"正在销毁 {device_name} 设备实例...")
# 从设备字典中移除
old_device = self.devices.pop(device_name, None)
if old_device:
# 强制删除引用,帮助垃圾回收
del old_device
self.logger.info(f"{device_name} 设备实例已销毁")
# 短暂等待,确保资源完全释放
time.sleep(0.2)
destroy_time = (time.time() - destroy_start) * 1000
# 第四步:重新创建设备实例
create_start = time.time()
self.logger.info(f"正在重新创建 {device_name} 设备实例...")
new_device = None
try:
# 根据设备类型重新创建实例
if device_name == 'camera':
try:
from .camera_manager import CameraManager
except ImportError:
from camera_manager import CameraManager
new_device = CameraManager(self.socketio, self.config_manager)
elif device_name == 'imu':
try:
from .imu_manager import IMUManager
except ImportError:
from imu_manager import IMUManager
new_device = IMUManager(self.socketio, self.config_manager)
elif device_name == 'pressure':
try:
from .pressure_manager import PressureManager
except ImportError:
from pressure_manager import PressureManager
new_device = PressureManager(self.socketio, self.config_manager)
elif device_name == 'femtobolt':
try:
from .femtobolt_manager import FemtoBoltManager
except ImportError:
from femtobolt_manager import FemtoBoltManager
new_device = FemtoBoltManager(self.socketio, self.config_manager)
else:
raise ValueError(f"未知的设备类型: {device_name}")
# 重新初始化
if device.initialize():
self.logger.info(f"设备 {device_name} 重启成功")
self._emit_event('device_restarted', {'device': device_name})
return True
else:
self.logger.error(f"设备 {device_name} 重启失败")
if new_device is None:
raise Exception("设备实例创建失败")
# 将新设备实例添加到设备字典
self.devices[device_name] = new_device
create_time = (time.time() - create_start) * 1000
self.logger.info(f"{device_name} 设备实例重新创建成功 (耗时: {create_time:.1f}ms)")
except Exception as e:
create_time = (time.time() - create_start) * 1000
self.logger.error(f"重新创建 {device_name} 设备实例失败: {e} (耗时: {create_time:.1f}ms)")
return False
# 第五步:初始化新设备实例
init_start = time.time()
self.logger.info(f"正在初始化新的 {device_name} 设备实例...")
if not new_device.initialize():
init_time = (time.time() - init_start) * 1000
self.logger.error(f"{device_name} 设备初始化失败 (耗时: {init_time:.1f}ms)")
# 初始化失败,从设备字典中移除
self.devices.pop(device_name, None)
return False
init_time = (time.time() - init_start) * 1000
self.logger.info(f"{device_name} 设备初始化成功 (耗时: {init_time:.1f}ms)")
# 第六步:如果之前在推流,则启动推流
stream_time = 0
if was_streaming and hasattr(new_device, 'start_streaming'):
stream_start = time.time()
self.logger.info(f"正在启动 {device_name} 设备推流...")
try:
if not new_device.start_streaming():
stream_time = (time.time() - stream_start) * 1000
self.logger.error(f"启动 {device_name} 设备推流失败 (耗时: {stream_time:.1f}ms)")
return False
stream_time = (time.time() - stream_start) * 1000
self.logger.info(f"{device_name} 设备推流已启动 (耗时: {stream_time:.1f}ms)")
except Exception as e:
stream_time = (time.time() - stream_start) * 1000
self.logger.error(f"启动 {device_name} 推流异常: {e} (耗时: {stream_time:.1f}ms)")
return False
# 计算总耗时并记录
total_time = (time.time() - restart_start) * 1000
self.logger.info(f"{device_name} 设备彻底重启完成 - 停止推流: {stop_time:.1f}ms, 清理资源: {cleanup_time:.1f}ms, 销毁实例: {destroy_time:.1f}ms, 创建实例: {create_time:.1f}ms, 初始化: {init_time:.1f}ms, 启动推流: {stream_time:.1f}ms, 总耗时: {total_time:.1f}ms")
return True
except Exception as e:
self.logger.error(f"重启设备 {device_name} 异常: {e}")
total_time = (time.time() - restart_start) * 1000
error_msg = f"彻底重启设备 {device_name} 异常: {e} (耗时: {total_time:.1f}ms)"
self.logger.error(error_msg)
return False
def _start_monitor(self):
@ -664,4 +788,184 @@ class DeviceCoordinator:
def __exit__(self, exc_type, exc_val, exc_tb):
"""上下文管理器出口"""
self.shutdown()
self.shutdown()
def test_restart_device(device_name=None):
"""
测试设备重启功能
该测试方法演示如何使用restart_device方法进行设备的彻底重启
包括模拟设备初始化推流重启和状态验证的完整流程
Args:
device_name (str, optional): 指定要测试的设备名称如果为None则自动选择第一个可用设备
可选值: 'camera', 'imu', 'pressure', 'femtobolt'
"""
import time
import threading
from unittest.mock import Mock
print("=" * 60)
print("设备协调器重启功能测试")
print("=" * 60)
# 创建模拟的SocketIO和配置管理器
mock_socketio = Mock()
mock_config_manager = Mock()
# 模拟配置数据
mock_config_manager.get_device_config.return_value = {
'camera': {'enabled': True, 'device_id': 0, 'fps': 30},
'imu': {'enabled': True, 'device_type': 'mock'},
'pressure': {'enabled': True, 'device_type': 'mock'},
'femtobolt': {'enabled': False}
}
try:
# 创建设备协调器实例
print("1. 创建设备协调器...")
coordinator = DeviceCoordinator(mock_socketio, mock_config_manager)
# 初始化设备协调器
print("2. 初始化设备协调器...")
if not coordinator.initialize():
print("❌ 设备协调器初始化失败")
return False
print("✅ 设备协调器初始化成功")
print(f" 已初始化设备: {list(coordinator.devices.keys())}")
# 等待设备稳定
time.sleep(1)
# 选择要测试的设备
available_devices = list(coordinator.devices.keys())
if not available_devices:
print("❌ 没有可用的设备进行测试")
return False
# 根据参数选择测试设备
if device_name:
if device_name in available_devices:
test_device = device_name
print(f"3. 使用指定的测试设备: {test_device}")
else:
print(f"❌ 指定的设备 '{device_name}' 不存在")
print(f" 可用设备: {available_devices}")
return False
else:
test_device = available_devices[0] # 选择第一个设备进行测试
print(f"3. 自动选择测试设备: {test_device}")
print(f" 可用设备: {available_devices}")
# 获取设备初始状态
device = coordinator.devices[test_device]
initial_streaming = getattr(device, 'is_streaming', False)
initial_connected = getattr(device, 'is_connected', False)
print(f" 设备初始状态 - 连接: {initial_connected}, 推流: {initial_streaming}")
# 如果设备未推流,先启动推流
if hasattr(device, 'start_streaming') and not initial_streaming:
print("4. 启动设备推流...")
if device.start_streaming():
print("✅ 设备推流启动成功")
time.sleep(0.5) # 等待推流稳定
else:
print("⚠️ 设备推流启动失败,继续测试")
# 记录重启前的设备实例ID
old_device_id = id(device)
print(f" 重启前设备实例ID: {old_device_id}")
# 执行设备重启
print("5. 执行设备重启...")
restart_start = time.time()
restart_success = coordinator.restart_device(test_device)
restart_time = (time.time() - restart_start) * 1000
if restart_success:
print(f"✅ 设备重启成功 (总耗时: {restart_time:.1f}ms)")
# 验证设备实例是否已更换
new_device = coordinator.devices.get(test_device)
if new_device:
new_device_id = id(new_device)
print(f" 重启后设备实例ID: {new_device_id}")
if new_device_id != old_device_id:
print("✅ 设备实例已成功更换")
else:
print("❌ 设备实例未更换,可能重启不彻底")
# 检查设备状态
new_connected = getattr(new_device, 'is_connected', False)
new_streaming = getattr(new_device, 'is_streaming', False)
print(f" 重启后设备状态 - 连接: {new_connected}, 推流: {new_streaming}")
# 验证推流状态恢复
if initial_streaming and new_streaming:
print("✅ 推流状态已正确恢复")
elif not initial_streaming and not new_streaming:
print("✅ 推流状态保持一致")
else:
print("⚠️ 推流状态与预期不符")
else:
print("❌ 重启后设备实例丢失")
else:
print(f"❌ 设备重启失败 (耗时: {restart_time:.1f}ms)")
print("8. 清理资源...")
coordinator.shutdown()
print("✅ 资源清理完成")
print("=" * 60)
print("测试完成")
print("=" * 60)
return restart_success
except Exception as e:
print(f"❌ 测试过程中发生异常: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
"""
直接运行此文件时执行设备重启测试
"""
print("启动设备协调器重启功能测试...")
# 检查命令行参数
device_name = None
try:
# 设置日志级别
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 执行测试
# 可选值: 'camera', 'imu', 'pressure', 'femtobolt'
success = test_restart_device('pressure')
if success:
print("\n🎉 所有测试通过!")
else:
print("\n❌ 测试失败,请检查日志")
except KeyboardInterrupt:
print("\n⚠️ 测试被用户中断")
except Exception as e:
print(f"\n❌ 测试启动失败: {e}")
import traceback
traceback.print_exc()

View File

@ -16,7 +16,7 @@ max_backups = 7
[CAMERA]
enabled = True
device_index = 3
device_index = 0
width = 1280
height = 720
fps = 30

View File

@ -863,61 +863,8 @@ class AppServer:
'message': f'不支持的设备类型: {device_name},支持的设备类型: {", ".join(supported_devices)}'
}), 400
result = self.config_manager.set_all_device_configs(data)
result = self.config_manager.set_all_device_configs(data)
# 如果配置设置成功,异步重启设备数据推送
# if result['success']:
# def restart_devices_async():
# """异步重启设备数据推送"""
# try:
# self.logger.info("设备配置更新成功,异步重启设备数据推送...")
# # 先停止当前的数据推送
# if self.is_pushing_data:
# self.stop_device_push_data()
# time.sleep(1) # 等待停止完成
# # 为每个设备管理器重新加载配置
# self.logger.info("重新加载设备配置...")
# reload_results = []
# for device_name, manager in self.device_managers.items():
# if manager is not None and hasattr(manager, 'reload_config'):
# try:
# success = manager.reload_config()
# reload_results.append(f"{device_name}: {'成功' if success else '失败'}")
# self.logger.info(f"{device_name}设备配置重新加载{'成功' if success else '失败'}")
# except Exception as e:
# reload_results.append(f"{device_name}: 异常 - {str(e)}")
# self.logger.error(f"{device_name}设备配置重新加载异常: {e}")
# else:
# reload_results.append(f"{device_name}: 跳过管理器未初始化或不支持reload_config")
# self.logger.info(f"配置重新加载结果: {'; '.join(reload_results)}")
# # 重新启动设备数据推送
# self.start_device_push_data()
# self.logger.info("设备配置更新并重启数据推送完成")
# # 通过SocketIO通知前端重启完成
# self.socketio.emit('device_restart_complete', {
# 'status': 'success',
# 'message': '设备重启完成',
# 'reload_results': reload_results
# }, namespace='/devices')
# except Exception as restart_error:
# self.logger.error(f"重启设备数据推送失败: {restart_error}")
# # 通过SocketIO通知前端重启失败
# self.socketio.emit('device_restart_complete', {
# 'status': 'error',
# 'message': f'设备重启失败: {str(restart_error)}'
# }, namespace='/devices')
# # 启动异步线程执行重启操作
# restart_thread = threading.Thread(target=restart_devices_async)
# restart_thread.daemon = True
# restart_thread.start()
# result['message'] = result.get('message', '') + ' 设备正在后台重启中,请稍候...'
status_code = 200 if result['success'] else 400
return jsonify(result), status_code
@ -1510,6 +1457,63 @@ class AppServer:
except Exception as e:
emit('test_status', {'status': 'error', 'message': str(e)}, namespace='/devices')
@self.socketio.on('restart_device', namespace='/devices')
def handle_restart_device(data):
"""重启特定设备"""
device_type = data.get('device_type')
self.logger.info(f'客户端请求重启{device_type}设备')
self.restart_device(device_type)
def restart_device(self, device_type: str):
"""重启指定类型的设备
Args:
device_type (str): 设备类型 (camera, imu, pressure, femtobolt)
"""
if not self.device_coordinator:
self.logger.error('设备协调器未初始化,无法重启设备')
return False
try:
self.logger.info(f'开始重启 {device_type} 设备...')
# 调用设备协调器的重启方法
success = self.device_coordinator.restart_device(device_type)
if success:
self.logger.info(f'{device_type} 设备重启成功')
# 发送重启成功事件到前端
self.socketio.emit('device_restart_message', {
'device_type': device_type,
'message': f'{device_type} 设备重启成功!',
'timestamp': time.time()
})
return True
else:
self.logger.error(f'{device_type} 设备重启失败!')
# 发送重启失败事件到前端
self.socketio.emit('device_restart_message', {
'device_type': device_type,
'message': f'{device_type} 设备重启失败!',
'timestamp': time.time()
})
return False
except Exception as e:
self.logger.error(f'重启 {device_type} 设备时发生异常: {str(e)}')
# 发送重启异常事件到前端
self.socketio.emit('device_restart_error', {
'device_type': device_type,
'error': str(e),
'message': f'重启 {device_type} 设备时发生异常',
'timestamp': time.time()
})
return False
def start_device_push_data(self):
"""开始设备数据推送"""
if self.is_pushing_data:

View File

@ -1984,9 +1984,9 @@ const startRecord = async () => { // 开始录屏
patient_id: patientInfo.value.sessionId,
//
creator_id: creatorId.value,
screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height-titile_height)],
camera_location:[Math.round(camera_location.x), Math.round(camera_location.y)+ titile_height, Math.round(camera_location.width), Math.round(camera_location.height-titile_height)],
femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height-titile_height)],
screen_location:[Math.round(screen_location.x), Math.round(screen_location.y) + titile_height, Math.round(screen_location.width), Math.round(screen_location.height)],
camera_location:[Math.round(camera_location.x), Math.round(camera_location.y)+ titile_height, Math.round(camera_location.width), Math.round(camera_location.height)],
femtobolt_location:[Math.round(femtobolt_location.x), Math.round(femtobolt_location.y) + titile_height, Math.round(femtobolt_location.width), Math.round(femtobolt_location.height)],
})
})