diff --git a/android/app/src/main/kotlin/com/lxh/battery_health/BatteryManagerHelper.kt b/android/app/src/main/kotlin/com/lxh/battery_health/BatteryManagerHelper.kt new file mode 100644 index 0000000..5feee63 --- /dev/null +++ b/android/app/src/main/kotlin/com/lxh/battery_health/BatteryManagerHelper.kt @@ -0,0 +1,51 @@ +package com.lxh.battery_health + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.BatteryManager + +class BatteryManagerHelper(private val context: Context) { + + fun getBatteryData(): Map { + val bm = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager + + // 电流(µA)。部分机型充电为负、放电为正,部分相反 + val rawCurrentUa = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CURRENT_NOW) + + // 电压 / 状态从 ACTION_BATTERY_CHANGED 广播里拿(比 BATTERY_PROPERTY_VOLTAGE 兼容性好) + val intent = context.registerReceiver( + null, + IntentFilter(Intent.ACTION_BATTERY_CHANGED) + ) + val voltageMv = intent?.getIntExtra(BatteryManager.EXTRA_VOLTAGE, -1) ?: -1 + val status = intent?.getIntExtra(BatteryManager.EXTRA_STATUS, -1) ?: -1 + val plugged = intent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1 + val temperatureTenth = intent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, -1) ?: -1 + + val isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || + status == BatteryManager.BATTERY_STATUS_FULL + + // 符号规约:充电为正 + val normalizedUa = when { + rawCurrentUa == Int.MIN_VALUE -> 0 + isCharging && rawCurrentUa < 0 -> -rawCurrentUa + !isCharging && rawCurrentUa > 0 -> -rawCurrentUa + else -> rawCurrentUa + } + + // 功率 W = V × A = (mV / 1000) × (µA / 1_000_000) + val powerW = if (voltageMv > 0 && normalizedUa != 0) { + (voltageMv / 1000.0) * (normalizedUa / 1_000_000.0) + } else 0.0 + + return mapOf( + "voltageMv" to voltageMv, + "currentUa" to normalizedUa, + "powerW" to powerW, + "temperatureC" to if (temperatureTenth > 0) temperatureTenth / 10.0 else 0.0, + "plugged" to plugged, // 0=未插, 1=AC, 2=USB, 4=WIRELESS + "isCharging" to isCharging, + ) + } +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt b/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt index f232060..ead1fde 100644 --- a/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt +++ b/android/app/src/main/kotlin/com/lxh/battery_health/MainActivity.kt @@ -255,26 +255,35 @@ private fun clearRefreshRate() { // 充电状态通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHARGE_CHANNEL) - .setMethodCallHandler { call, result -> - if (call.method == "getChargeState") { - val isCharging = getBatteryStatus() - val chargeType = if (isCharging) { - val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) - val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1 - when (plug) { - BatteryManager.BATTERY_PLUGGED_AC -> "AC" - BatteryManager.BATTERY_PLUGGED_USB -> "USB" - BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS" - else -> "UNKNOWN" - } - } else { - "NONE" + .setMethodCallHandler { call, result -> + when (call.method) { + "getChargeState" -> { + val isCharging = getBatteryStatus() + val chargeType = if (isCharging) { + val batteryIntent = registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + val plug = batteryIntent?.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) ?: -1 + when (plug) { + BatteryManager.BATTERY_PLUGGED_AC -> "AC" + BatteryManager.BATTERY_PLUGGED_USB -> "USB" + BatteryManager.BATTERY_PLUGGED_WIRELESS -> "WIRELESS" + else -> "UNKNOWN" } - result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType)) } else { - result.notImplemented() + "NONE" + } + result.success(mapOf("isCharging" to isCharging, "chargeType" to chargeType)) + } + // 🆕 新增分支 + "getBatteryManagerData" -> { + try { + result.success(BatteryManagerHelper(applicationContext).getBatteryData()) + } catch (e: Exception) { + result.error("BM_ERROR", e.message, null) } } + else -> result.notImplemented() + } + } // SAF 通道 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, SAF_CHANNEL) diff --git a/lib/pages/battery_health_page.dart b/lib/pages/battery_health_page.dart index bae9f5f..83d0d7b 100644 --- a/lib/pages/battery_health_page.dart +++ b/lib/pages/battery_health_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/services.dart'; import 'package:device_info_plus/device_info_plus.dart'; import '../theme/theme_service.dart'; import '../theme/app_colors.dart'; +import '../services/battery_manager_service.dart'; // --- 本地导入 --- import '../models/device_matching.dart'; @@ -64,6 +65,9 @@ class _BatteryHealthPageState extends State { int? _wiredLevel; bool _isWireless = false; DateTime? _chargingStartTime; + double _bmPower = 0.0; + double _bmVolt = 0.0; + double _bmCurr = 0.0; // ---------- SAF 状态 ---------- final SafStorageService _safService = SafStorageService(); @@ -80,6 +84,7 @@ class _BatteryHealthPageState extends State { // 轮询定时器(15秒) Timer? _pollTimer; + Timer? _bmTimer; // ---------- 生命周期 ---------- @override @@ -124,7 +129,7 @@ class _BatteryHealthPageState extends State { readBatteryCapacity(); } }); - + _startBmPolling(); // ← 调用,不是定义 dev.log('🔵 ========== initState 结束 ==========', name: 'BatteryHealth'); } @@ -132,6 +137,7 @@ class _BatteryHealthPageState extends State { void dispose() { _chargeEventSubscription?.cancel(); _pollTimer?.cancel(); + _bmTimer?.cancel(); _dbService.database.then((db) => db.close()).ignore(); super.dispose(); } @@ -252,6 +258,9 @@ class _BatteryHealthPageState extends State { _chargeCurr = 0.0; _wiredLevel = null; _chargingStartTime = null; + _bmPower = 0.0; // 🆕 + _bmVolt = 0.0; // 🆕 + _bmCurr = 0.0; // 🆕 return; } @@ -610,18 +619,34 @@ class _BatteryHealthPageState extends State { } } + // ---------- BM 轮询(1秒) ---------- + void _startBmPolling() { + _bmTimer?.cancel(); + _bmTimer = Timer.periodic(const Duration(seconds: 1), (_) async { + if (!_isCharging) return; + try { + final d = await BatteryManagerService.fetch(); + if (!mounted) return; + setState(() { + _bmPower = d.powerW; + _bmVolt = d.voltageV; + _bmCurr = d.currentA; + }); + } catch (e) { + // 静默降级,保留上一次的值 + } + }); + } + // ---------- 显示辅助 ---------- String _getDisplayPower() { if (!_isCharging) return ''; - if (_isWireless) { - return ChargeUtils.getDisplayPowerWireless(_chargePower); - } else { - return ChargeUtils.getMainDisplayPowerWired( - _chargePower, - _wiredLevel, - _chargeType, - ); - } + return ChargeUtils.getMainDisplayPowerBm( + _bmPower, + _wiredLevel, + _chargeType, + _isWireless, + ); } String _getChargeTypeLabel() { @@ -820,6 +845,9 @@ class _BatteryHealthPageState extends State { volt: _chargeVolt, curr: _chargeCurr, wiredLevel: _wiredLevel, + bmPower: _bmPower, // 🆕 + bmVolt: _bmVolt, // 🆕 + bmCurr: _bmCurr, // 🆕 ), ), ); diff --git a/lib/services/battery_manager_service.dart b/lib/services/battery_manager_service.dart new file mode 100644 index 0000000..9308b4b --- /dev/null +++ b/lib/services/battery_manager_service.dart @@ -0,0 +1,57 @@ +import 'package:flutter/services.dart'; + +/// BatteryManager 实时数据(电池端:电压 / 电流 / 功率) +class BatteryManagerData { + final int voltageMv; + final int currentUa; + final double powerW; + final double temperatureC; + final int plugged; // 0=未插, 1=AC, 2=USB, 4=WIRELESS + final bool isCharging; + + const BatteryManagerData({ + required this.voltageMv, + required this.currentUa, + required this.powerW, + required this.temperatureC, + required this.plugged, + required this.isCharging, + }); + + factory BatteryManagerData.empty() => const BatteryManagerData( + voltageMv: 0, + currentUa: 0, + powerW: 0, + temperatureC: 0, + plugged: 0, + isCharging: false, + ); + + factory BatteryManagerData.fromMap(dynamic raw) { + if (raw is! Map) return BatteryManagerData.empty(); + return BatteryManagerData( + voltageMv: (raw['voltageMv'] as num?)?.toInt() ?? 0, + currentUa: (raw['currentUa'] as num?)?.toInt() ?? 0, + powerW: (raw['powerW'] as num?)?.toDouble() ?? 0.0, + temperatureC: (raw['temperatureC'] as num?)?.toDouble() ?? 0.0, + plugged: (raw['plugged'] as num?)?.toInt() ?? 0, + isCharging: raw['isCharging'] as bool? ?? false, + ); + } + + /// 电压 V + double get voltageV => voltageMv / 1000.0; + + /// 电流 A + double get currentA => currentUa / 1_000_000.0; +} + +class BatteryManagerService { + static const _channel = MethodChannel('com.lxh.battery_health/charge'); + + /// 主动拉取一次 + static Future fetch() async { + final result = await _channel.invokeMethod('getBatteryManagerData'); + return BatteryManagerData.fromMap(result); + } +} diff --git a/lib/utils/charge_utils.dart b/lib/utils/charge_utils.dart index bf05217..4d14634 100644 --- a/lib/utils/charge_utils.dart +++ b/lib/utils/charge_utils.dart @@ -70,8 +70,11 @@ class ChargeUtils { if (power == 0) return "读取中..."; if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) { - final requested = ChargeMaps.wiredPower[wiredLevel]!.round(); - return "≈${requested}w/L-$wiredLevel"; + final requested = ChargeMaps.wiredPower[wiredLevel]!; + final str = requested == requested.roundToDouble() + ? requested.toInt().toString() + : requested.toStringAsFixed(1); + return "≈${str}w/L-$wiredLevel"; } return "≈${power.round()}w"; @@ -96,4 +99,31 @@ class ChargeUtils { if (chargeType == 'SDP') return 'USB'; return chargeType; } + + /// 主界面:BM 电池端功率 + 档位 + /// 示例:5.98w / L-7、12.3w、读取中... + static String getMainDisplayPowerBm( + double bmPower, + int? wiredLevel, + String chargeType, + bool isWireless, + ) { + if (bmPower <= 0 || bmPower > 100) return '读取中...'; + + final powerStr = '${bmPower.toStringAsFixed(1)}w'; + + if (isWireless || chargeType == 'SDP') return powerStr; + + // 有线:拼接标定瓦数 + if (wiredLevel != null && ChargeMaps.wiredPower.containsKey(wiredLevel)) { + final requested = ChargeMaps.wiredPower[wiredLevel]!; + // 7.5 → "7.5",27 → "27" + final requestedStr = requested == requested.roundToDouble() + ? requested.toInt().toString() + : requested.toStringAsFixed(1); + return '$powerStr / ${requestedStr}w'; + } + + return powerStr; + } } diff --git a/lib/widgets/charge_detail_page.dart b/lib/widgets/charge_detail_page.dart index 77f7ed1..7cc0191 100644 --- a/lib/widgets/charge_detail_page.dart +++ b/lib/widgets/charge_detail_page.dart @@ -10,6 +10,9 @@ class ChargeDetailPage extends StatelessWidget { final double volt; final double curr; final int? wiredLevel; + final double bmPower; + final double bmVolt; + final double bmCurr; const ChargeDetailPage({ super.key, @@ -19,6 +22,9 @@ class ChargeDetailPage extends StatelessWidget { required this.volt, required this.curr, this.wiredLevel, + this.bmPower = 0, + this.bmVolt = 0, + this.bmCurr = 0, }); String _getProtocolDisplay() { @@ -62,18 +68,31 @@ class ChargeDetailPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // ========== BMS 来源 ========== + _buildSectionTitle(context, 'BMS 日志'), _buildTextRow(context, '协议', _getProtocolDisplay()), const Divider(), if (!isWireless) ...[ - _buildTextRow(context, '标定档位', _getRequestedDisplay()), + _buildTextRow( + context, + '标定档位', + _getRequestedDisplay(), + valueFont: 'Flyme', + ), const Divider(), - _buildTextRow(context, '实际功率', _getActualDisplay()), + _buildTextRow( + context, + '实际功率', + _getActualDisplay(), + valueFont: 'Flyme', + ), const Divider(), ] else ...[ _buildTextRow( context, '功率', ChargeUtils.getDisplayPowerWireless(power), + valueFont: 'Flyme', ), const Divider(), ], @@ -81,20 +100,19 @@ class ChargeDetailPage extends StatelessWidget { const Divider(), _buildDetailRow(context, '电流', curr, 'A'), const Divider(), - if (isWireless) _buildTextRow(context, '类型', '无线'), - if (!isWireless && wiredLevel == null) - _buildTextRow(context, '档位', '自动'), + + const SizedBox(height: 24), + + // ========== BM 来源 ========== + _buildSectionTitle(context, 'BatteryManager(电池端)'), + _buildDetailRow(context, '电压', bmVolt, 'V'), + const Divider(), + _buildDetailRow(context, '电流', bmCurr, 'A'), + const Divider(), + bmPower > 0 + ? _buildDetailRow(context, '功率', bmPower, 'W') + : _buildTextRow(context, '功率', '读取中...'), const Spacer(), - Center( - child: Text( - '数据来自 BMS 实时日志', - style: TextStyle( - fontFamily: 'HarmonyOS_Sans_SC', - fontSize: 12, - color: c.textTertiary, - ), - ), - ), ], ), ), @@ -102,7 +120,28 @@ class ChargeDetailPage extends StatelessWidget { ); } - Widget _buildTextRow(BuildContext context, String label, String value) { + Widget _buildSectionTitle(BuildContext context, String title) { + final c = context.colors; + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Text( + title, + style: TextStyle( + fontFamily: 'HarmonyOS_Sans_SC', + fontSize: 13, + fontWeight: FontWeight.w600, + color: c.textTertiary, + ), + ), + ); + } + + Widget _buildTextRow( + BuildContext context, + String label, + String value, { + String valueFont = 'HarmonyOS_Sans_SC', + }) { final c = context.colors; return Padding( padding: const EdgeInsets.symmetric(vertical: 6.0), @@ -121,7 +160,7 @@ class ChargeDetailPage extends StatelessWidget { Text( value, style: TextStyle( - fontFamily: 'HarmonyOS_Sans_SC', + fontFamily: valueFont, fontSize: 16, fontWeight: FontWeight.w500, color: c.textPrimary,