This commit is contained in:
limengnan 2025-08-19 15:28:35 +08:00
commit d39a2c7695
11 changed files with 212 additions and 57 deletions

4
.gitignore vendored
View File

@ -43,6 +43,10 @@ Thumbs.db
dist/ dist/
build/ build/
# 前端构建输出
frontend/src/renderer/dist/
frontend/src/renderer/dist-electron/
# 临时文件 # 临时文件
*.tmp *.tmp
*.temp *.temp

View File

@ -14,7 +14,8 @@ a = Analysis(
('dll/femtobolt/bin/ob_usb.dll', 'dll/femtobolt/bin'), # Orbbec USB库 ('dll/femtobolt/bin/ob_usb.dll', 'dll/femtobolt/bin'), # Orbbec USB库
('dll/femtobolt/bin/live555.dll', 'dll/femtobolt/bin'), # Live555库 ('dll/femtobolt/bin/live555.dll', 'dll/femtobolt/bin'), # Live555库
('dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml', 'dll/femtobolt/bin'), # Orbbec配置文件 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSense传感器库 ('dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml', 'dll/femtobolt/bin'), # Orbbec配置文件 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSense传感器库
('dll/smitsense/SMiTSenseUsbWrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类 ('dll/smitsense/Wrapper.dll', 'dll/smitsense'), # Wrapper
('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # Wrapper
], ],
hiddenimports=[ hiddenimports=[
'flask', 'flask',

View File

@ -42,7 +42,8 @@ a = Analysis(
('dll/femtobolt/bin/ob_usb.dll', 'dll/femtobolt/bin'), # Orbbec USB库 ('dll/femtobolt/bin/ob_usb.dll', 'dll/femtobolt/bin'), # Orbbec USB库
('dll/femtobolt/bin/live555.dll', 'dll/femtobolt/bin'), # Live555库 ('dll/femtobolt/bin/live555.dll', 'dll/femtobolt/bin'), # Live555库
('dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml', 'dll/femtobolt/bin'), # Orbbec配置文件 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSense传感器库 ('dll/femtobolt/bin/OrbbecSDKConfig_v1.0.xml', 'dll/femtobolt/bin'), # Orbbec配置文件 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSense传感器库
('dll/smitsense/SMiTSenseUsbWrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类 ('dll/smitsense/SMiTSenseUsb-F3.0.dll', 'dll/smitsense'), # SMiTSenseUsb库
('dll/smitsense/Wrapper.dll', 'dll/smitsense'), # SMiTSense传感器库包装类
], ],
hiddenimports=[ hiddenimports=[
'flask', 'flask',

View File

@ -29,7 +29,7 @@ depth_range_max = 1700
[DEVICES] [DEVICES]
imu_device_type = real imu_device_type = real
imu_port = COM3 imu_port = COM7
imu_baudrate = 9600 imu_baudrate = 9600
pressure_device_type = real pressure_device_type = real
pressure_use_mock = False pressure_use_mock = False

View File

@ -11,7 +11,7 @@ import json
import logging import logging
from typing import Dict, Any, Optional, Union from typing import Dict, Any, Optional, Union
from pathlib import Path from pathlib import Path
import sys
class ConfigManager: class ConfigManager:
"""配置管理器""" """配置管理器"""
@ -43,19 +43,38 @@ class ConfigManager:
Returns: Returns:
str: 配置文件路径 str: 配置文件路径
""" """
import sys
# 可能的配置文件路径 # 可能的配置文件路径
possible_paths = [ possible_paths = []
os.path.join(os.path.dirname(__file__), 'config.ini')
] # 如果是打包后的exe文件从exe文件同级目录获取
if getattr(sys, 'frozen', False):
# 打包后的exe文件路径
exe_dir = os.path.dirname(sys.executable)
possible_paths.append(os.path.join(exe_dir, 'config.ini'))
# 开发环境下的路径
possible_paths.extend([
os.path.join(os.path.dirname(__file__), 'config.ini'),
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.ini'), # backend/config.ini
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), 'config.ini') # 项目根目录/config.ini
])
for path in possible_paths: for path in possible_paths:
abs_path = os.path.abspath(path) abs_path = os.path.abspath(path)
if os.path.exists(abs_path): if os.path.exists(abs_path):
self.logger.info(f"找到配置文件: {abs_path}") self.logger.info(f"找到配置文件: {abs_path}")
return abs_path return abs_path
# 如果都找不到返回默认路径exe同级目录或backend目录
if getattr(sys, 'frozen', False):
default_path = os.path.join(os.path.dirname(sys.executable), 'config.ini')
else:
default_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'config.ini')
self.logger.warning(f"未找到配置文件,使用默认路径: {default_path}")
return default_path
def _load_config(self): def _load_config(self):
""" """
加载配置文件 加载配置文件

View File

@ -147,10 +147,10 @@ class AppServer:
engineio_logger=False, engineio_logger=False,
ping_timeout=60, ping_timeout=60,
ping_interval=25, ping_interval=25,
manage_session=True, # 启用会话管理,解决打包环境会话问题 manage_session=False,
always_connect=False, always_connect=False,
transports=['polling'], # 只使用polling避免打包环境websocket问题 transports=['polling', 'websocket'], # 优先使用polling
allow_upgrades=False, # 禁用升级到websocket allow_upgrades=True, # 允许升级到websocket
cookie=None # 禁用cookie cookie=None # 禁用cookie
) )
self.logger.info('SocketIO初始化成功') self.logger.info('SocketIO初始化成功')
@ -205,9 +205,9 @@ class AppServer:
self.logger.info('正在初始化设备管理器...') self.logger.info('正在初始化设备管理器...')
self.device_managers = { self.device_managers = {
'camera': CameraManager(self.socketio, self.config_manager), 'camera': CameraManager(self.socketio, self.config_manager),
'femtobolt': FemtoBoltManager(self.socketio, self.config_manager), # 'femtobolt': FemtoBoltManager(self.socketio, self.config_manager),
'imu': IMUManager(self.socketio, self.config_manager), # 'imu': IMUManager(self.socketio, self.config_manager),
'pressure': PressureManager(self.socketio, self.config_manager) # 'pressure': PressureManager(self.socketio, self.config_manager)
} }
# 为每个设备添加状态变化回调 # 为每个设备添加状态变化回调

View File

@ -0,0 +1,66 @@
import psutil
import time
from datetime import datetime
from pynvml import *
def get_memory_usage():
mem = psutil.virtual_memory()
return mem.percent, mem.used / (1024 ** 3), mem.total / (1024 ** 3)
def get_cpu_usage():
return psutil.cpu_percent(interval=1) # 获取CPU利用率百分比
def get_gpu_usage():
try:
nvmlInit()
device_count = nvmlDeviceGetCount()
gpu_stats = []
for i in range(device_count):
handle = nvmlDeviceGetHandleByIndex(i)
name = nvmlDeviceGetName(handle).decode("utf-8")
mem_info = nvmlDeviceGetMemoryInfo(handle)
util = nvmlDeviceGetUtilizationRates(handle)
gpu_stats.append({
"name": name,
"gpu_util": util.gpu,
"mem_util": util.memory,
"mem_used": mem_info.used / (1024 ** 3),
"mem_total": mem_info.total / (1024 ** 3)
})
nvmlShutdown()
return gpu_stats
except Exception:
return [{"name": "N/A", "gpu_util": 0, "mem_util": 0, "mem_used": 0, "mem_total": 0}]
def main():
log_file = "monitor_log.txt"
with open(log_file, "a", encoding="utf-8") as f:
while True:
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# CPU
cpu_percent = get_cpu_usage()
# 内存
mem_percent, mem_used, mem_total = get_memory_usage()
# GPU
gpu_stats = get_gpu_usage()
# 拼接日志内容
log_line = (f"[{now}] CPU: {cpu_percent:.1f}% | "
f"Memory: {mem_percent:.1f}% ({mem_used:.2f} GB / {mem_total:.2f} GB)")
for gpu in gpu_stats:
log_line += (f" | GPU: {gpu['name']} {gpu['gpu_util']}% "
f"(Mem {gpu['mem_used']:.2f} GB / {gpu['mem_total']:.2f} GB)")
# 输出
print(log_line)
f.write(log_line + "\n")
f.flush()
time.sleep(9) # 这里+CPU统计的1秒总体大约10秒一个周期
if __name__ == "__main__":
main()

View File

@ -0,0 +1,104 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
class FemtoBoltViewer:
def __init__(self, depth_min=900, depth_max=1300):
self.depth_min = depth_min
self.depth_max = depth_max
# 自定义colormap
colors = ['fuchsia', 'red', 'yellow', 'lime', 'cyan', 'blue',
'fuchsia', 'red', 'yellow', 'lime', 'cyan', 'blue']
self.cmap = LinearSegmentedColormap.from_list("custom_cmap", colors)
# SDK设备句柄
self.device_handle = None
self.pykinect = None
self.config = None
# 初始化matplotlib figure
plt.ion()
self.fig, self.ax = plt.subplots(figsize=(7, 7))
def _load_sdk(self):
"""加载FemtoBolt SDK"""
try:
import pykinect_azure as pykinect
self.pykinect = pykinect
# 配置DLL路径
base_dir = os.path.dirname(os.path.abspath(__file__))
dll_path = os.path.join(base_dir, "..", "dll", "femtobolt", "bin", "k4a.dll")
self.pykinect.initialize_libraries(track_body=False, module_k4a_path=dll_path)
return True
except Exception as e:
print(f"加载SDK失败: {e}")
return False
def _configure_device(self):
"""配置FemtoBolt设备"""
self.config = self.pykinect.default_configuration
self.config.depth_mode = self.pykinect.K4A_DEPTH_MODE_NFOV_UNBINNED
self.config.camera_fps = self.pykinect.K4A_FRAMES_PER_SECOND_15
self.config.synchronized_images_only = False
self.config.color_resolution = 0
self.device_handle = self.pykinect.start_device(config=self.config)
def _render_depth(self, depth_image: np.ndarray):
"""使用matplotlib绘制深度图带背景和自定义colormap"""
# 过滤深度范围
depth = np.where((depth_image >= self.depth_min) & (depth_image <= self.depth_max),
depth_image, 0)
# 屏蔽深度为0的部分
depth = np.ma.masked_equal(depth, 0)
# 背景图(灰色)
background = np.ones_like(depth) * 0.5
self.ax.clear()
# 绘制背景
self.ax.imshow(background, origin='lower', cmap='gray', alpha=0.3)
# 绘制白色栅格线
self.ax.grid(True, which='both', axis='both', color='white', linestyle='-', linewidth=1, zorder=0)
# 绘制深度等高线
self.ax.contourf(depth, levels=200, cmap=self.cmap, vmin=self.depth_min, vmax=self.depth_max, origin='upper', zorder=2)
plt.pause(0.001)
plt.draw()
def run(self):
if not self._load_sdk():
print("SDK加载失败退出")
return
self._configure_device()
print("FemtoBolt深度相机启动成功按 Ctrl+C 退出")
try:
while True:
capture = self.device_handle.update()
if capture is None:
continue
ret, depth_image = capture.get_depth_image()
if not ret or depth_image is None:
continue
self._render_depth(depth_image)
except KeyboardInterrupt:
print("退出程序")
finally:
self.device_handle.stop()
self.device_handle.close()
plt.close(self.fig)
if __name__ == "__main__":
viewer = FemtoBoltViewer(depth_min=900, depth_max=1300)
viewer.run()

View File

@ -1,40 +0,0 @@
[08/18 20:41:43.488456][debug][24904][Context.cpp:30] Context creating, work_dir=D:\Trae_space\BodyBalanceEvaluation\frontend\src\renderer\dist-electron\win-unpacked\resources\backend
[08/18 20:41:43.488655][debug][24904][Context.cpp:49] Config file version=1.1
[08/18 20:41:43.488739][debug][24904][FrameBufferManager.cpp:23] Max global frame buffer size updated! size=2048.000MB
[08/18 20:41:43.488782][info][24904][Context.cpp:68] Context created with config: default config!
[08/18 20:41:43.488823][info][24904][Context.cpp:73] Work directory=D:\Trae_space\BodyBalanceEvaluation\frontend\src\renderer\dist-electron\win-unpacked\resources\backend, SDK version=v1.10.11-20240724-aeaa107e5
[08/18 20:41:43.489074][debug][24904][DeviceManager.cpp:30] DeviceManager init ...
[08/18 20:41:43.489100][info][24904][MfPal.cpp:105] createObPal: create WinPal!
[08/18 20:41:43.489124][debug][24904][MfPal.cpp:110] WmfPal init ...
[08/18 20:41:43.520661][debug][24904][MfPal.cpp:117] WmfPal created!
[08/18 20:41:43.520721][debug][24904][DeviceManager.cpp:34] Enable USB Device Enumerator ...
[08/18 20:41:43.553521][debug][24904][EnumeratorLibusb.cpp:321] queryDevicesInfo done!
[08/18 20:41:43.553940][debug][24904][MfPal.cpp:216] Create WinEventDeviceWatcher!
[08/18 20:41:43.554220][debug][24904][UsbDeviceEnumerator.cpp:78] No matched usb device found!
[08/18 20:41:43.554276][info][24904][DeviceManager.cpp:15] Current found device(s): (0)
[08/18 20:41:43.554305][debug][24904][DeviceManager.cpp:52] DeviceManager construct done!
[08/18 20:41:43.554347][debug][24904][Context.cpp:81] Context destroying ...
[08/18 20:41:43.554360][debug][24904][DeviceManager.cpp:56] DeviceManager destroy ...
[08/18 20:41:43.554371][debug][24904][DeviceManager.cpp:64] DeviceManager Destructors done
[08/18 20:41:43.558317][debug][24904][MfPal.cpp:128] WmfPal destroyed!
[08/18 20:41:43.558735][info][24904][Context.cpp:84] Context destroyed
[08/18 20:43:03.299275][debug][29780][Context.cpp:30] Context creating, work_dir=D:\Trae_space\BodyBalanceEvaluation\frontend\src\renderer\dist-electron\win-unpacked\resources\backend
[08/18 20:43:03.299338][debug][29780][Context.cpp:49] Config file version=1.1
[08/18 20:43:03.299358][debug][29780][FrameBufferManager.cpp:23] Max global frame buffer size updated! size=2048.000MB
[08/18 20:43:03.299375][info][29780][Context.cpp:68] Context created with config: default config!
[08/18 20:43:03.299399][info][29780][Context.cpp:73] Work directory=D:\Trae_space\BodyBalanceEvaluation\frontend\src\renderer\dist-electron\win-unpacked\resources\backend, SDK version=v1.10.11-20240724-aeaa107e5
[08/18 20:43:03.299416][debug][29780][DeviceManager.cpp:30] DeviceManager init ...
[08/18 20:43:03.299428][info][29780][MfPal.cpp:105] createObPal: create WinPal!
[08/18 20:43:03.299441][debug][29780][MfPal.cpp:110] WmfPal init ...
[08/18 20:43:03.340690][debug][29780][MfPal.cpp:117] WmfPal created!
[08/18 20:43:03.340727][debug][29780][DeviceManager.cpp:34] Enable USB Device Enumerator ...
[08/18 20:43:03.367003][debug][29780][EnumeratorLibusb.cpp:321] queryDevicesInfo done!
[08/18 20:43:03.367322][debug][29780][MfPal.cpp:216] Create WinEventDeviceWatcher!
[08/18 20:43:03.367577][debug][29780][UsbDeviceEnumerator.cpp:78] No matched usb device found!
[08/18 20:43:03.367605][info][29780][DeviceManager.cpp:15] Current found device(s): (0)
[08/18 20:43:03.367629][debug][29780][DeviceManager.cpp:52] DeviceManager construct done!
[08/18 20:43:03.367646][debug][29780][Context.cpp:81] Context destroying ...
[08/18 20:43:03.367657][debug][29780][DeviceManager.cpp:56] DeviceManager destroy ...
[08/18 20:43:03.367667][debug][29780][DeviceManager.cpp:64] DeviceManager Destructors done
[08/18 20:43:03.369012][debug][29780][MfPal.cpp:128] WmfPal destroyed!
[08/18 20:43:03.369386][info][29780][Context.cpp:84] Context destroyed

View File

@ -927,7 +927,7 @@ function connectWebSocket() {
// Socket.IO // Socket.IO
socket = io(BACKEND_URL, { socket = io(BACKEND_URL, {
transports: ['polling'], // 使polling transports: ['websocket', 'polling'], // 使polling
timeout: 10000, timeout: 10000,
forceNew: true, forceNew: true,
reconnection: true, reconnection: true,
@ -937,7 +937,7 @@ function connectWebSocket() {
// //
devicesSocket = io(BACKEND_URL + '/devices', { devicesSocket = io(BACKEND_URL + '/devices', {
transports: ['polling'], // 使polling transports: ['websocket', 'polling'], // 使polling
timeout: 10000, timeout: 10000,
forceNew: true forceNew: true
}) })