Sistema de monitoramento de saúde
Tempo de leitura: 8 minutes
ESP32 + LM35 + MAX30102 +Firebase-database + Flutter
Arduino path
Firebase
devices
Apps
Código Fonte (Arduino)
#define analogTem 35 #include <WiFi.h> #include <Wire.h> #include <FirebaseESP32.h> #include "MAX30105.h" #include "heartRate.h" #define WIFI_SSID "godusevpn" // Change the name of your WIFI #define WIFI_PASSWORD "dsa21413" // Change the password of your WIFI #define FIREBASE_HOST "https://fyp-test-c34d8-default-rtdb.firebaseio.com/" #define FIREBASE_Authorization_key "w5kidxuOqHNKtMB4EbhEJuF8lFAyXhLKJGfypMXV" MAX30105 particleSensor; const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good. byte rates[RATE_SIZE]; //Array of heart rates byte rateSpot = 0; long lastBeat = 0; //Time at which the last beat occurred float beatsPerMinute; float BPM = 0; int beatAvg; int Avg =0; FirebaseData firebaseData; FirebaseJson json; uint32_t tsLastReport = 0; void onBeatDetected(){ Serial.println("Beat Detected!"); } void connectToNetwork(){ Serial.println("Connected to network"); Serial.println(WIFI_SSID); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED){ delay(500); Serial.println("Establishing connection to WiFi.."); } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); } void MAX30102pox(){ if (!particleSensor.begin(Wire, I2C_SPEED_FAST)){ //Use default I2C port, 400kHz speed Serial.println("MAX30105 was not found. Please check wiring/power. "); while (1); } Serial.println("Place your index finger on the sensor with steady pressure."); particleSensor.setup(); //Configure sensor with default settings particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED } void setup(){ Serial.begin(115200); //wifi connect____________________________________________ connectToNetwork(); //MAX30102 connect________________________________________ MAX30102pox(); //firebase connect________________________________________ Firebase.begin(FIREBASE_HOST,FIREBASE_Authorization_key); } void loop(){ long irValue = particleSensor.getIR(); int TemRawValue = analogRead(analogTem); float Voltage = TemRawValue * (1330 / 1024.0);//3.3V and arm 1330 float TempData = Voltage * 0.1; if (checkForBeat(irValue) == true){ long delta = millis() - lastBeat; lastBeat = millis(); //Serial.println(delta);//hand base 2000-3000 beatsPerMinute =60/(delta / 3800.0);//org 1000 testing 3000 //Firebase.setFloat(firebaseData,"/ESP32/BPM", beatsPerMinute); if (beatsPerMinute < 255 && beatsPerMinute > 20){ rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array rateSpot %= RATE_SIZE; //Wrap variable beatAvg = 0;//Take average of reading for (byte x = 0 ; x < RATE_SIZE ; x++) beatAvg += rates[x]; beatAvg /= RATE_SIZE; Firebase.setFloat(firebaseData,"/ESP32/Avg", beatAvg); Firebase.setFloat(firebaseData,"/ESP32/Temperature", TempData); } } Serial.print(beatsPerMinute); Serial.println("bpm"); Serial.print(beatAvg); Serial.println("avg bpm"); Serial.print(TempData,1); Serial.println(" \xC2\xB0 C"); }
Código Fonte Flutter
main.dart
import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import './DataDisplay.dart'; import 'package:testingesp32/DataDisplay.dart'; import 'package:flutter/cupertino.dart'; import 'package:adaptive_theme/adaptive_theme.dart'; void manin() async{ WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return AdaptiveTheme( light: ThemeData( brightness: Brightness.light, primarySwatch: Colors.grey, accentColor: Colors.black, ), dark: ThemeData( brightness: Brightness.dark, primarySwatch: Colors.grey, accentColor: Colors.black, ), initial: AdaptiveThemeMode.light, builder: (theme, darkTheme) => MaterialApp( title: 'Adaptive Theme Demo', theme: theme, darkTheme: darkTheme, debugShowCheckedModeBanner: false, home: DisplayPage(), ), ); } }
stringValue.dart
import 'package:flutter/material.dart'; String theLocate ="en"; double textSizeValue =24; String systemText(String text) { return Strings().getText(text); } TextStyle getTextSetting(double size) { return Strings().getTextStyle(); } class Strings { String getText(String text) { switch (theLocate) { // eng ================================================================================================================== case 'en': switch (text) { case 'title': return 'Welcome to Health Monitor System'; break; case 'Login': return 'Login in database'; break; case 'apptitle': return 'Health Monitor App'; break; case 'bpmtitle': return 'Heart Bit bpm'; break; case 'temptitle': return 'Body Temperature'; break; case 'nowtempis': return 'Now Temperature '; break; case 'nowbpm': return 'Now Heart bit '; break; case '°C': return '°C'; break; case 'bpm': return 'bpm'; break; case 'Language': return 'Language Setting'; break; case 'english': return 'English'; break; case 'chinese': return 'Chinese'; break; case 'AppTitle': return 'Health Monitor System'; break; case 'settingPage': return 'Main Setting'; break; case 'Other Setting': return 'Other Setting'; break; case 'close': return 'Close'; break; case 'fontsize': return 'Text Size'; break; case 'small': return 'S'; break; case 'medium': return 'M'; break; case 'large': return 'L'; break; case 'mode': return 'Light Theme'; break; case 'lightmode': return 'Light'; break; case 'darkmode': return 'Dark'; break; case 'call': return 'Ask for help'; break; default: return 'error'; break; } break; // zh locate ================================================================================================================== case 'zh': switch (text) { case 'AppTitle': return '健康監測系統'; break; case 'bpm': return ' '; break; case 'bpmtitle': return '心跳率監測'; break; case 'nowbpm': return '現在心跳率每分鐘'; break; case 'temp': return '體溫'; break; case '°C': return '度'; break; case 'temptitle': return '體溫監測'; break; case 'nowtempis': return '現在體溫'; break; case 'Language': return '語言設定'; break; case 'english': return '英文'; break; case 'chinese': return '中文'; break; case 'fontsize': return '字體大小'; break; case 'small': return '小'; break; case 'medium': return '中'; break; case 'large': return '大'; break; case 'textmode': return '文本模式'; break; case 'Other Setting': return '其他設定'; break; case 'settingPage': return '主要設定'; break; case 'mode': return '顯示模式'; break; case 'lightmode': return '亮模式'; break; case 'darkmode': return '深色模式'; break; case 'close': return '關閉'; break; case 'call': return '尋求幫助'; break; default: return '文字出現錯誤'; break; } break; // default ================================================================================================================== default: switch (text) { case 'welcome': return 'welcome'; break; default: return 'error'; break; } break; } } TextStyle getTextStyle() { return TextStyle(fontSize: textSizeValue); } Color getThemeTextColor(String theme) { if (theme == 'light') { return Colors.black; } else if (theme == 'dark') { return Colors.white; } return Colors.white; } } class ThemeChanger with ChangeNotifier { Brightness _themeData; double _textFont; String _displayList; int _sortedNumber; ThemeChanger(this._themeData, this._textFont); getTheme() => _themeData; getTextFont() => _textFont; getDisplayList() => _displayList; getSortedNumber () => _sortedNumber; setSortedNumber (int sortedNumber) { _sortedNumber = sortedNumber; notifyListeners(); } setTheme(Brightness theme) { _themeData = theme; notifyListeners(); } setTextFont(double textFont) { _textFont = textFont; notifyListeners(); } setDisplayList(String displayList) { _displayList = displayList; notifyListeners(); } }
dataHandle.dart
class ESP{ final double temp; final double avg; final double bpm; ESP({this.temp, this.avg,this.bpm}); factory ESP.fromJson(Map<dynamic,dynamic> json){ double dataValue(dynamic source){ try{ return double.parse(source.toString()); } on FormatException{ return -1; } } return ESP( temp: dataValue(json['Temperature']), bpm: dataValue(json['BPM']), avg: dataValue(json['Avg'])); } }
DataDisplay.dart
import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_icons/flutter_icons.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:flutter_animation_progress_bar/flutter_animation_progress_bar.dart'; import '/dataHandle.dart'; import 'package:oscilloscope/oscilloscope.dart'; import 'package:testingesp32/stringValue.dart'; import 'package:adaptive_theme/adaptive_theme.dart'; class DisplayPage extends StatefulWidget { final Widget Function(BuildContext context,Brightness brightness) builder; DisplayPage({this.builder}); @override _DisplayPageState createState() => _DisplayPageState(); } class _DisplayPageState extends State<DisplayPage> with SingleTickerProviderStateMixin{ TabController _tabController; int tabSet = 0; int _radioModeValue; int _radioTextValue; int _radioLanguageValue; double textSizeChange = 1; final FirebaseAuth _auth =FirebaseAuth.instance; DatabaseReference _espRef = FirebaseDatabase.instance.ref().child('ESP32'); bool _login; // ignore: deprecated_member_use List<double> traceDustTemp =List(); // ignore: deprecated_member_use List<double> traceDustBpm =List(); @override void initState() { // TODO: implement initState setState(() { _radioModeValue = 0; _radioTextValue = 1; _radioLanguageValue = 1; }); super.initState(); _tabController= TabController(length: 3, vsync: this); _login =false; } @override Widget build(BuildContext context) { return _login ? firstScaffold() : logInScaffold(); } Widget firstScaffold(){ return Scaffold( appBar: AppBar( title: Text(systemText("AppTitle"), style: TextStyle(fontSize: 22*textSizeChange), ), bottom: TabBar( indicatorColor: Colors.cyanAccent, indicatorSize: TabBarIndicatorSize.label, controller: _tabController, onTap: (int index){setState(() {tabSet = index;});}, tabs: [ Tab(icon: Icon(MaterialCommunityIcons.temperature_celsius),), Tab(icon: Icon(Icons.favorite),), Tab(icon: Icon(Icons.settings),) ], ), ), body: StreamBuilder( stream: _espRef.onValue, builder: (context, snapshot) { if (snapshot.hasData && !snapshot.hasError && snapshot.data.snapshot.value != null){ print("snapshot data:${snapshot.data.snapshot.value.toString()}"); var _esp =ESP.fromJson(snapshot.data.snapshot.value); print("ESP json data :${_esp.temp}/${_esp.bpm}/${_esp.avg}"); traceDustTemp.add(double.tryParse(_esp.temp.toStringAsFixed(1)) ?? 0); traceDustBpm.add(double.tryParse(_esp.avg.toStringAsFixed(0)) ?? 0); return IndexedStack( index: tabSet, children: [_tempUI(_esp),_bpmUI(_esp),_settingUI(_esp),], ); }else{ return Center( child: Text("NO DATA YET"), ); } } ), ); } static const Radius _borderRadius = const Radius.circular(50); Widget _tempUI(ESP _esp){ Oscilloscope vied = Oscilloscope( showYAxis: true, margin: EdgeInsets.all(20.10), strokeWidth: 5, backgroundColor: Colors.grey, traceColor: Colors.red, yAxisMax: 45.0, yAxisMin: 30.0, dataSet: traceDustTemp, ); return Center( child:Column( children: [ Container( padding: const EdgeInsets.only(top: 50), child: Text(systemText("temptitle"), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30 *textSizeChange ), ), ), Expanded( flex: 1, child: Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Row( children:<Widget>[ Container( width: 200, margin: EdgeInsets.all(40), child: vied, ), FAProgressBar( direction: Axis.vertical, verticalDirection: VerticalDirection.up, currentValue:_esp.temp, progressColor: Colors.green, backgroundColor: Colors.grey, changeProgressColor: Colors.red, size: 80, maxValue: 50, changeColorValue: 41, displayText: " °C", borderRadius: BorderRadius.all(_borderRadius), animatedDuration: Duration(milliseconds: 1000), ), ] ), ), ), Container( padding: const EdgeInsets.only(bottom: 50), child: Text('${systemText('nowtempis')}' '${_esp.temp.toStringAsFixed(1)} ${systemText('°C')}', style: TextStyle(fontWeight: FontWeight.bold,fontSize: 30* textSizeChange), ), ), ], ) ); } Widget _bpmUI(ESP _esp){ Oscilloscope ibp = Oscilloscope( showYAxis: true, margin: EdgeInsets.all(20.10), strokeWidth: 5, backgroundColor: Colors.grey, traceColor: Colors.red, yAxisMax: 200.0, yAxisMin: 20.0, dataSet: traceDustBpm, ); return Center( child:Column( children: [ Container( padding: const EdgeInsets.only(top:50), child: Text(systemText("bpmtitle"), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 30 *textSizeChange), ), ), Expanded( flex: 1, child: Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Row( children:<Widget>[ Container( width: 200, margin: EdgeInsets.all(40), child: ibp, ), FAProgressBar( direction: Axis.vertical, verticalDirection: VerticalDirection.up, currentValue:_esp.avg, progressColor: Colors.pink, backgroundColor: Colors.grey, changeProgressColor: Colors.red, size: 80, maxValue: 180, changeColorValue: 105,//5 value change displayText: "bpm", borderRadius: BorderRadius.all(_borderRadius), animatedDuration: Duration(milliseconds: 1000), ), ], ) ), ), Container( padding: const EdgeInsets.only(bottom: 50), child: Text('${systemText('nowbpm')} ' '${_esp.avg.toStringAsFixed(0)} ${systemText('bpm')}', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 30* textSizeChange ), ), ), ], ) ); } Widget _settingUI(ESP _esp){ return Scaffold( body: Container( padding: const EdgeInsets.all(10), child: ListView( children: [ SizedBox(height: 40), Row( children: [ Icon(Icons.settings, color: Colors.indigoAccent,), SizedBox(width: 10), Text(systemText("settingPage"), style: TextStyle( fontSize: 24 * textSizeChange, fontWeight: FontWeight.bold, letterSpacing: 1.2 ), ) ], ), Divider(height: 20, thickness: 1), SizedBox(height: 10), //buildSetting(context, "Content Settings"), buildSetting(context, systemText("Language")), SizedBox(height: 20), Row( children: [ Icon(Icons.settings, color: Colors.indigoAccent,), SizedBox(width: 10), Text(systemText("Other Setting"), style: TextStyle( fontSize: 24 *textSizeChange, fontWeight: FontWeight.bold, letterSpacing: 1.2 ), ) ], ), Divider(height: 20, thickness: 1), SizedBox(height: 10), Row( children: <Widget>[ Container( height: 50, margin: EdgeInsets.all(10), //width: 110, child: Text( systemText('fontsize'), textAlign: TextAlign.center, style: TextStyle( fontSize: 20 *textSizeChange ), ), alignment: Alignment(0.0, 0.0), ), Container( height: 30, margin: EdgeInsets.all(20), child: Row( children: <Widget>[ Text(systemText('small'), style: TextStyle(fontSize: 16 *textSizeChange), ), new Radio( value: 0, groupValue: _radioTextValue, onChanged: _handleRadioTextValueChange, ), Text(systemText('medium'), style: TextStyle(fontSize: 20 *textSizeChange), ), new Radio( value: 1, groupValue: _radioTextValue, onChanged: _handleRadioTextValueChange, ), Text(systemText('large'), style: TextStyle(fontSize: 22 *textSizeChange), ), new Radio( value: 2, groupValue: _radioTextValue, onChanged: _handleRadioTextValueChange, ), ], ), ), ], ), SizedBox(height: 20), Row( children: <Widget>[ Container( margin: EdgeInsets.all(10), //height: 60, //width: 80, child: Text( systemText('mode'), textAlign: TextAlign.center, style: TextStyle(fontSize: 20*textSizeChange), ), alignment: Alignment(0.0, 0.0), ), Container( height: 30, decoration: BoxDecoration( //border: Border.all(width: 2.8, color: Colors.red), borderRadius: BorderRadius.circular(12)), child: Row( children: <Widget>[ Text(systemText('lightmode'), style: TextStyle(fontSize: 18*textSizeChange),), new Radio( value: 0, groupValue: _radioModeValue, onChanged: _handleRadioModeValueChange, ), Text(systemText('darkmode'), style: TextStyle(fontSize: 18*textSizeChange),), new Radio( value: 1, groupValue: _radioModeValue, onChanged: _handleRadioModeValueChange, ), ], ), ), ], ), Center( child: OutlinedButton( style:OutlinedButton.styleFrom( padding: const EdgeInsets.symmetric(horizontal: 40), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20) ) ), onPressed: (){}, child: Text(systemText("call"), style: TextStyle( fontSize: 16* textSizeChange, letterSpacing: 2.2, //color: Colors.black ),), ), ) ], ), ), ); } GestureDetector buildSetting(BuildContext context, String title){ return GestureDetector( onTap: (){ showDialog(context: context, builder: (BuildContext context){ return AlertDialog( title: Text(title,style: TextStyle(fontSize: 22*textSizeChange),), content: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: <Widget>[ Container( margin: EdgeInsets.all(5), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12)), child: Row( children: <Widget>[ Text(systemText('chinese')), new Radio( value: 0, groupValue: _radioLanguageValue, onChanged: _handleRadioLanguageValueChange, ), Text(systemText('english')), new Radio( value: 1, groupValue: _radioLanguageValue, onChanged: _handleRadioLanguageValueChange, ), ], ), ) ], ) ], ), actions: [ TextButton( onPressed: (){ Navigator.of(context).pop(); }, child: Text(systemText("close"))) ], ); }); }, child: Padding( padding: const EdgeInsets.symmetric(vertical: 8,horizontal: 20), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(title,style: TextStyle( fontSize: 20* textSizeChange, fontWeight: FontWeight.w500, )), Icon(Icons.arrow_forward,color: Colors.grey) ], ), ), ); } Widget logInScaffold(){ return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [Text(systemText("title"), style: TextStyle(fontWeight :FontWeight.bold, fontSize: 20), ), SizedBox(height: 50,), ElevatedButton( child: Text(systemText('Login'), style: TextStyle(fontSize: 20), ), style: ElevatedButton.styleFrom( shape: new RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: BorderSide(color: Colors.black) )), onPressed: ()async{ _signIn(); }, )], ), ), ); } void _signIn() async { final User user = (await _auth.signInAnonymously()).user; print("*** user isAnonymous: ${user.isAnonymous}"); print("*** user uid: ${user.uid}"); setState(() { if (user != null) { _login = true; } else { _login = false; } }); } void _handleRadioModeValueChange(int value){ setState(() { _radioModeValue =value; if(_radioModeValue == 0){ AdaptiveTheme.of(context).setLight(); }else if(_radioModeValue ==1){ AdaptiveTheme.of(context).setDark(); } }); } void _handleRadioTextValueChange(int value) { setState(() { _radioTextValue = value; if (_radioTextValue == 0) { return textSizeChange =0.8; } else if (_radioTextValue == 1) { return textSizeChange =1; } else if (_radioTextValue == 2) { return textSizeChange =1.2; } }); } void _handleRadioLanguageValueChange(int value) { setState(() { _radioLanguageValue = value; if (_radioLanguageValue == 0) { setState(() { theLocate = 'zh'; }); } else if (_radioLanguageValue == 1) { setState(() { theLocate = 'en'; }); } }); } }
pubspec.yaml
name: testingesp32 description: A new Flutter application. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ">=2.7.0 <3.0.0" dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.2 firebase_auth: ^3.0.0 firebase_database: ^9.0.7 google_sign_in: ^5.1.0 flutter_animation_progress_bar: ^2.1.1 flutter_icons: ^1.1.0 oscilloscope: ^0.2.0+1 adaptive_theme: ^2.3.1 dev_dependencies: flutter_test: sdk: flutter firebase_core : ^1.10.0 flutter: uses-material-design: true