diff --git a/backend/devices/imu_manager.py b/backend/devices/imu_manager.py index 243b105b..bc781a70 100644 --- a/backend/devices/imu_manager.py +++ b/backend/devices/imu_manager.py @@ -42,6 +42,7 @@ 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: @@ -107,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 @@ -156,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() @@ -176,11 +186,39 @@ 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: from bleak import BleakScanner @@ -312,6 +350,11 @@ 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) @@ -364,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 @@ -412,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 diff --git a/frontend/src/renderer/src/views/Detection.vue b/frontend/src/renderer/src/views/Detection.vue index 9932c0f3..5e7ccc51 100644 --- a/frontend/src/renderer/src/views/Detection.vue +++ b/frontend/src/renderer/src/views/Detection.vue @@ -84,7 +84,10 @@
-
校准
@@ -1155,6 +1158,7 @@ const headlist = ref({
tilt: 0,
pitch: 0
})
+const imuBatteryPercent = ref(null)
// 开始计时器
const startTimer = () => {
if (isRunning.value) return;
@@ -1706,6 +1710,14 @@ const IMU_CHANGE_EPS = 0.1 // 小于0.1°的变化忽略
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 }
@@ -2763,6 +2775,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%;