浏览代码

Added bt loading and connecting code WIP

Toby Chui 2 年之前
父节点
当前提交
eaf41e566c

+ 1 - 0
src/pm25go/android/app/build.gradle

@@ -51,6 +51,7 @@ android {
         targetSdkVersion flutter.targetSdkVersion
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
+        minSdkVersion 19
     }
 
     buildTypes {

+ 4 - 0
src/pm25go/android/app/src/main/AndroidManifest.xml

@@ -1,5 +1,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.pm25go">
+    <uses-permission android:name="android.permission.BLUETOOTH" />  
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />  
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 
+    <uses-permission android:name="android.permission.INTERNET"/>
    <application
         android:label="Pocket PM2.5 Monitor"
         android:name="${applicationName}"

二进制
src/pm25go/assets/images/banner.png


二进制
src/pm25go/assets/images/banner.psd


+ 10 - 0
src/pm25go/ios/Runner/Info.plist

@@ -2,6 +2,16 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
+	<key>NSBluetoothAlwaysUsageDescription</key>  
+	<string>Need BLE permission</string>  
+	<key>NSBluetoothPeripheralUsageDescription</key>  
+	<string>Need BLE permission</string>  
+	<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>  
+	<string>Need Location permission</string>  
+	<key>NSLocationAlwaysUsageDescription</key>  
+	<string>Need Location permission</string>  
+	<key>NSLocationWhenInUseUsageDescription</key>  
+	<string>Need Location permission</string>
 	<key>CFBundleDevelopmentRegion</key>
 	<string>$(DEVELOPMENT_LANGUAGE)</string>
 	<key>CFBundleDisplayName</key>

+ 101 - 21
src/pm25go/lib/main.dart

@@ -1,4 +1,6 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
+import 'package:pm25go/scan.dart';
 
 void main() {
   runApp(const MyApp());
@@ -22,13 +24,33 @@ class MyApp extends StatelessWidget {
         // or simply save your changes to "hot reload" in a Flutter IDE).
         // Notice that the counter didn't reset back to zero; the application
         // is not restarted.
-        primarySwatch: Colors.deepPurple,
+        primarySwatch: createMaterialColor(Color.fromARGB(255, 64, 155, 253)),
       ),
       home: const MyHomePage(title: 'Pocket PM2.5 Monitor'),
     );
   }
 }
 
+MaterialColor createMaterialColor(Color color) {
+  List strengths = <double>[.05];
+  final swatch = <int, Color>{};
+  final int r = color.red, g = color.green, b = color.blue;
+
+  for (int i = 1; i < 10; i++) {
+    strengths.add(0.1 * i);
+  }
+  strengths.forEach((strength) {
+    final double ds = 0.5 - strength;
+    swatch[(strength * 1000).round()] = Color.fromRGBO(
+      r + ((ds < 0 ? r : (255 - r)) * ds).round(),
+      g + ((ds < 0 ? g : (255 - g)) * ds).round(),
+      b + ((ds < 0 ? b : (255 - b)) * ds).round(),
+      1,
+    );
+  });
+  return MaterialColor(color.value, swatch);
+}
+
 class MyHomePage extends StatefulWidget {
   const MyHomePage({super.key, required this.title});
 
@@ -48,23 +70,50 @@ class MyHomePage extends StatefulWidget {
 }
 
 class _MyHomePageState extends State<MyHomePage> {
-  int _counter = 0;
-  var _statusText = "Waiting for Bluetooth Connection...";
+  var _statusText = "正在等待藍牙運接…\nWaiting for Bluetooth Connection...";
+
+  //Bluetooth Connection
+  bool _btConnected = false;
+  var _btAddr = "";
 
+  //Data update for display
   int _pm01 = 0; //No recommended data found
   int _pm25 = 0; //Healthy range: 35, recommend < 15
   int _pm10 = 0; //Healthy range: 150, recommend < 50
-  void _incrementCounter() {
+  void _toggleBluetooth() {
     setState(() {
       // This call to setState tells the Flutter framework that something has
       // changed in this State, which causes it to rerun the build method below
       // so that the display can reflect the updated values. If we changed
       // _counter without calling setState(), then the build method would not be
       // called again, and so nothing would appear to happen.
-      _counter++;
+      Navigator.push(
+        context,
+        MaterialPageRoute(builder: (context) => const ConnectionPage()),
+      );
+      return;
+      if (_btConnected) {
+        //Disconnect the current bt connection
+        _statusText = "已斷開連接\nDisconnected";
+        _btConnected = false;
+      } else {
+        //Connect to the target bluetooth module
+        _statusText = "正在連接口袋 PM2.5 傳感器…\nConnecting to Pocket PM2.5 Sensor...";
+
+        _connectBluetoothDevice();
+      }
     });
   }
 
+  void _connectBluetoothDevice() {
+    try {
+      _btConnected = true;
+    } catch (exception) {
+      //Connection to BT failed
+      _btConnected = false;
+    }
+  }
+
   @override
   Widget build(BuildContext context) {
     // This method is rerun every time setState is called, for instance as done
@@ -75,11 +124,13 @@ class _MyHomePageState extends State<MyHomePage> {
     // than having to individually change instances of widgets.
     return Scaffold(
       resizeToAvoidBottomInset: false,
-      appBar: AppBar(
+      appBar:
+          null /*AppBar(
         // Here we take the value from the MyHomePage object that was created by
         // the App.build method, and use it to set our appbar title.
         title: Text(widget.title),
-      ),
+      )*/
+      ,
       body: SingleChildScrollView(
           child: Center(
         // Center is a layout widget. It takes a single child and positions it
@@ -101,6 +152,8 @@ class _MyHomePageState extends State<MyHomePage> {
           // horizontal).
           mainAxisAlignment: MainAxisAlignment.start,
           children: <Widget>[
+            //Title banner
+            Image.asset('assets/images/banner.png'),
             //Status Text
             Container(
               width: double.infinity,
@@ -122,8 +175,15 @@ class _MyHomePageState extends State<MyHomePage> {
                         height: 120,
                         color: Colors.blue,
                         child: Text(
-                          "",
-                          style: TextStyle(color: Colors.white),
+                          (() {
+                            if (_pm01 == 0) {
+                              return "無數據";
+                            } else if (_pm01 < 35) {
+                              return "良好";
+                            }
+                            return "一般";
+                          }()),
+                          style: const TextStyle(color: Colors.white),
                         )),
                   ),
                   Expanded(
@@ -131,17 +191,17 @@ class _MyHomePageState extends State<MyHomePage> {
                     child: Container(
                         color: Colors.white,
                         height: 120,
-                        padding: EdgeInsetsDirectional.only(top: 35),
+                        padding: const EdgeInsetsDirectional.only(top: 30),
                         child: Column(
                           children: [
-                            Text(
-                              "PM 1.0 Reading",
+                            const Text(
+                              "PM 1.0 數值",
                               textAlign: TextAlign.center,
                             ),
                             Text(
                               '$_pm01 μg/m^3',
                               textAlign: TextAlign.center,
-                              style: TextStyle(fontSize: 30),
+                              style: const TextStyle(fontSize: 30),
                             )
                           ],
                         )),
@@ -160,7 +220,17 @@ class _MyHomePageState extends State<MyHomePage> {
                         height: 120,
                         color: Colors.grey,
                         child: Text(
-                          "NO DATA",
+                          (() {
+                            if (_pm25 == 0) {
+                              return "無數據";
+                            } else if (_pm25 < 15) {
+                              return "良好";
+                            } else if (_pm25 < 35) {
+                              return "一般";
+                            }
+
+                            return "差";
+                          }()),
                           style: TextStyle(color: Colors.white),
                         )),
                   ),
@@ -169,11 +239,11 @@ class _MyHomePageState extends State<MyHomePage> {
                     child: Container(
                         color: Colors.white,
                         height: 120,
-                        padding: EdgeInsetsDirectional.only(top: 35),
+                        padding: EdgeInsetsDirectional.only(top: 30),
                         child: Column(
                           children: [
                             Text(
-                              "PM 2.5 Reading",
+                              "PM 2.5 數值",
                               textAlign: TextAlign.center,
                             ),
                             Text(
@@ -198,7 +268,17 @@ class _MyHomePageState extends State<MyHomePage> {
                         height: 120,
                         color: Colors.grey,
                         child: Text(
-                          "NO DATA",
+                          (() {
+                            if (_pm10 == 0) {
+                              return "無數據";
+                            } else if (_pm10 < 50) {
+                              return "良好";
+                            } else if (_pm10 < 150) {
+                              return "一般";
+                            }
+
+                            return "差";
+                          }()),
                           style: TextStyle(color: Colors.white),
                         )),
                   ),
@@ -207,11 +287,11 @@ class _MyHomePageState extends State<MyHomePage> {
                     child: Container(
                         color: Colors.white,
                         height: 120,
-                        padding: EdgeInsetsDirectional.only(top: 35),
+                        padding: EdgeInsetsDirectional.only(top: 30),
                         child: Column(
                           children: [
                             Text(
-                              "PM 10.0 Reading",
+                              "PM 10.0 數值",
                               textAlign: TextAlign.center,
                             ),
                             Text(
@@ -227,8 +307,8 @@ class _MyHomePageState extends State<MyHomePage> {
         ),
       )),
       floatingActionButton: FloatingActionButton(
-        onPressed: _incrementCounter,
-        tooltip: 'Connect To Bluetooth',
+        onPressed: _toggleBluetooth,
+        tooltip: '連接藍牙裝置',
         child: const Icon(Icons.bluetooth),
       ), // This trailing comma makes auto-formatting nicer for build methods.
     );

+ 111 - 0
src/pm25go/lib/scan.dart

@@ -0,0 +1,111 @@
+import 'dart:async';
+import 'dart:ffi';
+import 'dart:typed_data';
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';
+
+class ConnectionPage extends StatefulWidget {
+  const ConnectionPage({super.key});
+
+  @override
+  State<ConnectionPage> createState() => _ConnectionPage();
+}
+
+void printDiscovered() {}
+
+List<BluetoothDiscoveryResult> scanBluetoothDevice(
+    List<BluetoothDiscoveryResult> results) {
+  print("Scanning started!");
+  FlutterBluetoothSerial.instance.startDiscovery().listen((r) {
+    print(r.device.address);
+    final existingIndex = results
+        .indexWhere((element) => element.device.address == r.device.address);
+    if (existingIndex >= 0)
+      results[existingIndex] = r;
+    else
+      results.add(r);
+  });
+
+  return results;
+}
+
+class _ConnectionPage extends State<ConnectionPage> {
+  late BluetoothConnection connection;
+  bool _isConnected = false;
+  var _latestDiscoveredId = "Nothing Yet";
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text('Second Route'),
+        backgroundColor: Colors.blueGrey,
+      ),
+      body: Column(
+        children: [
+          Center(
+            child: ElevatedButton(
+              onPressed: () {
+                if (_isConnected && connection.isConnected) {
+                  connection.close();
+                  print('Connection Closed');
+                  _isConnected = false;
+                }
+              },
+              child: const Text('Disconnect Bluetooth'),
+            ),
+          ),
+          Center(
+            child: ElevatedButton(
+              onPressed: () async {
+                try {
+                  connection =
+                      await BluetoothConnection.toAddress("98:D3:11:FC:39:EC");
+                  print('Connected to the device');
+                  _isConnected = true;
+                  connection.input!.listen((Uint8List data) {
+                    print(data);
+                  });
+                } catch (ex) {
+                  print(ex);
+                }
+              },
+              child: const Text('Connect Bluetooth'),
+            ),
+          ),
+          Center(
+            child: ElevatedButton(
+              onPressed: () {
+                FlutterBluetoothSerial.instance
+                    .getBondedDevices()
+                    .then((List<BluetoothDevice> bondedDevices) {
+                  _latestDiscoveredId = "";
+                  for (var device in bondedDevices) {
+                    print(device.address + " / " + device.name!);
+                    setState(() {
+                      _latestDiscoveredId +=
+                          "\n" + device.address + " / " + device.name!;
+                    });
+                  }
+                });
+              },
+              child: const Text('Select Bluetooth'),
+            ),
+          ),
+          Text(
+              //Show the latest found Bluetooth addr
+              _latestDiscoveredId),
+          Center(
+            child: ElevatedButton(
+              onPressed: () {
+                Navigator.pop(context);
+              },
+              child: const Text('Go back!'),
+            ),
+          )
+        ],
+      ),
+    );
+  }
+}

+ 8 - 0
src/pm25go/pubspec.lock

@@ -55,6 +55,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_bluetooth_serial:
+    dependency: "direct main"
+    description:
+      name: flutter_bluetooth_serial
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.0"
   flutter_lints:
     dependency: "direct dev"
     description:
@@ -158,3 +165,4 @@ packages:
     version: "2.1.2"
 sdks:
   dart: ">=2.18.1 <3.0.0"
+  flutter: ">=1.17.0"

+ 4 - 5
src/pm25go/pubspec.yaml

@@ -1,5 +1,5 @@
 name: pm25go
-description: A new Flutter project.
+description: Pocket PM2.5 Monitor
 
 # The following line prevents the package from being accidentally published to
 # pub.dev using `flutter pub publish`. This is preferred for private packages.
@@ -32,10 +32,10 @@ dependencies:
   flutter:
     sdk: flutter
 
-
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^1.0.2
+  flutter_bluetooth_serial: ^0.4.0
 
 dev_dependencies:
   flutter_test:
@@ -60,9 +60,8 @@ flutter:
   uses-material-design: true
 
   # To add assets to your application, add an assets section, like this:
-  # assets:
-  #   - images/a_dot_burr.jpeg
-  #   - images/a_dot_ham.jpeg
+  assets:
+    - assets/images/banner.png
 
   # An image asset can refer to one or more resolution-specific "variants", see
   # https://flutter.dev/assets-and-images/#resolution-aware