heart_rate.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:fis_common/logger/logger.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:get/get.dart';
  6. import 'package:vitalapp/managers/interfaces/data_convert.dart';
  7. import 'package:vitalapp/managers/interfaces/models/device.dart';
  8. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_card.dart';
  9. import 'package:vitalapp/pages/medical/controller.dart';
  10. import 'package:vitalapp/pages/medical/controllers/heart.dart';
  11. import 'package:vitalapp/pages/medical/models/worker.dart';
  12. import 'package:vitalapp/pages/medical/widgets/device_status.dart';
  13. import 'package:vitalapp/pages/medical/widgets/device_status_position.dart';
  14. import 'package:vitalapp/pages/medical/widgets/ecg_view/index.dart';
  15. import 'package:vitalapp/pages/medical/widgets/side_bar.dart';
  16. import 'package:vnote_device_plugin/consts/types.dart';
  17. import 'package:vnote_device_plugin/devices/heart.dart';
  18. import 'package:vnote_device_plugin/models/exams/heart.dart';
  19. class HeartRate extends StatefulWidget {
  20. const HeartRate({
  21. super.key,
  22. });
  23. @override
  24. State<HeartRate> createState() => _HeartRateState();
  25. }
  26. class _HeartRateState extends State<HeartRate> {
  27. final MedicalController medicalController = Get.find<MedicalController>();
  28. final IDataConvertManager dataConvertManager =
  29. Get.find<IDataConvertManager>();
  30. late HeartDeviceController heartDeviceController;
  31. late HeartDeviceWorker worker;
  32. WorkerStatus connectStatus = WorkerStatus.connecting;
  33. List<int> ecgPoint = [];
  34. int errorCount = 0;
  35. bool isConnectFail = false;
  36. late String _heart =
  37. medicalController.diagnosisDataValue['Heart']?['HEART']?.toString() ?? '';
  38. late String _assess =
  39. medicalController.diagnosisDataValue['Heart']?['ASSESS'] ?? '';
  40. late final heartRate =
  41. medicalController.diagnosisDataValue['Heart']?['HEART'] ?? 0;
  42. /// 是否显示重试按钮
  43. bool showResetButton = false;
  44. /// 初始时的心电初始数据
  45. List<int>? initEcgData;
  46. String _deviceError = '';
  47. @override
  48. Widget build(BuildContext context) {
  49. return Stack(
  50. children: [
  51. ExamCard(
  52. titleText: const SizedBox(),
  53. bottomPadding: 10,
  54. content: Column(
  55. mainAxisAlignment: MainAxisAlignment.start,
  56. children: [
  57. Row(
  58. mainAxisAlignment: MainAxisAlignment.end,
  59. children: [
  60. const SideBar(
  61. title: '心率',
  62. value: '',
  63. unit: '',
  64. ),
  65. const Expanded(child: SizedBox()),
  66. Text(
  67. _heart.isEmpty ? '--' : _heart,
  68. style: const TextStyle(
  69. fontSize: 60,
  70. color: Colors.black,
  71. ),
  72. ),
  73. const Text(
  74. " bpm",
  75. style: TextStyle(fontSize: 25),
  76. ),
  77. const SizedBox(
  78. width: 15,
  79. )
  80. ],
  81. ),
  82. if (_deviceError.isNotEmpty)
  83. Row(
  84. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  85. crossAxisAlignment: CrossAxisAlignment.start,
  86. children: [
  87. Container(
  88. padding: const EdgeInsets.symmetric(
  89. horizontal: 30,
  90. ),
  91. child: const Text(
  92. '心率测量出错',
  93. style: TextStyle(
  94. fontSize: 25,
  95. ),
  96. ),
  97. ),
  98. Container(
  99. padding: const EdgeInsets.symmetric(
  100. horizontal: 50,
  101. ),
  102. child: Text(
  103. _deviceError,
  104. style: const TextStyle(
  105. fontSize: 24,
  106. ),
  107. ),
  108. ),
  109. ],
  110. ),
  111. if (_assess.isNotEmpty)
  112. Row(
  113. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  114. crossAxisAlignment: CrossAxisAlignment.start,
  115. children: [
  116. Container(
  117. padding: const EdgeInsets.symmetric(
  118. horizontal: 30,
  119. ),
  120. child: const Text(
  121. '心率评估',
  122. style: TextStyle(
  123. fontSize: 25,
  124. ),
  125. ),
  126. ),
  127. Container(
  128. padding: const EdgeInsets.symmetric(
  129. horizontal: 50,
  130. ),
  131. child: Text(
  132. dataConvertManager.heartRateConversion(
  133. int.parse(heartRate.toString())),
  134. style: const TextStyle(
  135. fontSize: 24,
  136. ),
  137. ),
  138. ),
  139. ],
  140. ),
  141. const SizedBox(
  142. height: 10,
  143. ),
  144. SizedBox(
  145. height: 240,
  146. child: LayoutBuilder(builder: (context, constraints) {
  147. if (initEcgData == null) {
  148. return Container();
  149. } else {
  150. return EcgView(
  151. width: constraints.maxWidth,
  152. height: constraints.maxHeight,
  153. initData: initEcgData!,
  154. );
  155. }
  156. }),
  157. ),
  158. Container(
  159. // !! 这一层 Container 不能删 ,否则会引发 EcgView 的 controller 被异常删除
  160. child: _buildResetButton(),
  161. ),
  162. ],
  163. ),
  164. ),
  165. if (!isConnectFail)
  166. DeviceStatusPosition(
  167. deviceStatus: DeviceStatus(connectStatus: connectStatus),
  168. )
  169. else
  170. _buildErrorButton(),
  171. ],
  172. );
  173. }
  174. // Widget _buildDebugButton() {
  175. // return Container(
  176. // child: Row(
  177. // children: [
  178. // const Text(
  179. // '测试============?',
  180. // style: TextStyle(color: Colors.grey, fontSize: 24),
  181. // ),
  182. // TextButton(
  183. // onPressed: () {
  184. // debugTest();
  185. // },
  186. // child: const Text(
  187. // '重试',
  188. // style: TextStyle(color: Colors.blue, fontSize: 24),
  189. // ),
  190. // ),
  191. // ],
  192. // ),
  193. // );
  194. // }
  195. Widget _buildResetButton() {
  196. if (!showResetButton) {
  197. return const SizedBox(
  198. height: 48,
  199. );
  200. }
  201. return Container(
  202. padding: const EdgeInsets.only(left: 30),
  203. child: Row(
  204. children: [
  205. const Text(
  206. '测量数据不完整,是否需要重试?',
  207. style: TextStyle(color: Colors.grey, fontSize: 24),
  208. ),
  209. TextButton(
  210. onPressed: () {
  211. ecgPoint = [];
  212. resetEcgView();
  213. setEcgData();
  214. setState(() {
  215. showResetButton = false;
  216. });
  217. },
  218. child: const Text(
  219. '重试',
  220. style: TextStyle(color: Colors.blue, fontSize: 24),
  221. ),
  222. ),
  223. ],
  224. ),
  225. );
  226. }
  227. // 设备初始化
  228. Future<void> initDevice() async {
  229. DeviceModel? device = await medicalController.getDevice(DeviceTypes.HEART);
  230. if (device == null) {
  231. connectStatus = WorkerStatus.unboundDevice;
  232. setState(() {});
  233. return;
  234. }
  235. heartDeviceController = HeartDeviceController(device.model, device.mac);
  236. worker = heartDeviceController.worker;
  237. connectStatus = heartDeviceController.connectStatus;
  238. loadListeners();
  239. worker.connect();
  240. }
  241. /// 尝试重连
  242. Future<void> tryReconnect() async {
  243. await worker.disconnect();
  244. await worker.connect();
  245. }
  246. @override
  247. void dispose() async {
  248. super.dispose();
  249. releaseListeners();
  250. await worker.disconnect();
  251. }
  252. /// 重置数据以及心电图
  253. void resetEcgView() {
  254. try {
  255. EcgViewController ecgViewController = Get.find<EcgViewController>();
  256. ecgPoint = [];
  257. ecgViewController.reset();
  258. } catch (e) {
  259. logger.i('心率图测量失败:$e');
  260. }
  261. }
  262. /// 数据初始化
  263. Future<void> initData() async {
  264. await medicalController.readCachedRecord();
  265. if (medicalController.diagnosisDataValue['Heart'] == null) {
  266. medicalController.diagnosisDataValue['Heart'] = {};
  267. }
  268. if (medicalController.diagnosisDataValue['Heart']?['ECG_POINT'] != null) {
  269. initEcgData = jsonDecode(
  270. medicalController.diagnosisDataValue['Heart']?['ECG_POINT'])
  271. .cast<int>();
  272. if (initEcgData != null &&
  273. initEcgData!.isNotEmpty &&
  274. initEcgData!.length < 125 * 30) {
  275. // print("数据不完整 ⭐⭐⭐⭐⭐⭐⭐");
  276. showResetButton = true;
  277. }
  278. } else {
  279. initEcgData = [];
  280. }
  281. setState(() {});
  282. }
  283. Future<void> initHeart() async {
  284. await initDevice();
  285. await initData();
  286. }
  287. @override
  288. void initState() {
  289. initHeart();
  290. super.initState();
  291. }
  292. void loadListeners() {
  293. worker.connectErrorEvent.addListener(_onConnectFail);
  294. worker.connectedEvent.addListener(_onConnectSuccess);
  295. worker.disconnectedEvent.addListener(_onDisconnected);
  296. worker.hrValueUpdateEvent.addListener(_onHrValueUpdate);
  297. worker.ecgValueUpdateEvent.addListener(_onEcgValueUpdate);
  298. worker.resultReceivedEvent.addListener(_onRCesultReceived);
  299. worker.errorEvent.addListener(_onError);
  300. }
  301. void releaseListeners() {
  302. worker.connectErrorEvent.removeListener(_onConnectFail);
  303. worker.connectedEvent.removeListener(_onConnectSuccess);
  304. worker.disconnectedEvent.removeListener(_onDisconnected);
  305. worker.hrValueUpdateEvent.removeListener(_onHrValueUpdate);
  306. worker.ecgValueUpdateEvent.removeListener(_onEcgValueUpdate);
  307. worker.resultReceivedEvent.removeListener(_onRCesultReceived);
  308. worker.errorEvent.removeListener(_onError);
  309. }
  310. /// 需要封装一下
  311. Widget _buildErrorButton() {
  312. return DeviceStatusPosition(
  313. deviceStatus: Row(
  314. children: [
  315. const Text(
  316. '请确认设备是否启动',
  317. style: TextStyle(fontSize: 24, color: Colors.red),
  318. ),
  319. IconButton(
  320. onPressed: () {
  321. worker.connect();
  322. setState(() {
  323. connectStatus = WorkerStatus.connecting;
  324. isConnectFail = false;
  325. });
  326. },
  327. icon: const Icon(Icons.refresh),
  328. iconSize: 32,
  329. ),
  330. ],
  331. ),
  332. );
  333. }
  334. void _onConnectFail(sender, e) async {
  335. logger.i("设备连接失败:${worker.mac}");
  336. if (errorCount < 3) {
  337. errorCount++;
  338. await tryReconnect();
  339. } else {
  340. isConnectFail = true;
  341. }
  342. connectStatus = WorkerStatus.connectionFailed;
  343. setState(() {});
  344. }
  345. void _onConnectSuccess(sender, e) {
  346. logger.e('_HeartRateState ${worker.mac}, 设备连接成功');
  347. isConnectFail = false;
  348. errorCount = 0;
  349. connectStatus = WorkerStatus.connected;
  350. setState(() {});
  351. }
  352. /// 设备连接中断
  353. void _onDisconnected(sender, e) async {
  354. print('设备连接中断');
  355. logger.i("设备连接中断:${worker.mac}");
  356. if (errorCount < 3) {
  357. errorCount++;
  358. await tryReconnect();
  359. } else {
  360. isConnectFail = true;
  361. }
  362. connectStatus = WorkerStatus.disconnected;
  363. setState(() {});
  364. }
  365. // 心电图数据更新
  366. void _onEcgValueUpdate(_, List<int> e) {
  367. try {
  368. EcgViewController ecgViewController = Get.find<EcgViewController>();
  369. ecgPoint.addAll(e);
  370. if (ecgPoint.length > 125 * 3) {
  371. // 3s 后开始塞数据
  372. ecgViewController.addData(e);
  373. }
  374. } catch (e) {
  375. print(e);
  376. }
  377. }
  378. /// 更新心率
  379. void _onHrValueUpdate(_, int e) {
  380. logger.i('心率更新:$e');
  381. if (_deviceError.isNotEmpty || _assess.isNotEmpty) {
  382. // 如果上次因错误而停止了,这里需要先重置
  383. _deviceError = '';
  384. _assess = '';
  385. resetEcgView();
  386. }
  387. setState(() {
  388. _heart = e.toString();
  389. showResetButton = false;
  390. });
  391. }
  392. void _onError(_, String e) {
  393. logger.i('心率更新失败:$e');
  394. _deviceError = e;
  395. resetEcgView();
  396. setState(() {});
  397. }
  398. /// 接收到最终结果
  399. void _onRCesultReceived(_, HeartExamResult e) async {
  400. logger.i('心率更新接收结果:${e.heartRate}');
  401. // print(ecgPoint.toString());
  402. // _heart = e.toString();
  403. _assess = e.analysis.first;
  404. _heart = e.heartRate.toString();
  405. medicalController.diagnosisDataValue['Heart']?['HEART'] =
  406. e.heartRate.toString();
  407. medicalController.diagnosisDataValue['Heart']?['ASSESS'] =
  408. dataConvertManager.heartRateConversion(int.parse(heartRate));
  409. await setEcgData();
  410. /// 判断数据是否完整
  411. if (ecgPoint.isNotEmpty && ecgPoint.length < 125 * 30) {
  412. // print("数据不完整 ⭐⭐⭐⭐⭐⭐⭐");
  413. showResetButton = true;
  414. }
  415. /// FIXME 解除下方注释
  416. // try {
  417. // medicalController.saveCachedRecord();
  418. // } catch (e) {
  419. // print(e);
  420. // }
  421. // print(medicalController.diagnosisDataValue);
  422. setState(() {});
  423. }
  424. void debugTest() async {
  425. logger.i('心率更新接收结果 测试');
  426. // print(ecgPoint.toString());
  427. // _heart = e.toString();
  428. _assess = "ss";
  429. _heart = "90";
  430. medicalController.diagnosisDataValue['Heart']?['HEART'] = "90";
  431. medicalController.diagnosisDataValue['Heart']?['ASSESS'] =
  432. dataConvertManager.heartRateConversion(int.parse(heartRate));
  433. await setEcgData();
  434. /// 判断数据是否完整
  435. // if (ecgPoint.isNotEmpty && ecgPoint.length < 125 * 30) {
  436. // }
  437. print("数据不完整 ⭐⭐⭐⭐⭐⭐⭐");
  438. showResetButton = true;
  439. medicalController.saveCachedRecord();
  440. print(medicalController.diagnosisDataValue);
  441. setState(() {});
  442. }
  443. /// 设置最终数据
  444. Future<void> setEcgData() async {
  445. EcgViewController ecgViewController = Get.find<EcgViewController>();
  446. medicalController.diagnosisDataValue['Heart']?['ECG'] =
  447. await ecgViewController.getFullDataImageBase64();
  448. medicalController.diagnosisDataValue['Heart']?['ECG_POINT'] =
  449. jsonEncode(ecgViewController.allPoints);
  450. }
  451. }