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 @@
-
头部姿态
+
+ 头部姿态 + 电量{{ imuBatteryPercent }}% +
校准 @@ -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%;