diff --git a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/ble/BleBridge.kt b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/ble/BleBridge.kt index 89d1728d..fdceabd8 100644 --- a/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/ble/BleBridge.kt +++ b/android/app/src/main/kotlin/com/cloud/diplomaticquarterapp/ble/BleBridge.kt @@ -32,6 +32,7 @@ import com.lepu.blepro.ext.BleServiceHelper import com.lepu.blepro.ext.er2.DeviceInfo import com.lepu.blepro.ext.er2.Er2EcgFile import com.lepu.blepro.ext.er2.Er2File +import com.lepu.blepro.ext.er2.RtData import com.lepu.blepro.ext.pc60fw.RtParam import com.lepu.blepro.objs.Bluetooth import com.lepu.blepro.objs.BluetoothController @@ -130,6 +131,9 @@ class BleBridge( private var rtHandler = Handler() private var rtTask = RtTask() + private var ecgRTHandler = Handler() + private var ecgRTTask = ECGRtTask() + inner class RtTask : Runnable { override fun run() { rtHandler.postDelayed(rtTask, 1000) @@ -137,6 +141,13 @@ class BleBridge( } } + inner class ECGRtTask : Runnable { + override fun run() { + ecgRTHandler.postDelayed(rtTask, 1000) + BleServiceHelper.BleServiceHelper.startRtTask(model) + } + } + @SuppressLint("NewApi") fun create() { channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) @@ -244,6 +255,7 @@ class BleBridge( println("EventEr2SetTime") // Get Device Info BleServiceHelper.BleServiceHelper.er2GetInfo(model) + ecgRTTask.run() } LiveEventBus.get(InterfaceEvent.ER2.EventEr2ReadFileComplete) .observe(this.mainActivity) { @@ -259,9 +271,30 @@ class BleBridge( ecgData.startTime = startTime val returnData = mapOf("type" to "fileDetail", "data" to gson.toJson(ecgData)) -// val output = gson.toJson(returnData) eventSink?.success(returnData) } + + LiveEventBus.get(InterfaceEvent.ER2.EventEr2RtData) + .observe(this.mainActivity) { + val data = it.data as RtData + println("EventEr2RtData") + val returnData = + mapOf("type" to "realtimeDataECG", "data" to gson.toJson(data)) + println(returnData) + eventSink?.success(returnData) +// DataController.receive(data.wave.ecgFloats) +// hr.text = "${data.param.hr}" +// data_log.text = "${data.param}" + + // sampling rate:125HZ + // mV = n * 0.002467(data.wave.ecgFloats = data.wave.ecgShorts * 0.002467) + // data.param.batteryState:0(no charge),1(charging),2(charging complete),3(low battery) + // data.param.battery:0-100 + // data.param.recordTime:unit(s) + // data.param.curStatus:0(idle),1(preparing),2(measuring),3(saving file),4(saving succeed), + // 5(less than 30s, file not saved),6(6 retests),7(lead off) + } + } if (deviceName == "O2M 1670") { diff --git a/lib/models/ble_devices/viatom_devices/ekg_realtime_data_response.dart b/lib/models/ble_devices/viatom_devices/ekg_realtime_data_response.dart new file mode 100644 index 00000000..1b70382a --- /dev/null +++ b/lib/models/ble_devices/viatom_devices/ekg_realtime_data_response.dart @@ -0,0 +1,78 @@ +class EKGRealTimeDataResponseModel { + Param param; + Wave wave; + + EKGRealTimeDataResponseModel({this.param, this.wave}); + + EKGRealTimeDataResponseModel.fromJson(Map json) { + param = json['param'] != null ? new Param.fromJson(json['param']) : null; + wave = json['wave'] != null ? new Wave.fromJson(json['wave']) : null; + } + + Map toJson() { + final Map data = new Map(); + if (this.param != null) { + data['param'] = this.param.toJson(); + } + if (this.wave != null) { + data['wave'] = this.wave.toJson(); + } + return data; + } +} + +class Param { + num battery; + num batteryState; + num curStatus; + num hr; + bool poorSignal; + bool rSignal; + num recordTime; + + Param({this.battery, this.batteryState, this.curStatus, this.hr, this.poorSignal, this.rSignal, this.recordTime}); + + Param.fromJson(Map json) { + battery = json['battery']; + batteryState = json['batteryState']; + curStatus = json['curStatus']; + hr = json['hr']; + poorSignal = json['poorSignal']; + rSignal = json['rSignal']; + recordTime = json['recordTime']; + } + + Map toJson() { + final Map data = new Map(); + data['battery'] = this.battery; + data['batteryState'] = this.batteryState; + data['curStatus'] = this.curStatus; + data['hr'] = this.hr; + data['poorSignal'] = this.poorSignal; + data['rSignal'] = this.rSignal; + data['recordTime'] = this.recordTime; + return data; + } +} + +class Wave { + List ecgBytes; + List ecgFloats; + List ecgShorts; + + Wave({this.ecgBytes, this.ecgFloats, this.ecgShorts}); + + Wave.fromJson(Map json) { + ecgBytes = json['ecgBytes'].cast(); + ecgFloats = json['ecgFloats'].cast(); + ecgShorts = json['ecgShorts'].cast(); + } + + Map toJson() { + final Map data = new Map(); + data['ecgBytes'] = this.ecgBytes; + data['ecgFloats'] = this.ecgFloats; + data['ecgShorts'] = this.ecgShorts; + return data; + } +} diff --git a/lib/pages/medical/my_trackers/viatom_devices/ekg_tracker_ble.dart b/lib/pages/medical/my_trackers/viatom_devices/ekg_tracker_ble.dart index 04ac140e..29526c39 100644 --- a/lib/pages/medical/my_trackers/viatom_devices/ekg_tracker_ble.dart +++ b/lib/pages/medical/my_trackers/viatom_devices/ekg_tracker_ble.dart @@ -2,15 +2,20 @@ import 'dart:async'; import 'dart:convert'; import 'package:diplomaticquarterapp/models/ble_devices/viatom_devices/ekg_file_detail_response_model.dart'; +import 'package:diplomaticquarterapp/models/ble_devices/viatom_devices/ekg_realtime_data_response.dart'; import 'package:diplomaticquarterapp/pages/medical/my_trackers/viatom_devices/ekg_chart_view.dart'; import 'package:diplomaticquarterapp/uitl/utils_new.dart'; import 'package:diplomaticquarterapp/viatom_ble/ble_connect.dart'; import 'package:diplomaticquarterapp/widgets/buttons/defaultButton.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart'; +import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'dart:math' as math; + class EKG_BLE extends StatefulWidget { const EKG_BLE(); @@ -22,36 +27,101 @@ class _EKG_BLEState extends State { EventChannel eventChannel = EventChannel('BLE-Platform-Bridge-Event'); String receivedData = ''; - // final ekgValueNotifier = ValueNotifier>; final ekgValueNotifier = ValueNotifier("start"); List ekgFilesList = []; - List ecgData = []; + EKGFileDetailResponseModel ekgFileDetailResponseModel; + EKGRealTimeDataResponseModel ekgRealTimeDataResponseModel; + + List ecgBytesAllDataList = []; + Timer timer; + List<_ChartData> chartData; + int count; + ChartSeriesController _chartSeriesController; @override void dispose() { - // ekgValueNotifier.dispose(); + ekgValueNotifier.dispose(); super.dispose(); BleChannel.disconnect(); timer?.cancel(); + chartData.clear(); + _chartSeriesController = null; } @override void initState() { // TODO: implement initState + + count = 19; + chartData = <_ChartData>[ + _ChartData(0, 42), + _ChartData(1, 47), + _ChartData(2, 33), + _ChartData(3, 49), + _ChartData(4, 54), + _ChartData(5, 41), + _ChartData(6, 58), + _ChartData(7, 51), + _ChartData(8, 98), + _ChartData(9, 41), + _ChartData(10, 53), + _ChartData(11, 72), + _ChartData(12, 86), + _ChartData(13, 52), + _ChartData(14, 94), + _ChartData(15, 92), + _ChartData(16, 86), + _ChartData(17, 72), + _ChartData(18, 94), + ]; + timer = Timer.periodic(const Duration(milliseconds: 1000), _updateDataSource); super.initState(); + } + + SfCartesianChart _buildLiveLineChart() { + return SfCartesianChart( + plotAreaBorderWidth: 0, + primaryXAxis: NumericAxis(majorGridLines: const MajorGridLines(width: 0)), + primaryYAxis: NumericAxis(axisLine: const AxisLine(width: 0), majorTickLines: const MajorTickLines(size: 0)), - // Simulate live ECG data (replace this with your actual data source) - timer = Timer.periodic(Duration(milliseconds: 500), (Timer t) { - setState(() { - ecgData.add((10.0 * (1 - 2 * (ecgData.length % 2).toDouble())).toDouble()); - if (ecgData.length > 10) { - ecgData.removeAt(0); - } - }); - }); + series: >[ + LineSeries<_ChartData, int>( + onRendererCreated: (ChartSeriesController controller) { + _chartSeriesController = controller; + }, + dataSource: chartData, + color: const Color.fromRGBO(192, 108, 132, 1), + xValueMapper: (_ChartData sales, _) => sales.country, + yValueMapper: (_ChartData sales, _) => sales.sales, + animationDuration: 2000.0) + ]); + } + + void _updateDataSource(Timer timer) { + // if (isCardView != null) { + chartData.add(_ChartData(count, _getRandomInt(10, 100))); + if (chartData.length == 20) { + chartData.removeAt(0); + _chartSeriesController?.updateDataSource( + addedDataIndexes: [chartData.length - 1], + removedDataIndexes: [0], + ); + } else { + _chartSeriesController?.updateDataSource( + addedDataIndexes: [chartData.length - 1], + ); + } + count = count + 1; + // } + } + + ///Get the random data + int _getRandomInt(int min, int max) { + final math.Random random = math.Random(); + return min + random.nextInt(max - min); } @override @@ -88,6 +158,10 @@ class _EKG_BLEState extends State { print(event['data']); parseEKGFileDetailObject(event['data']); } + if (event['type'] == "realtimeDataECG") { + ekgValueNotifier.value = event['data']; + parseEKGRealTimeDataObject(event['data']); + } }); await BleChannel.getScanningResult(["oximeter", "ekg"]); }, @@ -106,16 +180,76 @@ class _EKG_BLEState extends State { ), ], ), - mHeight(50.0), - ValueListenableBuilder( - valueListenable: ekgValueNotifier, - builder: (context, value, _) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [Text(value), mHeight(24.0), getFilesListWidget()], - ); - }, - ), + mHeight(20.0), + _buildLiveLineChart() + // ValueListenableBuilder( + // valueListenable: ekgValueNotifier, + // builder: (context, value, _) { + // return Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // value, + // style: TextStyle(fontSize: 9.0), + // ), + // mHeight(24.0), + // getFilesListWidget(), + // mHeight(20.0), + // // SizedBox( + // // height: 150.0, + // // width: MediaQuery.of(context).size.width, + // // child: (ekgRealTimeDataResponseModel != null && ekgRealTimeDataResponseModel.wave != null) + // // ? LineChart( + // // LineChartData( + // // lineTouchData: LineTouchData(handleBuiltInTouches: false), + // // gridData: FlGridData( + // // show: true, + // // verticalInterval: 30, + // // horizontalInterval: 30, + // // getDrawingVerticalLine: (value) { + // // return FlLine( + // // color: Colors.red[300], + // // strokeWidth: 0.4, + // // ); + // // }, + // // getDrawingHorizontalLine: (value) { + // // return FlLine( + // // color: Colors.red[300], + // // strokeWidth: 0.4, + // // ); + // // }, + // // ), + // // titlesData: FlTitlesData(show: true), + // // borderData: FlBorderData( + // // show: false, + // // border: Border.all(color: const Color(0xff37434d), width: 1), + // // ), + // // minX: 0, + // // // maxX: (ekgRealTimeDataResponseModel.wave.ecgBytes.length.toDouble() - 1), + // // maxX: ecgBytesAllDataList.length.toDouble(), + // // minY: ekgRealTimeDataResponseModel.wave.ecgBytes.reduce((value, element) => value < element ? value : element).toDouble(), + // // maxY: ekgRealTimeDataResponseModel.wave.ecgBytes.reduce((value, element) => value > element ? value : element).toDouble(), + // // lineBarsData: [ + // // LineChartBarData( + // // isCurved: false, + // // preventCurveOverShooting: true, + // // barWidth: 0.5, + // // dotData: FlDotData(show: false), + // // // spots: getDataList(ekgRealTimeDataResponseModel.wave.ecgBytes), + // // spots: getDataList(ekgRealTimeDataResponseModel.wave.ecgBytes), + // // colors: [Colors.grey[800]], + // // isStrokeCapRound: true, + // // belowBarData: BarAreaData(show: false), + // // ), + // // ], + // // ), + // // ) + // // : Container(), + // // ), + // ], + // ); + // }, + // ), ], ), ), @@ -130,6 +264,21 @@ class _EKG_BLEState extends State { ekgValueNotifier.value = ekgFilesList.toString(); } + List getDataList(List list) { + ecgBytesAllDataList.add(list[0]); + // ecgBytesAllDataList.addAll(list[0]); + + // if(ecgBytesAllDataList.length >= 1250) { + // ecgBytesAllDataList.removeRange(0, 1200); + // } + + List spotsList = []; + for (int i = 0; i < ecgBytesAllDataList.length; i++) { + spotsList.add(FlSpot(i.toDouble(), ecgBytesAllDataList[i].toDouble())); + } + return spotsList; + } + Widget getFilesListWidget() { return ListView.separated( shrinkWrap: true, @@ -153,7 +302,19 @@ class _EKG_BLEState extends State { } void parseEKGFileDetailObject(dynamic returnData) { - EKGFileDetailResponseModel ekgFileDetailResponseModel = EKGFileDetailResponseModel.fromJson(json.decode(returnData)); + ekgFileDetailResponseModel = EKGFileDetailResponseModel.fromJson(json.decode(returnData)); Navigator.push(context, FadePage(page: EKGChartView(ekgFileDetailResponseModel: ekgFileDetailResponseModel))); } + + String parseEKGRealTimeDataObject(dynamic returnData) { + ekgRealTimeDataResponseModel = EKGRealTimeDataResponseModel.fromJson(json.decode(returnData)); + return "HeartRate: ${ekgRealTimeDataResponseModel.param.hr} - Current State: ${ekgRealTimeDataResponseModel.param.curStatus}"; + } +} + +class _ChartData { + _ChartData(this.country, this.sales); + + final int country; + final num sales; } diff --git a/pubspec.yaml b/pubspec.yaml index c57721ef..399979be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -107,6 +107,9 @@ dependencies: # Calendar syncfusion_flutter_calendar: ^19.3.55 + #Charts for Live ECG View + syncfusion_flutter_charts: ^19.3.55 + # SVG Images # flutter_svg: ^0.23.0+1