From 992f7dd454c38e2e3255cb43911cc54f5a56afd6 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 11 Sep 2023 17:02:05 +0300 Subject: [PATCH] updates --- lib/config/localized_values.dart | 1 + lib/pages/medical/my_trackers/ecg_ble.dart | 146 +++++++++++++ .../medical/my_trackers/my_trackers.dart | 70 ++++-- lib/pages/medical/my_trackers/spirometer.dart | 205 ++++++++++++++++++ .../medical/my_trackers/temperature.dart | 44 +--- lib/uitl/ble_utils.dart | 15 +- lib/uitl/bluetooth_off.dart | 47 ++++ lib/uitl/translations_delegate_base.dart | 1 + 8 files changed, 465 insertions(+), 64 deletions(-) create mode 100644 lib/pages/medical/my_trackers/ecg_ble.dart create mode 100644 lib/pages/medical/my_trackers/spirometer.dart create mode 100644 lib/uitl/bluetooth_off.dart diff --git a/lib/config/localized_values.dart b/lib/config/localized_values.dart index 4984053d..d14c26c2 100644 --- a/lib/config/localized_values.dart +++ b/lib/config/localized_values.dart @@ -1893,4 +1893,5 @@ const Map localizedValues = { "sickLeaveAdmittedPatient": {"en": "You cannot activate this sick leave since you're an admitted patient.", "ar": "لا يمكنك تفعيل هذه الإجازة المرضية لأنك مريض مقبل."}, "dischargeDate": {"en": "Discharge Date", "ar": "تاريخ التفريغ"}, "selectAdmissionText": {"en": "Please select one of the admissions from below to view medical reports:", "ar": "يرجى تحديد أحد حالات القبول من الأسفل لعرض التقارير الطبية:"}, + "spirometer": {"en": "Spirometer", "ar": "مقياس التنفس"}, }; \ No newline at end of file diff --git a/lib/pages/medical/my_trackers/ecg_ble.dart b/lib/pages/medical/my_trackers/ecg_ble.dart new file mode 100644 index 00000000..29e4726a --- /dev/null +++ b/lib/pages/medical/my_trackers/ecg_ble.dart @@ -0,0 +1,146 @@ +import 'package:diplomaticquarterapp/theme/colors.dart'; +import 'package:diplomaticquarterapp/uitl/ble_utils.dart'; +import 'package:diplomaticquarterapp/uitl/bluetooth_off.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/buttons/defaultButton.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class ECG_BLE extends StatefulWidget { + @override + State createState() => _ECG_BLEState(); +} + +class _ECG_BLEState extends State { + String connectionStatus = "disconnected"; + + BluetoothDevice currentConnectedDevice; + + BluetoothCharacteristic ecgWriteCharacteristic; + + @override + void dispose() { + super.dispose(); + if (currentConnectedDevice != null) currentConnectedDevice.disconnect(); + } + + + @override + void initState() { + super.initState(); + FlutterBluePlus.setLogLevel(LogLevel.verbose, color:false); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + appBarTitle: "ECG", + showNewAppBar: true, + isShowDecPage: true, + showNewAppBarTitle: true, + backgroundColor: Color(0xffF8F8F8), + body: SingleChildScrollView( + child: StreamBuilder( + stream: FlutterBluePlus.adapterState, + initialData: BluetoothAdapterState.unknown, + builder: (c, snapshot) { + final adapterState = snapshot.data; + if (adapterState == BluetoothAdapterState.on) { + return Container( + margin: EdgeInsets.only(top: 200.0, left: 50.0, right: 50.0), + child: Column( + children: [ + Center( + child: DefaultButton( + TranslationBase.of(context).start.toUpperCase(), + () { + checkBLEPermissions(); + }, + color: CustomColors.green, + ), + ), + SizedBox( + height: 50.0, + ), + Text("Connection state: $connectionStatus"), + SizedBox( + height: 50.0, + ), + // Text("Current Temp: $currentTempInCelsius"), + ], + ), + ); + } else { + FlutterBluePlus.stopScan(); + return SizedBox(height: 300.0, child: BluetoothOffScreen(adapterState: adapterState)); + } + }), + ), + ); + } + + void checkBLEPermissions() { + [Permission.location, Permission.storage, Permission.bluetooth, Permission.bluetoothConnect, Permission.bluetoothScan].request().then((status) { + startBLEConnection(); + }); + } + + void startBLEConnection() { + if (FlutterBluePlus.isScanningNow == false) { + setState(() { + connectionStatus = "Connecting..."; + }); + + FlutterBluePlus.startScan(timeout: const Duration(seconds: 5), androidUsesFineLocation: false).then((value) { + List blueToothDevices = value; + blueToothDevices.forEach((element) async { + if (element.device.localName.isNotEmpty) { + if (element.device.localName.toLowerCase() == "pm101897") { + element.device.connectionState.listen((BluetoothConnectionState state) async { + if(mounted) { + setState(() { + connectionStatus = state.toString(); + }); + } + if (state == BluetoothConnectionState.disconnected) { + // typically, start a periodic timer that tries to periodically reconnect. + // Note: you must always re-discover services after disconnection! + } + if (state == BluetoothConnectionState.connected) { + currentConnectedDevice = element.device; + List services = await element.device.discoverServices(); + services.forEach((service) { + if (service.serviceUuid.toString().toLowerCase() == BLEUtils.ECG_SERVICE) { + print(service.serviceUuid); + service.characteristics.forEach((characteristic) async { + if (characteristic.characteristicUuid.toString().toLowerCase() == BLEUtils.ECG_READ_CHARACTERISTIC) { + print(characteristic.characteristicUuid); + characteristic.onValueReceived.listen((event) { + print("onValueReceived Stream"); + print(event); + }); + await characteristic.setNotifyValue(true); + } + + if (characteristic.characteristicUuid.toString().toLowerCase() == BLEUtils.ECG_WRITE_CHARACTERISTIC) { + print(characteristic.characteristicUuid); + ecgWriteCharacteristic = characteristic; + ecgWriteCharacteristic.write([0x90], allowLongWrite: true); + } + }); + return true; + } + }); + } + }); + await element.device.connect(timeout: Duration(seconds: 35)); + return true; + } + } + }); + }); + } + } +} diff --git a/lib/pages/medical/my_trackers/my_trackers.dart b/lib/pages/medical/my_trackers/my_trackers.dart index 7d3abefb..0c202e93 100644 --- a/lib/pages/medical/my_trackers/my_trackers.dart +++ b/lib/pages/medical/my_trackers/my_trackers.dart @@ -2,6 +2,8 @@ import 'package:diplomaticquarterapp/core/model/ImagesInfo.dart'; import 'package:diplomaticquarterapp/pages/medical/my_trackers/Weight/WeightHomePage.dart'; import 'package:diplomaticquarterapp/pages/medical/my_trackers/blood_pressure/BloodPressureBLE.dart'; import 'package:diplomaticquarterapp/pages/medical/my_trackers/blood_suger/blood_sugar_home_page.dart'; +import 'package:diplomaticquarterapp/pages/medical/my_trackers/ecg_ble.dart'; +import 'package:diplomaticquarterapp/pages/medical/my_trackers/spirometer.dart'; import 'package:diplomaticquarterapp/pages/medical/my_trackers/temperature.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/data_display/medical/medical_profile_item.dart'; @@ -27,12 +29,12 @@ class MyTrackers extends StatelessWidget { ], body: SingleChildScrollView( child: Container( - child: GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 2 / 2, crossAxisSpacing: 12, mainAxisSpacing: 12), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.all(21), - shrinkWrap: true, - children: [ + child: GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 2 / 2, crossAxisSpacing: 12, mainAxisSpacing: 12), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(21), + shrinkWrap: true, + children: [ InkWell( onTap: () { Navigator.push(context, FadePage(page: BloodSugarHomePage())); @@ -85,20 +87,48 @@ class MyTrackers extends StatelessWidget { height: 45.0, ), ), - InkWell( - onTap: () { - Navigator.push(context, FadePage(page: BloodPressureBLE())); - }, - child: MedicalProfileItem( - title: TranslationBase.of(context).bloodPressure + " BLE", - imagePath: 'assets/tracker/blood-pressure.png', - subTitle: null, - isPngImage: true, - width: 45.0, - height: 45.0, - ), - ), - ])), + InkWell( + onTap: () { + Navigator.push(context, FadePage(page: BloodPressureBLE())); + }, + child: MedicalProfileItem( + title: TranslationBase.of(context).bloodPressure + " BLE", + imagePath: 'assets/tracker/blood-pressure.png', + subTitle: null, + isPngImage: true, + width: 45.0, + height: 45.0, + ), + ), + InkWell( + onTap: () { + Navigator.push(context, FadePage(page: SpirometerBLE())); + }, + child: MedicalProfileItem( + title: TranslationBase.of(context).spirometer + " BLE", + imagePath: 'assets/tracker/blood-pressure.png', + subTitle: null, + isPngImage: true, + width: 45.0, + height: 45.0, + ), + ), + InkWell( + onTap: () { + Navigator.push(context, FadePage(page: ECG_BLE())); + }, + child: MedicalProfileItem( + title: "ECG BLE", + imagePath: 'assets/tracker/blood-pressure.png', + subTitle: null, + isPngImage: true, + width: 45.0, + height: 45.0, + ), + ), + ], + ), + ), ), ); } diff --git a/lib/pages/medical/my_trackers/spirometer.dart b/lib/pages/medical/my_trackers/spirometer.dart new file mode 100644 index 00000000..a00f8e7b --- /dev/null +++ b/lib/pages/medical/my_trackers/spirometer.dart @@ -0,0 +1,205 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:diplomaticquarterapp/theme/colors.dart'; +import 'package:diplomaticquarterapp/uitl/ble_utils.dart'; +import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; +import 'package:diplomaticquarterapp/widgets/buttons/defaultButton.dart'; +import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; +import 'package:permission_handler/permission_handler.dart'; + +class SpirometerBLE extends StatefulWidget { + @override + State createState() => _SpirometerBLEState(); +} + +class _SpirometerBLEState extends State { + String connectionStatus = "disconnected"; + BluetoothDevice currentConnectedDevice; + + Timer _timer; + + Timer _timerRead; + + @override + void dispose() { + super.dispose(); + if (currentConnectedDevice != null) currentConnectedDevice.disconnect(); + if (_timer != null && _timer.isActive) _timer.cancel(); + if (_timerRead != null && _timerRead.isActive) _timerRead.cancel(); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + appBarTitle: TranslationBase.of(context).spirometer, + showNewAppBar: true, + isShowDecPage: true, + showNewAppBarTitle: true, + backgroundColor: Color(0xffF8F8F8), + body: SingleChildScrollView( + child: StreamBuilder( + stream: FlutterBluePlus.adapterState, + initialData: BluetoothAdapterState.unknown, + builder: (c, snapshot) { + final adapterState = snapshot.data; + if (adapterState == BluetoothAdapterState.on) { + return Container( + margin: EdgeInsets.only(top: 200.0, left: 50.0, right: 50.0), + child: Column( + children: [ + Center( + child: DefaultButton( + TranslationBase.of(context).start.toUpperCase(), + () { + checkBLEPermissions(); + }, + color: CustomColors.green, + ), + ), + SizedBox( + height: 50.0, + ), + Text("Connection state: $connectionStatus"), + SizedBox( + height: 50.0, + ), + // Text("Current Temp: $currentTempInCelsius"), + ], + ), + ); + } else { + FlutterBluePlus.stopScan(); + return SizedBox(height: 300.0, child: BluetoothOffScreen(adapterState: adapterState)); + } + }), + ), + ); + } + + void checkBLEPermissions() { + [Permission.location, Permission.storage, Permission.bluetooth, Permission.bluetoothConnect, Permission.bluetoothScan].request().then((status) { + startBLEConnection(); + }); + } + + void startBLEConnection() { + if (FlutterBluePlus.isScanningNow == false) { + setState(() { + connectionStatus = "Connecting..."; + }); + + FlutterBluePlus.startScan(timeout: const Duration(seconds: 5), androidUsesFineLocation: false).then((value) { + List blueToothDevices = value; + blueToothDevices.forEach((element) async { + if (element.device.localName.isNotEmpty) { + if (element.device.localName.toLowerCase() == "ble-msa") { + element.device.connectionState.listen((BluetoothConnectionState state) async { + setState(() { + connectionStatus = state.toString(); + }); + if (state == BluetoothConnectionState.disconnected) { + // typically, start a periodic timer that tries to periodically reconnect. + // Note: you must always re-discover services after disconnection! + if (_timer != null && _timer.isActive) _timer.cancel(); + if (_timerRead != null && _timerRead.isActive) _timerRead.cancel(); + } + if (state == BluetoothConnectionState.connected) { + currentConnectedDevice = element.device; + + List BLEToMSAWriteCharacters = []; + BLEToMSAWriteCharacters.add(int.parse("5506", radix: 16)); + + List services = await element.device.discoverServices(); + services.forEach((service) { + if (service.serviceUuid.toString().toLowerCase() == BLEUtils.BLE_TO_MSA100_SERVICE) { + print(service.serviceUuid); + service.characteristics.forEach((characteristic) async { + if (characteristic.characteristicUuid.toString().toLowerCase() == BLEUtils.BLE_TO_MSA100_CHARACTERISTIC) { + print(characteristic.characteristicUuid); + _timer = Timer.periodic(Duration(seconds: 2), (Timer timer) { + characteristic.write(BLEToMSAWriteCharacters).then((value) { + print("Characteristic response:"); + }).catchError((err) {}); + }); + } + if (characteristic.characteristicUuid.toString().toLowerCase() == BLEUtils.MSA100_TO_BLE_CHARACTERISTIC) { + print(characteristic.characteristicUuid); + _timerRead = Timer.periodic(Duration(seconds: 2), (Timer timer) { + characteristic.read().then((value) { + print("Characteristic Read value: "); + print(value); + }); + }); + + // characteristic.onValueReceived.listen((event) { + // print("onValueReceived Stream"); + // print(event); + // setState(() { + // currentTempInCelsius = convertIntListToHex(event); + // String currentTempInFahrenheit = ((num.parse(currentTempInCelsius) * 1.8) + 32).toStringAsFixed(1); + // currentTempInCelsius = currentTempInCelsius + "\u2103" + " / " + currentTempInFahrenheit + "\u2109"; + // }); + // }); + // await characteristic.setNotifyValue(true); + } + }); + return true; + } + }); + } + }); + await element.device.connect(timeout: Duration(seconds: 35)); + return true; + } + } + }); + }); + } + } +} + +class BluetoothOffScreen extends StatelessWidget { + const BluetoothOffScreen({Key key, this.adapterState}) : super(key: key); + + final BluetoothAdapterState adapterState; + + @override + Widget build(BuildContext context) { + return ScaffoldMessenger( + child: Scaffold( + backgroundColor: Colors.lightBlue, + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.bluetooth_disabled, + size: 200.0, + color: Colors.white54, + ), + Text( + 'Bluetooth Adapter is ${adapterState != null ? adapterState.toString().split(".").last + ", Please turn on your bluetooth to continue." : 'not available'}.', + textAlign: TextAlign.center, + style: Theme.of(context).primaryTextTheme.titleSmall?.copyWith(color: Colors.white), + ), + if (Platform.isAndroid) + ElevatedButton( + child: const Text('TURN ON'), + onPressed: () async { + try { + if (Platform.isAndroid) { + await FlutterBluePlus.turnOn(); + } + } catch (e) {} + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/medical/my_trackers/temperature.dart b/lib/pages/medical/my_trackers/temperature.dart index 1ca0cd03..f4afd9ce 100644 --- a/lib/pages/medical/my_trackers/temperature.dart +++ b/lib/pages/medical/my_trackers/temperature.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:diplomaticquarterapp/theme/colors.dart'; import 'package:diplomaticquarterapp/uitl/ble_utils.dart'; +import 'package:diplomaticquarterapp/uitl/bluetooth_off.dart'; import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart'; import 'package:diplomaticquarterapp/widgets/buttons/defaultButton.dart'; import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart'; @@ -149,46 +150,3 @@ class _TemperatureHomePageState extends State { return (number * 0.01).toStringAsFixed(1); } } - -class BluetoothOffScreen extends StatelessWidget { - const BluetoothOffScreen({Key key, this.adapterState}) : super(key: key); - - final BluetoothAdapterState adapterState; - - @override - Widget build(BuildContext context) { - return ScaffoldMessenger( - child: Scaffold( - backgroundColor: Colors.lightBlue, - body: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.bluetooth_disabled, - size: 200.0, - color: Colors.white54, - ), - Text( - 'Bluetooth Adapter is ${adapterState != null ? adapterState.toString().split(".").last + ", Please turn on your bluetooth to continue." : 'not available'}.', - textAlign: TextAlign.center, - style: Theme.of(context).primaryTextTheme.titleSmall?.copyWith(color: Colors.white), - ), - if (Platform.isAndroid) - ElevatedButton( - child: const Text('TURN ON'), - onPressed: () async { - try { - if (Platform.isAndroid) { - await FlutterBluePlus.turnOn(); - } - } catch (e) {} - }, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/uitl/ble_utils.dart b/lib/uitl/ble_utils.dart index 83a3ec01..4f6059b8 100644 --- a/lib/uitl/ble_utils.dart +++ b/lib/uitl/ble_utils.dart @@ -6,4 +6,17 @@ class BLEUtils { //Temperature static const String TEMPERATURE_SERVICE = "00001809-0000-1000-8000-00805f9b34fb"; static const String TEMPERATURE_CHARACTERISTIC = "00002a1c-0000-1000-8000-00805f9b34fb"; -} + + //Spirometer + static const String BLE_TO_MSA100_SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb"; + static const String BLE_TO_MSA100_CHARACTERISTIC = "0000ff0b-0000-1000-8000-00805f9b34fb"; + + static const String MSA100_TO_BLE_SERVICE = "0000fff0-0000-1000-8000-00805f9b34fb"; + static const String MSA100_TO_BLE_CHARACTERISTIC = "0000ff0a-0000-1000-8000-00805f9b34fb"; + + //ECG + static const String ECG_SERVICE = "49535343-fe7d-4ae5-8fa9-9fafd205e455"; // + static const String ECG_READ_CHARACTERISTIC = "49535343-1e4d-4bd9-ba61-23c647249616"; //49535343-1e4d-4bd9-ba61-23c647249616 + static const String ECG_WRITE_CHARACTERISTIC = "49535343-8841-43f4-a8d4-ecbe34729bb3"; //49535343-8841-43f4-a8d4-ecbe34729bb3 + +} \ No newline at end of file diff --git a/lib/uitl/bluetooth_off.dart b/lib/uitl/bluetooth_off.dart new file mode 100644 index 00000000..f0a7f6b7 --- /dev/null +++ b/lib/uitl/bluetooth_off.dart @@ -0,0 +1,47 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; + +class BluetoothOffScreen extends StatelessWidget { + const BluetoothOffScreen({Key key, this.adapterState}) : super(key: key); + + final BluetoothAdapterState adapterState; + + @override + Widget build(BuildContext context) { + return ScaffoldMessenger( + child: Scaffold( + backgroundColor: Colors.lightBlue, + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.bluetooth_disabled, + size: 200.0, + color: Colors.white54, + ), + Text( + 'Bluetooth Adapter is ${adapterState != null ? adapterState.toString().split(".").last + ", Please turn on your bluetooth to continue." : 'not available'}.', + textAlign: TextAlign.center, + style: Theme.of(context).primaryTextTheme.titleSmall?.copyWith(color: Colors.white), + ), + if (Platform.isAndroid) + ElevatedButton( + child: const Text('TURN ON'), + onPressed: () async { + try { + if (Platform.isAndroid) { + await FlutterBluePlus.turnOn(); + } + } catch (e) {} + }, + ), + ], + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/uitl/translations_delegate_base.dart b/lib/uitl/translations_delegate_base.dart index 8d8f51ee..0e51fa4b 100644 --- a/lib/uitl/translations_delegate_base.dart +++ b/lib/uitl/translations_delegate_base.dart @@ -2902,6 +2902,7 @@ class TranslationBase { String get sickLeaveAdmittedPatient => localizedValues["sickLeaveAdmittedPatient"][locale.languageCode]; String get dischargeDate => localizedValues["dischargeDate"][locale.languageCode]; String get selectAdmissionText => localizedValues["selectAdmissionText"][locale.languageCode]; + String get spirometer => localizedValues["spirometer"][locale.languageCode]; }