Merge branch 'dev-v15' of http://121.37.111.42:3000/ThbTech/BodyBalanceEvaluation into dev-v15
This commit is contained in:
commit
641bc9e8a0
@ -29,8 +29,8 @@ fourcc = MJPG
|
||||
backend = directshow
|
||||
|
||||
[CAMERA2]
|
||||
enable = False
|
||||
device_index = 2
|
||||
enable = True
|
||||
device_index = 1
|
||||
width = 1280
|
||||
height = 720
|
||||
fps = 30
|
||||
|
||||
@ -849,12 +849,20 @@ class DeviceCoordinator:
|
||||
self.logger.warning(f"设备 {device_name} 连接丢失")
|
||||
self.stats['device_errors'][device_name] += 1
|
||||
|
||||
if self.stats['reconnect_attempts'][device_name] >= 3:
|
||||
continue
|
||||
|
||||
now = time.time()
|
||||
if now - self._last_restart_ts[device_name] >= 15.0:
|
||||
if now - self._last_restart_ts[device_name] >= 50.0:
|
||||
self._last_restart_ts[device_name] = now
|
||||
self.logger.info(f"尝试重连设备: {device_name}")
|
||||
if self.restart_device(device_name):
|
||||
self.stats['device_errors'][device_name] = 0
|
||||
self.stats['reconnect_attempts'][device_name] = 0
|
||||
else:
|
||||
self.stats['reconnect_attempts'][device_name] += 1
|
||||
if self.stats['reconnect_attempts'][device_name] >= 3:
|
||||
self.logger.error(f"设备 {device_name} 重连失败已达3次,停止自动重试")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"检查设备 {device_name} 状态异常: {e}")
|
||||
|
||||
@ -33,6 +33,37 @@ class DeviceModel:
|
||||
self.isOpen = False
|
||||
self.callback_method = callback_method
|
||||
self.deviceData = {}
|
||||
self._battery_ts = 0.0
|
||||
|
||||
@staticmethod
|
||||
def _battery_percent_from_reg(reg_value: int) -> int:
|
||||
try:
|
||||
v = int(reg_value)
|
||||
except Exception:
|
||||
return 0
|
||||
if v > 396:
|
||||
return 100
|
||||
if v >= 393:
|
||||
return 90
|
||||
if v >= 387:
|
||||
return 75
|
||||
if v >= 382:
|
||||
return 60
|
||||
if v >= 379:
|
||||
return 50
|
||||
if v >= 377:
|
||||
return 40
|
||||
if v >= 373:
|
||||
return 30
|
||||
if v >= 370:
|
||||
return 20
|
||||
if v >= 368:
|
||||
return 15
|
||||
if v >= 350:
|
||||
return 10
|
||||
if v >= 340:
|
||||
return 5
|
||||
return 0
|
||||
|
||||
# region 获取设备数据 Obtain device data
|
||||
# 设置设备数据 Set device data
|
||||
@ -71,34 +102,59 @@ class DeviceModel:
|
||||
notify_characteristic = None
|
||||
|
||||
self.logger.info("正在匹配服务...")
|
||||
services = None
|
||||
for _ in range(3):
|
||||
await asyncio.sleep(0.3)
|
||||
services = []
|
||||
for i in range(10):
|
||||
tmp_services = None
|
||||
get_services = getattr(client, 'get_services', None)
|
||||
if callable(get_services):
|
||||
services = await get_services()
|
||||
else:
|
||||
try:
|
||||
tmp_services = await get_services()
|
||||
except Exception:
|
||||
tmp_services = None
|
||||
if not tmp_services:
|
||||
backend = getattr(client, "_backend", None)
|
||||
backend_get_services = getattr(backend, "get_services", None)
|
||||
if callable(backend_get_services):
|
||||
services = await backend_get_services()
|
||||
else:
|
||||
services = getattr(client, 'services', None)
|
||||
if services:
|
||||
try:
|
||||
tmp_services = await backend_get_services()
|
||||
except Exception:
|
||||
tmp_services = None
|
||||
if not tmp_services:
|
||||
tmp_services = getattr(client, 'services', None)
|
||||
|
||||
try:
|
||||
services = list(tmp_services) if tmp_services else []
|
||||
except Exception:
|
||||
services = []
|
||||
|
||||
for service in services:
|
||||
try:
|
||||
svc_uuid = str(getattr(service, "uuid", "") or "").lower()
|
||||
except Exception:
|
||||
svc_uuid = ""
|
||||
if svc_uuid == str(target_service_uuid).lower():
|
||||
self.logger.info(f"匹配到服务: {service}")
|
||||
self.logger.info("正在匹配特征...")
|
||||
chars = []
|
||||
try:
|
||||
chars = list(getattr(service, "characteristics", None) or [])
|
||||
except Exception:
|
||||
chars = []
|
||||
for characteristic in chars:
|
||||
try:
|
||||
chr_uuid = str(getattr(characteristic, "uuid", "") or "").lower()
|
||||
except Exception:
|
||||
chr_uuid = ""
|
||||
if chr_uuid == str(target_characteristic_uuid_read).lower():
|
||||
notify_characteristic = characteristic
|
||||
if chr_uuid == str(target_characteristic_uuid_write).lower():
|
||||
self.writer_characteristic = characteristic
|
||||
if notify_characteristic:
|
||||
break
|
||||
if notify_characteristic:
|
||||
break
|
||||
await asyncio.sleep(0.2)
|
||||
if not services:
|
||||
services = []
|
||||
for service in services:
|
||||
if service.uuid == target_service_uuid:
|
||||
self.logger.info(f"匹配到服务: {service}")
|
||||
self.logger.info("正在匹配特征...")
|
||||
for characteristic in service.characteristics:
|
||||
if characteristic.uuid == target_characteristic_uuid_read:
|
||||
notify_characteristic = characteristic
|
||||
if characteristic.uuid == target_characteristic_uuid_write:
|
||||
self.writer_characteristic = characteristic
|
||||
if notify_characteristic:
|
||||
break
|
||||
await asyncio.sleep(0.2 + i * 0.1)
|
||||
|
||||
if notify_characteristic:
|
||||
self.logger.info(f"匹配到特征: {notify_characteristic}")
|
||||
@ -110,14 +166,33 @@ class DeviceModel:
|
||||
# 保持连接打开 Keep connected and open
|
||||
try:
|
||||
while self.isOpen:
|
||||
try:
|
||||
if not bool(getattr(client, "is_connected", False)):
|
||||
self.isOpen = False
|
||||
break
|
||||
except Exception:
|
||||
self.isOpen = False
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
# 在退出时停止通知 Stop notification on exit
|
||||
await client.stop_notify(notify_characteristic.uuid)
|
||||
try:
|
||||
await client.stop_notify(notify_characteristic.uuid)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
self.logger.warning("未找到匹配的服务或特征")
|
||||
try:
|
||||
svc_list = []
|
||||
for s in services:
|
||||
try:
|
||||
svc_list.append(str(getattr(s, "uuid", "") or ""))
|
||||
except Exception:
|
||||
pass
|
||||
self.logger.warning(f"未找到匹配的服务或特征,可用服务: {svc_list}")
|
||||
except Exception:
|
||||
self.logger.warning("未找到匹配的服务或特征")
|
||||
raise RuntimeError("未找到匹配的服务或特征")
|
||||
|
||||
# 关闭设备 close Device
|
||||
@ -134,24 +209,41 @@ class DeviceModel:
|
||||
if len(self.TempBytes) == 1 and self.TempBytes[0] != 0x55:
|
||||
del self.TempBytes[0]
|
||||
continue
|
||||
if len(self.TempBytes) == 2 and self.TempBytes[1] != 0x61:
|
||||
del self.TempBytes[0]
|
||||
continue
|
||||
if len(self.TempBytes) == 20:
|
||||
self.processData(self.TempBytes)
|
||||
self.TempBytes.clear()
|
||||
|
||||
# 数据解析 data analysis
|
||||
def processData(self, Bytes):
|
||||
if Bytes[1] != 0x61:
|
||||
if Bytes[1] == 0x61:
|
||||
AngX = self.getSignInt16(Bytes[15] << 8 | Bytes[14]) / 32768 * 180
|
||||
AngY = self.getSignInt16(Bytes[17] << 8 | Bytes[16]) / 32768 * 180
|
||||
AngZ = self.getSignInt16(Bytes[19] << 8 | Bytes[18]) / 32768 * 180
|
||||
self.set("AngX", round(AngX, 3))
|
||||
self.set("AngY", round(AngY, 3))
|
||||
self.set("AngZ", round(AngZ, 3))
|
||||
self.callback_method(self)
|
||||
return
|
||||
|
||||
if Bytes[1] == 0x71:
|
||||
start_reg = int(Bytes[2])
|
||||
regs = []
|
||||
for i in range(8):
|
||||
base = 4 + i * 2
|
||||
if base + 1 >= len(Bytes):
|
||||
break
|
||||
regs.append(int(Bytes[base]) | (int(Bytes[base + 1]) << 8))
|
||||
for idx, val in enumerate(regs):
|
||||
self.set(f"Reg_{start_reg + idx:02X}", val)
|
||||
if regs and start_reg == 0x64:
|
||||
raw = int(regs[0])
|
||||
self.set("BatteryRaw", raw)
|
||||
self.set("BatteryVoltage", round(raw / 100.0, 2))
|
||||
self.set("BatteryPercent", int(self._battery_percent_from_reg(raw)))
|
||||
self._battery_ts = time.time()
|
||||
self.set("BatteryTS", self._battery_ts)
|
||||
self.callback_method(self)
|
||||
return
|
||||
AngX = self.getSignInt16(Bytes[15] << 8 | Bytes[14]) / 32768 * 180
|
||||
AngY = self.getSignInt16(Bytes[17] << 8 | Bytes[16]) / 32768 * 180
|
||||
AngZ = self.getSignInt16(Bytes[19] << 8 | Bytes[18]) / 32768 * 180
|
||||
self.set("AngX", round(AngX, 3))
|
||||
self.set("AngY", round(AngY, 3))
|
||||
self.set("AngZ", round(AngZ, 3))
|
||||
self.callback_method(self)
|
||||
|
||||
# 获得int16有符号数 Obtain int16 signed number
|
||||
@staticmethod
|
||||
@ -175,6 +267,26 @@ class DeviceModel:
|
||||
# 封装读取指令并向串口发送数据 Encapsulate read instructions and send data to the serial port
|
||||
await self.sendData(self.get_readBytes(regAddr))
|
||||
|
||||
async def readBattery(self, timeout: float = 2.0):
|
||||
if not bool(self.isOpen):
|
||||
return None
|
||||
client = getattr(self, "client", None)
|
||||
if client is None or not bool(getattr(client, "is_connected", False)):
|
||||
return None
|
||||
prev_ts = float(self.deviceData.get("BatteryTS", 0.0) or 0.0)
|
||||
await self.readReg(0x64)
|
||||
deadline = time.time() + max(0.1, float(timeout))
|
||||
while time.time() < deadline:
|
||||
cur_ts = float(self.deviceData.get("BatteryTS", 0.0) or 0.0)
|
||||
if cur_ts > prev_ts:
|
||||
return {
|
||||
"raw": self.deviceData.get("BatteryRaw"),
|
||||
"voltage": self.deviceData.get("BatteryVoltage"),
|
||||
"percent": self.deviceData.get("BatteryPercent"),
|
||||
}
|
||||
await asyncio.sleep(0.05)
|
||||
return None
|
||||
|
||||
# 写入寄存器 Write Register
|
||||
async def writeReg(self, regAddr, sValue):
|
||||
# 解锁 unlock
|
||||
|
||||
@ -42,7 +42,9 @@ class BleIMUDevice:
|
||||
self._device_model = None
|
||||
self._open_task = None
|
||||
self._main_task = None
|
||||
self._battery_task = None
|
||||
self._last_update_ts = None
|
||||
self._last_ble_device = None
|
||||
try:
|
||||
from . import device_model as wit_device_model
|
||||
except Exception:
|
||||
@ -106,7 +108,8 @@ class BleIMUDevice:
|
||||
'pitch': self.last_data['roll'] # pitch 对应俯仰
|
||||
},
|
||||
'temperature': self.last_data.get('temperature', 25.0),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'battery': self.last_data.get('battery'),
|
||||
}
|
||||
return self.apply_calibration(raw) if apply_calibration else raw
|
||||
|
||||
@ -155,6 +158,14 @@ class BleIMUDevice:
|
||||
|
||||
async def _disconnect(self):
|
||||
try:
|
||||
if self._battery_task is not None and not self._battery_task.done():
|
||||
self._battery_task.cancel()
|
||||
try:
|
||||
await self._battery_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
if self._device_model is not None:
|
||||
try:
|
||||
self._device_model.closeDevice()
|
||||
@ -175,9 +186,38 @@ class BleIMUDevice:
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self._battery_task = None
|
||||
self._open_task = None
|
||||
self._device_model = None
|
||||
self._last_update_ts = None
|
||||
self._connected = False
|
||||
|
||||
async def _battery_poll_loop(self, interval_s: float = 30.0):
|
||||
try:
|
||||
await asyncio.sleep(1.0)
|
||||
while self.running:
|
||||
dm = self._device_model
|
||||
if dm is None or not bool(getattr(dm, "isOpen", False)):
|
||||
await asyncio.sleep(1.0)
|
||||
continue
|
||||
try:
|
||||
battery = await dm.readBattery(timeout=2.0)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except Exception:
|
||||
battery = None
|
||||
if isinstance(battery, dict):
|
||||
battery_payload = {
|
||||
"raw": battery.get("raw"),
|
||||
"voltage": battery.get("voltage"),
|
||||
"percent": battery.get("percent"),
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
with self._lock:
|
||||
self.last_data["battery"] = battery_payload
|
||||
await asyncio.sleep(max(1.0, float(interval_s)))
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def _connect_and_listen(self):
|
||||
try:
|
||||
@ -187,46 +227,89 @@ class BleIMUDevice:
|
||||
self.running = False
|
||||
return
|
||||
|
||||
async def find_device() -> Optional[Any]:
|
||||
scan_timeout_s = 30.0
|
||||
if self.ble_name:
|
||||
find_by_name = getattr(BleakScanner, "find_device_by_name", None)
|
||||
if callable(find_by_name):
|
||||
try:
|
||||
device = await find_by_name(self.ble_name, timeout=scan_timeout_s)
|
||||
if device is not None:
|
||||
if self.mac_address and (getattr(device, "address", "") or "").lower() != self.mac_address.lower():
|
||||
return None
|
||||
return device
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
found = await BleakScanner.discover(timeout=scan_timeout_s)
|
||||
except Exception:
|
||||
found = []
|
||||
if self.ble_name:
|
||||
for d in found:
|
||||
if (getattr(d, "name", None) or "") != self.ble_name:
|
||||
continue
|
||||
if self.mac_address and (getattr(d, "address", "") or "").lower() != self.mac_address.lower():
|
||||
continue
|
||||
return d
|
||||
if self.mac_address:
|
||||
target = self.mac_address.lower()
|
||||
for d in found:
|
||||
addr = getattr(d, "address", "") or ""
|
||||
if addr.lower() == target:
|
||||
async def find_device(total_timeout_s: float = 30.0) -> Optional[Any]:
|
||||
if self._last_ble_device is not None:
|
||||
return self._last_ble_device
|
||||
|
||||
find_by_address = getattr(BleakScanner, "find_device_by_address", None)
|
||||
find_by_name = getattr(BleakScanner, "find_device_by_name", None)
|
||||
|
||||
deadline = time.time() + max(0.1, total_timeout_s)
|
||||
attempt = 0
|
||||
|
||||
async def _match_discover(timeout_s: float) -> Optional[Any]:
|
||||
try:
|
||||
found = await BleakScanner.discover(timeout=timeout_s)
|
||||
except Exception:
|
||||
found = []
|
||||
|
||||
if self.mac_address:
|
||||
target = self.mac_address.lower()
|
||||
for d in found:
|
||||
addr = (getattr(d, "address", "") or "").lower()
|
||||
if addr == target:
|
||||
return d
|
||||
|
||||
if self.ble_name:
|
||||
for d in found:
|
||||
if (getattr(d, "name", None) or "") != self.ble_name:
|
||||
continue
|
||||
if self.mac_address and (getattr(d, "address", "") or "").lower() != self.mac_address.lower():
|
||||
continue
|
||||
return d
|
||||
candidates = [d for d in found if (getattr(d, "name", "") or "").startswith("WT")]
|
||||
if len(candidates) == 1:
|
||||
return candidates[0]
|
||||
|
||||
candidates = [d for d in found if (getattr(d, "name", "") or "").startswith("WT")]
|
||||
if len(candidates) == 1:
|
||||
return candidates[0]
|
||||
return None
|
||||
|
||||
while time.time() < deadline and self.running:
|
||||
attempt += 1
|
||||
remaining = deadline - time.time()
|
||||
per_try = min(5.0, max(0.2, remaining))
|
||||
|
||||
strat = attempt % 3
|
||||
device = None
|
||||
|
||||
if strat == 1 and self.ble_name and callable(find_by_name):
|
||||
try:
|
||||
device = await find_by_name(self.ble_name, timeout=per_try)
|
||||
except Exception:
|
||||
device = None
|
||||
|
||||
if device is None and strat == 2 and self.mac_address and callable(find_by_address):
|
||||
try:
|
||||
device = await find_by_address(self.mac_address, timeout=per_try)
|
||||
except TypeError:
|
||||
try:
|
||||
device = await find_by_address(self.mac_address, cb=dict(use_bdaddr=False))
|
||||
except Exception:
|
||||
device = None
|
||||
except Exception:
|
||||
device = None
|
||||
|
||||
if device is None:
|
||||
device = await _match_discover(per_try)
|
||||
|
||||
if device is not None:
|
||||
if self.mac_address and (getattr(device, "address", "") or "").lower() != self.mac_address.lower():
|
||||
device = None
|
||||
elif self.ble_name and getattr(device, "name", None) not in (None, "") and getattr(device, "name", None) != self.ble_name:
|
||||
device = None
|
||||
|
||||
if device is not None:
|
||||
self._last_ble_device = device
|
||||
return device
|
||||
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
return None
|
||||
|
||||
while self.running:
|
||||
try:
|
||||
attempt_ts = time.perf_counter()
|
||||
logger.info(f"BLE IMU开始扫描并连接: name={self.ble_name}, mac={self.mac_address}")
|
||||
device = await find_device()
|
||||
device = await find_device(total_timeout_s=30.0)
|
||||
|
||||
if device is None:
|
||||
logger.info(f"BLE IMU扫描未发现设备 (耗时: {(time.perf_counter()-attempt_ts)*1000:.1f}ms)")
|
||||
@ -236,13 +319,14 @@ class BleIMUDevice:
|
||||
device_addr = getattr(device, "address", None)
|
||||
device_name = getattr(device, "name", None)
|
||||
logger.info(f"BLE IMU发现设备 (耗时: {(time.perf_counter()-attempt_ts)*1000:.1f}ms, address={device_addr}, name={device_name})")
|
||||
self._last_ble_device = device
|
||||
self._connected = False
|
||||
self._last_update_ts = None
|
||||
self._device_model = self._wit_device_model.DeviceModel("WitMotionBle5.0", device, self._on_device_update)
|
||||
self._open_task = asyncio.create_task(self._device_model.openDevice())
|
||||
|
||||
ready = False
|
||||
ready_timeout_s = 20.0
|
||||
ready_timeout_s = 30.0
|
||||
deadline = time.time() + ready_timeout_s
|
||||
while self.running and time.time() < deadline:
|
||||
if self._open_task is not None and self._open_task.done():
|
||||
@ -266,23 +350,42 @@ class BleIMUDevice:
|
||||
continue
|
||||
|
||||
logger.info(f"BLE IMU连接并开始产出数据 (耗时: {(time.perf_counter()-attempt_ts)*1000:.1f}ms)")
|
||||
try:
|
||||
if self._battery_task is None or self._battery_task.done():
|
||||
self._battery_task = asyncio.create_task(self._battery_poll_loop(interval_s=30.0))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
while self.running and self._open_task is not None and not self._open_task.done():
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
await self._disconnect()
|
||||
self._connected = False
|
||||
self._last_ble_device = None
|
||||
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
logger.error(f"BLE IMU连接/监听失败: {e}", exc_info=True)
|
||||
self._connected = False
|
||||
self._last_ble_device = None
|
||||
await asyncio.sleep(2.0)
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
return self._connected
|
||||
dm = self._device_model
|
||||
if dm is None:
|
||||
return False
|
||||
if not bool(getattr(dm, 'isOpen', False)):
|
||||
return False
|
||||
client = getattr(dm, 'client', None)
|
||||
if client is not None:
|
||||
try:
|
||||
if not bool(getattr(client, 'is_connected', False)):
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def has_received_data(self) -> bool:
|
||||
@ -304,7 +407,13 @@ class MockIMUDevice:
|
||||
'roll': 0.0,
|
||||
'pitch': 0.0,
|
||||
'yaw': 0.0,
|
||||
'temperature': 25.0
|
||||
'temperature': 25.0,
|
||||
'battery': {
|
||||
"raw": None,
|
||||
"voltage": None,
|
||||
"percent": 100,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
},
|
||||
}
|
||||
self._phase = 0.0
|
||||
self._last_update_ts = None
|
||||
@ -352,7 +461,8 @@ class MockIMUDevice:
|
||||
'pitch': self.last_data['roll']
|
||||
},
|
||||
'temperature': self.last_data.get('temperature', 25.0),
|
||||
'timestamp': datetime.now().isoformat()
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'battery': self.last_data.get('battery'),
|
||||
}
|
||||
return self.apply_calibration(raw) if apply_calibration else raw
|
||||
|
||||
@ -815,8 +925,6 @@ class IMUManager(BaseDevice):
|
||||
self.update_heartbeat()
|
||||
except Exception:
|
||||
self.update_heartbeat()
|
||||
else:
|
||||
self.update_heartbeat()
|
||||
return connected
|
||||
|
||||
if hasattr(self.imu_device, 'ser') and getattr(self.imu_device, 'ser', None):
|
||||
|
||||
@ -4,6 +4,7 @@ import time
|
||||
from statistics import mean
|
||||
|
||||
import bleak
|
||||
import device_model
|
||||
|
||||
|
||||
async def find_device_by_address(address: str, timeout_s: float):
|
||||
@ -56,13 +57,105 @@ def parse_args():
|
||||
parser.add_argument("--runs", type=int, default=10)
|
||||
parser.add_argument("--timeout", type=float, default=30.0)
|
||||
parser.add_argument("--cooldown", type=float, default=0.3)
|
||||
parser.add_argument("--mode", choices=["mac", "name", "both"], default="both")
|
||||
parser.add_argument("--mode", choices=["mac", "name", "both", "isopen", "battery"], default="both")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
async def main():
|
||||
args = parse_args()
|
||||
|
||||
if args.mode == "isopen":
|
||||
device = await find_device_by_name(args.name, args.timeout)
|
||||
if device is None:
|
||||
device = await find_device_by_address(args.address, args.timeout)
|
||||
if device is None:
|
||||
print("FAIL: 未发现设备")
|
||||
return
|
||||
addr = getattr(device, "address", None)
|
||||
name = getattr(device, "name", None)
|
||||
if args.address and addr and addr.lower() != args.address.lower():
|
||||
print(f"FAIL: 发现设备地址不匹配 found={addr} expected={args.address}")
|
||||
return
|
||||
print(f"FOUND address={addr} name={name}")
|
||||
|
||||
first_frame = asyncio.Event()
|
||||
|
||||
def on_update(dm):
|
||||
if not first_frame.is_set():
|
||||
first_frame.set()
|
||||
|
||||
dm = device_model.DeviceModel("imu_test", device, on_update)
|
||||
task = asyncio.create_task(dm.openDevice())
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(first_frame.wait(), timeout=30.0)
|
||||
print(f"OPENED isOpen={dm.isOpen} client_connected={bool(getattr(getattr(dm, 'client', None), 'is_connected', False))}")
|
||||
except Exception:
|
||||
print(f"OPEN_TIMEOUT isOpen={dm.isOpen} client_connected={bool(getattr(getattr(dm, 'client', None), 'is_connected', False))}")
|
||||
|
||||
while True:
|
||||
client_connected = bool(getattr(getattr(dm, "client", None), "is_connected", False))
|
||||
print(f"STATUS isOpen={dm.isOpen} client_connected={client_connected}")
|
||||
if not dm.isOpen:
|
||||
break
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(task, timeout=5.0)
|
||||
except Exception:
|
||||
try:
|
||||
task.cancel()
|
||||
except Exception:
|
||||
pass
|
||||
print("DONE")
|
||||
return
|
||||
|
||||
if args.mode == "battery":
|
||||
device = await find_device_by_name(args.name, args.timeout)
|
||||
if device is None:
|
||||
device = await find_device_by_address(args.address, args.timeout)
|
||||
if device is None:
|
||||
print("FAIL: 未发现设备")
|
||||
return
|
||||
addr = getattr(device, "address", None)
|
||||
name = getattr(device, "name", None)
|
||||
if args.address and addr and addr.lower() != args.address.lower():
|
||||
print(f"FAIL: 发现设备地址不匹配 found={addr} expected={args.address}")
|
||||
return
|
||||
print(f"FOUND address={addr} name={name}")
|
||||
|
||||
first_frame = asyncio.Event()
|
||||
|
||||
def on_update(dm):
|
||||
if not first_frame.is_set():
|
||||
first_frame.set()
|
||||
|
||||
dm = device_model.DeviceModel("imu_test", device, on_update)
|
||||
task = asyncio.create_task(dm.openDevice())
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(first_frame.wait(), timeout=30.0)
|
||||
print("OPENED")
|
||||
except Exception:
|
||||
print("OPEN_TIMEOUT")
|
||||
|
||||
info = await dm.readBattery(timeout=3.0)
|
||||
print(f"BATTERY {info}")
|
||||
|
||||
try:
|
||||
dm.closeDevice()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
await asyncio.wait_for(task, timeout=5.0)
|
||||
except Exception:
|
||||
try:
|
||||
task.cancel()
|
||||
except Exception:
|
||||
pass
|
||||
print("DONE")
|
||||
return
|
||||
|
||||
if args.mode in ("mac", "both"):
|
||||
await run_trials(
|
||||
"mac",
|
||||
|
||||
@ -270,6 +270,10 @@ class AppServer:
|
||||
self.logger.info('开始初始化设备...')
|
||||
if self.device_coordinator.initialize():
|
||||
self.logger.info('设备协调器初始化完成')
|
||||
try:
|
||||
self.device_coordinator.set_status_change_callback(self._on_device_status_change)
|
||||
except Exception as e:
|
||||
self.logger.error(f'注册设备状态变化回调失败: {e}')
|
||||
# 获取设备管理器实例
|
||||
self.device_managers = self.device_coordinator.get_device_managers()
|
||||
|
||||
@ -1969,6 +1973,10 @@ class AppServer:
|
||||
|
||||
try:
|
||||
self.logger.info(f'开始重启 {device_type} 设备...')
|
||||
try:
|
||||
self.broadcast_device_status(device_type, False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 调用设备协调器的重启方法
|
||||
with self._get_device_lock(device_type):
|
||||
@ -1976,6 +1984,10 @@ class AppServer:
|
||||
|
||||
if success:
|
||||
self.logger.info(f'{device_type} 设备重启成功')
|
||||
try:
|
||||
self.broadcast_device_status(device_type, True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 发送重启成功事件到前端
|
||||
self.socketio.emit('device_restart_message', {
|
||||
@ -1986,6 +1998,10 @@ class AppServer:
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f'{device_type} 设备重启失败!')
|
||||
try:
|
||||
self.broadcast_device_status(device_type, False)
|
||||
except Exception:
|
||||
pass
|
||||
# 发送重启失败事件到前端
|
||||
self.socketio.emit('device_restart_message', {
|
||||
'device_type': device_type,
|
||||
@ -2255,7 +2271,7 @@ def main():
|
||||
"""主函数"""
|
||||
# 解析命令行参数
|
||||
parser = argparse.ArgumentParser(description='Body Balance Evaluation System Backend')
|
||||
parser.add_argument('--host', default='localhost', help='Host address to bind to')
|
||||
parser.add_argument('--host', default='0.0.0.0', help='Host address to bind to')
|
||||
parser.add_argument('--port', type=int, default=5000, help='Port number to bind to')
|
||||
parser.add_argument('--debug', action='store_true', help='Enable debug mode')
|
||||
args = parser.parse_args()
|
||||
|
||||
@ -35,7 +35,7 @@ chart_dpi = 300
|
||||
export_format = csv
|
||||
|
||||
[SECURITY]
|
||||
secret_key = f50c705c26a963701a4832ae3d69a091674f587a4b02da8b1c59909c0bd312fe
|
||||
secret_key = 855842922ac3d1747493bcf40f0b2534387ac6304b903c901cf980e4059d5150
|
||||
session_timeout = 3600
|
||||
max_login_attempts = 5
|
||||
|
||||
|
||||
@ -83,7 +83,10 @@
|
||||
<div class="body-title-display">
|
||||
<div class="body-son-display">
|
||||
<img src="@/assets/detection/title2.png" alt="" style="margin-right: 8px;">
|
||||
<div class="body-posture-text">头部姿态</div>
|
||||
<div class="body-posture-text">
|
||||
头部姿态
|
||||
<span v-if="imuBatteryPercent !== null" class="imu-battery-percent">电量{{ imuBatteryPercent }}%</span>
|
||||
</div>
|
||||
<div class="calibration-zero" @click="calibrationClick" style="margin-left:20px">
|
||||
<img src="@/assets/detection/calibration.png" style="margin-right:7px">
|
||||
校准
|
||||
@ -1230,6 +1233,7 @@ const headlist = ref({
|
||||
tilt: 0,
|
||||
pitch: 0
|
||||
})
|
||||
const imuBatteryPercent = ref(null)
|
||||
// 开始计时器
|
||||
const startTimer = () => {
|
||||
if (isRunning.value) return;
|
||||
@ -1782,6 +1786,14 @@ function handleIMUData(data) {
|
||||
try {
|
||||
if (!data) return
|
||||
|
||||
const batteryPercent = data && data.battery && data.battery.percent
|
||||
if (batteryPercent !== undefined && batteryPercent !== null) {
|
||||
const v = Number(batteryPercent)
|
||||
if (!Number.isNaN(v) && Number.isFinite(v)) {
|
||||
imuBatteryPercent.value = Math.max(0, Math.min(100, Math.round(v)))
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容两种载荷结构:
|
||||
// 1) { rotation, tilt, pitch }
|
||||
// 2) { head_pose: { rotation, tilt, pitch } }
|
||||
@ -2620,17 +2632,28 @@ function refreshClick(type) {
|
||||
ElMessage.warning(`🚀 发送重启设备请求...`)
|
||||
if (devicesSocket && devicesSocket.connected) {
|
||||
if(type == 'camera1'){
|
||||
camera1Status.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'camera1' })
|
||||
}else if(type == 'camera2'){
|
||||
camera2Status.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'camera2' })
|
||||
}else if(type == 'femtobolt'){
|
||||
femtoboltStatus.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'femtobolt' })
|
||||
}else if(type == 'imu'){
|
||||
imuStatus.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'imu' })
|
||||
}else if(type == 'pressure'){
|
||||
pressureStatus.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'pressure' })
|
||||
}else if(type == 'remote'){
|
||||
remoteStatus.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'remote' })
|
||||
}else if(type == 'camera'){
|
||||
camera1Status.value = '未连接'
|
||||
camera2Status.value = '未连接'
|
||||
devicesSocket.emit('restart_device', { device_type: 'camera1' })
|
||||
devicesSocket.emit('restart_device', { device_type: 'camera2' })
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Socket服务未连接,无法重启设备!')
|
||||
@ -2841,6 +2864,13 @@ function viewClick(e){
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
}
|
||||
.imu-battery-percent{
|
||||
margin-left: 10px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.body-header-bottombox{
|
||||
width: 100%;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user