13 Commits

Author SHA1 Message Date
lxh2875931338 17c728565c 确认变色逻辑 2026-07-04 12:32:42 +08:00
lxh2875931338 64e1a9f910 容量百分比变色显示逻辑优化 2026-07-04 12:30:12 +08:00
lxh2875931338 9d5a53ea1f 趋势图逻辑改动 2026-07-04 12:26:34 +08:00
lxh2875931338 49d4127ad9 趋势图逻辑改动 2026-07-04 12:25:46 +08:00
lxh2875931338 b97508dd10 部分设计环境调整 2026-07-04 12:20:52 +08:00
lxh2875931338 ff1070a350 调整充电列表档位信息 2026-07-03 23:22:29 +08:00
lxh2875931338 22db1bc89b Merge branch 'main' of http://192.168.3.16:3800/lxh2875931338/MEIZU-Battery-Healthy 2026-07-03 23:07:31 +08:00
lxh2875931338 c445ced7c1 readme措辞优化 2026-07-03 23:06:40 +08:00
lxh2875931338 a745a02b2a revert 4652034373
revert Merge remote-tracking branch 'origin/main'
2026-07-03 23:03:46 +08:00
lxh2875931338 4652034373 Merge remote-tracking branch 'origin/main' 2026-07-03 23:01:00 +08:00
lxh2875931338 0834ba8406 readme更新 2026-07-03 23:00:47 +08:00
lxh2875931338 b8ce1e4462 readme更新 2026-07-03 22:54:05 +08:00
lxh2875931338 15870b4ac4 调整协议识别逻辑、调整时间读取逻辑 2026-07-03 22:36:18 +08:00
6 changed files with 146 additions and 43 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"cmake.ignoreCMakeListsMissing": true
}
+47 -10
View File
@@ -1,16 +1,53 @@
# battery_health
# MEIZU Battery Healthy
A new Flutter project.
> 专机专配 · 仅针对魅族独家开发
## Getting Started
一个简洁的手机电池健康度监测工具<br>
通过读取 BMS 日志(`/storage/emulated/0/mbattery_charger/`)直接获取电池真实数据<br>
不依赖系统 API 估算。
This project is a starting point for a Flutter application.
---
A few resources to get you started if this is your first Flutter project:
## 功能
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
- 读取 BMS 日志,解析 `learned_cap``cycle``tbat``vbat` 等关键字段
- 显示电池健康度(当前容量 / 设计容量)
- 显示循环次数、电压、温度(含当日极值)
- 近 15 天容量百分比趋势柱状图
- 实时充电状态识别(有线 / 无线、PPS / SDP / EPP
- 充电功率、电压、电流实时显示
- 充电详情页(协议 / 功率 / 电压 / 电流 / 档位)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
---
## 当前状态
| 版本 | 主题 | 状态 |
| :--- | :--- | :--- |
| 1.2.0 | UTC 时区基础修正 | ✅ 已发布 |
| 1.3.0 | 时间窗口 + real_type 优先级 | 🚀 测试中 |
| 1.4.0 | SAF 权限改造 | 📋 规划中 |
| 1.5.0 | 功率计算优化 | 📋 规划中 |
| 2.0.0 | 数据看板扩展(月/季/年趋势) | 📋 远景 |
---
## 环境要求
- Flutter SDK: ^3.9.2
- Android: 8.0+ (API 26+)
- 仅支持魅族手机(15 系列 ~ 25 系列)
---
## 如果需要基于此源代码构建
```bash
# 拉取依赖
flutter pub get
# Debug 版本
flutter run
# Release APK
flutter build apk --release
+1 -1
View File
@@ -22,6 +22,6 @@ class ChargeMaps {
6: 30,
7: 45,
8: 65,
9: 80,
10: 80,
};
}
+92 -29
View File
@@ -47,13 +47,16 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
// ---------- 充电状态 ----------
bool _isCharging = false;
String _chargeType = ''; // "EPP", "PPS", "SDP", "无线" 等
String _chargeType = ''; // "EPP", "PPS", "SDP", "无线", "MZ_EPP"
double _chargePower = 0.0;
double _chargeVolt = 0.0;
double _chargeCurr = 0.0;
int? _wiredLevel;
bool _isWireless = false;
// 🟢 1.3.0-dev.3: 充电开始时间(UTC),用于时间窗口判断
DateTime? _chargingStartTime;
// 动画控制
late AnimationController _chargeAnimationController;
late Animation<double> _bottomSlide;
@@ -195,15 +198,17 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
}
// ---------- 1.3.0-dev.1: 使用 UTC 生成文件名 ----------
String getTodayFileName() {
DateTime now = DateTime.now();
String year = now.year.toString();
String month = now.month.toString().padLeft(2, '0');
String day = now.day.toString().padLeft(2, '0');
DateTime nowUtc = DateTime.now().toUtc();
String year = nowUtc.year.toString();
String month = nowUtc.month.toString().padLeft(2, '0');
String day = nowUtc.day.toString().padLeft(2, '0');
return 'mbattery_charger_log_${year}_${month}_$day';
}
// ---------- 时间戳工具方法 ----------
/// 解析日志行中的时间戳,强制解析为 UTC
DateTime? _parseLogTimestamp(String line) {
RegExp reg = RegExp(r'\[(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2})\]');
Match? match = reg.firstMatch(line);
@@ -226,18 +231,24 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
}
/// 🟢 1.3.0-dev.3: 时间窗口以插入时刻为基准,只向前看
bool _isLogTimestampValid(DateTime? logTime) {
if (logTime == null) return false;
DateTime nowUtc = DateTime.now().toUtc();
Duration diff = nowUtc.difference(logTime);
bool isValid = diff.abs().inMinutes <= 3;
return isValid;
if (_chargingStartTime == null) {
// 边缘情况:回退到当前时间 ±3 分钟
DateTime nowUtc = DateTime.now().toUtc();
Duration diff = nowUtc.difference(logTime);
return diff.abs().inMinutes <= 3;
}
// 日志时间必须在 [充电开始时间, 充电开始时间 + 3 分钟] 范围内
DateTime endTime = _chargingStartTime!.add(const Duration(minutes: 3));
return logTime.isAfter(_chargingStartTime!) && logTime.isBefore(endTime);
}
// ---------- 🟢 核心:系统API只触发充电状态,不设类型 ----------
// ---------- 核心:系统API定调 ----------
void _applySystemChargeState(bool isCharging, String systemChargeType) {
dev.log(
'[_applySystemChargeState] isCharging: $isCharging, systemChargeType: $systemChargeType (忽略,只用来触发状态)',
'[_applySystemChargeState] isCharging: $isCharging, systemChargeType: $systemChargeType',
name: 'BatteryHealth',
);
@@ -252,16 +263,48 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_chargeVolt = 0.0;
_chargeCurr = 0.0;
_wiredLevel = null;
_chargingStartTime = null; // 🟢 清空开始时间
_updateChargeState();
return;
}
// 🟢 不再根据 systemChargeType 设定类型,统一显示"识别中..."
// 类型完全由日志解析决定
if (_chargeType.isEmpty) {
_chargeType = ''; // 保持空,让日志解析来填充
// 🟢 1.3.0-dev.3: 记录充电开始时刻(仅当从断开状态变为充电状态时)
if (_chargingStartTime == null) {
_chargingStartTime = DateTime.now().toUtc();
dev.log(
'[_applySystemChargeState] 充电开始时间: $_chargingStartTime',
name: 'BatteryHealth',
);
}
if (systemChargeType == "WIRELESS") {
dev.log('[_applySystemChargeState] 无线充电模式', name: 'BatteryHealth');
_isWireless = true;
_chargeType = "无线";
_wiredLevel = null;
} else if (systemChargeType == "AC") {
dev.log(
'[_applySystemChargeState] 有线快充模式 (AC/PPS)',
name: 'BatteryHealth',
);
_isWireless = false;
_chargeType = "PPS";
} else if (systemChargeType == "USB") {
dev.log(
'[_applySystemChargeState] 有线慢充模式 (USB/SDP)',
name: 'BatteryHealth',
);
_isWireless = false;
_chargeType = "SDP";
} else {
// UNKNOWN 时清空旧类型,等待日志修正
dev.log(
'[_applySystemChargeState] ⚠️ 未知类型: $systemChargeType,清空类型等待日志',
name: 'BatteryHealth',
);
_isWireless = false;
_chargeType = '';
}
_isWireless = false; // 重置无线标志,等待日志确认
_chargePower = 0.0;
_chargeVolt = 0.0;
@@ -269,13 +312,12 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_updateChargeState();
});
// 🟢 插入充电器后立即读取日志
if (_isCharging) {
_fetchChargeDataFromLog();
}
}
// ---------- 🟢 日志解析:唯一的数据源 ----------
// ---------- 日志解析 ----------
void _parseChargeDataFromLog(List<String> lines) {
dev.log(
'[_parseChargeDataFromLog] 开始解析,共 ${lines.length}',
@@ -367,15 +409,16 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_chargeVolt = vbus / 1000000.0;
_chargeCurr = ibus / 1000000.0;
// 有线模式
_isWireless = false;
// 🟢 1.3.0-dev.2: real_type 优先级最高
if (realType != null && realType == "PPS") {
_chargeType = "PPS";
} else if (level >= 3) {
_chargeType = "PPS";
} else if (realType != null && realType == "SDP") {
} else if (realType != null &&
(realType == "SDP" || realType == "FLOAT")) {
_chargeType = "SDP";
} else if (level >= 3 && _chargeType != "PPS") {
_chargeType = "PPS";
} else if (_chargeType.isEmpty) {
_chargeType = "SDP";
}
@@ -385,8 +428,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
} else {
_wiredLevel = null;
}
dev.log(
'[_parseChargeDataFromLog] 有线更新: $_chargeType, ${_chargePower.toStringAsFixed(2)}W',
'[_parseChargeDataFromLog] 有线更新: $_chargeType, ${_chargePower.toStringAsFixed(2)}W, level: $level',
name: 'BatteryHealth',
);
});
@@ -394,11 +438,25 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
}
}
// 🟢 1.3.0-dev.3: 增加超时保护
Future<void> _fetchChargeDataFromLog() async {
if (!_isCharging) {
dev.log('[_fetchChargeDataFromLog] 未充电,跳过读取', name: 'BatteryHealth');
return;
}
// 超时检查:如果插入超过 5 分钟还没读到数据,不再频繁报错
if (_chargingStartTime != null) {
Duration elapsed = DateTime.now().toUtc().difference(_chargingStartTime!);
if (elapsed.inMinutes > 5) {
dev.log(
'[_fetchChargeDataFromLog] 充电开始已超过 5 分钟,跳过本次轮询',
name: 'BatteryHealth',
);
return;
}
}
String fullPath = _filePath + getTodayFileName();
dev.log('[_fetchChargeDataFromLog] 尝试读取: $fullPath', name: 'BatteryHealth');
@@ -636,7 +694,6 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
_applySystemChargeState(isCharging, systemType);
});
// 🟢 轮询间隔缩短为 15 秒
_pollTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
if (_isCharging) {
_fetchChargeDataFromLog();
@@ -738,10 +795,13 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
children: [
Text(
healthPercent.toStringAsFixed(1),
style: const TextStyle(
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w500,
height: 1.0,
color: healthPercent < 70
? Colors.red
: Colors.black, // 🟢 动态颜色
),
),
const SizedBox(width: 2),
@@ -751,7 +811,9 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
fontSize: 20,
fontWeight: FontWeight.w300,
height: 1.0,
color: Colors.grey.shade600,
color: healthPercent < 70
? Colors.red
: Colors.grey.shade600, // 🟢 动态颜色
),
),
],
@@ -982,10 +1044,11 @@ class _BatteryHealthPageState extends State<BatteryHealthPage>
if (value > 0) {
if (value >= 80) {
barColor = kHealthGreen;
} else if (value >= 60)
} else if (value >= 70) {
barColor = kHealthYellow;
else
barColor = kHealthOrange;
} else {
barColor = Colors.red; // 🟢 小于 70 显示红色
}
} else {
barColor = Colors.grey.shade300;
}
+2 -2
View File
@@ -11,14 +11,14 @@ class ChargeDetailPage extends StatelessWidget {
final int? wiredLevel;
const ChargeDetailPage({
Key? key,
super.key,
required this.chargeType,
required this.isWireless,
required this.power,
required this.volt,
required this.curr,
this.wiredLevel,
}) : super(key: key);
});
String _getDisplayPower() {
if (isWireless) {
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.2.0
version: 1.3.0
environment:
sdk: ^3.9.2