16_flutter_BLE,串口

1,159 阅读2分钟

1_BLE

1.1_BLE手柄

安装库

flutter pub add flutter_joystick
flutter pub add flutter_reactive_ble

添加安卓BLE权限

android/app/src/main/AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" android:maxSdkVersion="30" />
 <application

修改最小Android SDK版本

android/app/build.gradle中进行更改:

Android {
  defaultConfig {
     minSdkVersion: 21

flutter代码

import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:convert' as convert;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_joystick/flutter_joystick.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations(
          [DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft])
      .then((_) {
    runApp(const MyApp());
  });
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData.dark(),
      home: const JoyPad(),
    );
  }
}

class JoyPad extends StatefulWidget {
  const JoyPad({Key? key}) : super(key: key);

  @override
  JoyPadState createState() => JoyPadState();
}

class JoyPadState extends State<JoyPad> {
  var serviceId = Uuid.parse(
      '4fafc201-1fb5-459e-8fcc-c5c9c331914b'); // REPLACE WITH YOUR SERVICE_ID
  var characteristicId = Uuid.parse(
      'beb5483e-36e1-4688-b7f5-ea07361b26a8'); // REPLACE WITH YOUR CHARACTERISTIC_ID

  StreamSubscription? subscription;
  StreamSubscription<ConnectionStateUpdate>? connection;
  QualifiedCharacteristic? characteristic;
  final bleManager = FlutterReactiveBle();

  String connectionText = "";
  late List<String> buttonCharacter = ['A', 'B', 'X', 'Y'];
  late Map<String, String> user = {
    "u": "0",
    "d": "0",
    "l": "0",
    "r": "0",
    "s": "0",
    "t": "0",
    "a": "0",
    "b": "0",
    "x": "0",
    "y": "0",
  };

  @override
  void initState() {
    super.initState();
    bleManager.statusStream.listen((status) {
      log("STATUS: $status");
      if (status == BleStatus.ready) initBle();
    });
  }

  @override
  void dispose() {
    subscription?.cancel();
    connection?.cancel();
    super.dispose();
  }

//停止扫描
  Future<void> stopScan() async {
    log('HF: stopping BLE scan');
    await subscription?.cancel();
    subscription = null;
  }

//初始化状态
  void initBle() {
    subscription?.cancel();

//扫描设备
    subscription = bleManager.scanForDevices(
        withServices: [serviceId],
        scanMode: ScanMode.lowLatency).listen((device) {
      log("SCAN FOUND: ${device.name}");
      stopScan();

//建立连接
      connection = bleManager
          .connectToDevice(
        id: device.id,
        servicesWithCharacteristicsToDiscover: {
          serviceId: [characteristicId]
        },
        connectionTimeout: const Duration(seconds: 2),
      )
          .listen((connectionState) {
        log("CONNECTING: $connectionState");
        if (connectionState.connectionState ==
            DeviceConnectionState.connected) {
          setState(() {
            bleManager.requestConnectionPriority(
                deviceId: serviceId.toString(),
                priority: ConnectionPriority.highPerformance); //设置优先级
            bleManager.deinitialize();
            characteristic = QualifiedCharacteristic(
                serviceId: serviceId,
                characteristicId: characteristicId,
                deviceId: device.id);
            connectionText = "一切就绪${device.name}";
          });
        } else {
          log("NOT CONNECTED");
          initBle(); // 尝试连接到设备,直到它在范围内。
        }
      }, onError: (Object error) {
        log("error on connect: $error");
      });
    }, onError: (obj, stack) {
      log('AN ERROR WHILE SCANNING:\r$obj\r$stack');
    });
  }

//写入数据
  writeData(String data) async {
    List<int> bytes = utf8.encode(data);
    await bleManager.writeCharacteristicWithResponse(characteristic!,
        value: bytes);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: const Text("ESP32S3 BLE NES手柄"),
        ),
        body: Center(
            child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              child: characteristic == null
                  ? const Center(
                      child: Text(
                        "Waiting...",
                        style: TextStyle(fontSize: 24, color: Colors.red),
                      ),
                    )
                  : Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: <Widget>[
                        // alignment: const Alignment(0, 0.1),
                        Joystick(
                            mode: JoystickMode.horizontalAndVertical,
                            listener: (details) {
                              if (details.x > 0) {
                                user["u"] = "1";
                                user["d"] = "0";
                              } else if (details.x < 0) {
                                user["u"] = "0";
                                user["d"] = "1";
                              } else {
                                user["u"] = "0";
                                user["d"] = "0";
                              }

                              if (details.y > 0) {
                                user["l"] = "1";
                                user["r"] = "0";
                              } else if (details.y < 0) {
                                user["l"] = "0";
                                user["r"] = "1";
                              } else {
                                user["l"] = "0";
                                user["r"] = "0";
                              }

                              String data = convert.jsonEncode(user);
                              writeData(data);
                            }),
                        Align(
                            alignment: Alignment.bottomCenter,
                            child: InkWell(
                              child: ElevatedButton(
                                onPressed: null,
                                style: ElevatedButton.styleFrom(
                                  primary: const Color(0x00303134),
                                  shape: const CircleBorder(),
                                  padding: const EdgeInsets.all(20),
                                ),
                                child: const Text('开始'),
                              ),
                              onTapDown: (context) {
                                user["t"] = "1";
                                String data = convert.jsonEncode(user);
                                writeData(data);
                              },
                              onTapUp: (context) {
                                user["t"] = "0";
                                String data = convert.jsonEncode(user);
                                writeData(data);
                              },
                            )),
                        Align(
                            alignment: Alignment.bottomCenter,
                            child: InkWell(
                              child: ElevatedButton(
                                onPressed: null,
                                style: ElevatedButton.styleFrom(
                                  primary: const Color(0x00303134),
                                  shape: const CircleBorder(),
                                  padding: const EdgeInsets.all(20),
                                ),
                                child: const Text('选择'),
                              ),
                              onTapDown: (context) {
                                user["s"] = "1";
                                String data = convert.jsonEncode(user);
                                writeData(data);
                              },
                              onTapUp: (context) {
                                user["s"] = "0";
                                String data = convert.jsonEncode(user);
                                writeData(data);
                              },
                            )),
                        Container(
                          height: 200,
                          width: 200,
                          decoration: BoxDecoration(
                              color: const Color(0x50616161),
                              borderRadius: BorderRadius.circular(100)
                              //more than 50% of width makes circle
                              ),
                          child: Stack(
                            children: [
                              Align(
                                  alignment: Alignment.topCenter,
                                  child: InkWell(
                                    child: ElevatedButton(
                                      onPressed: null,
                                      style: ElevatedButton.styleFrom(
                                        primary: const Color(0x00303134),
                                        shape: const CircleBorder(),
                                        padding: const EdgeInsets.all(20),
                                      ),
                                      child: Text(buttonCharacter[0]),
                                    ),
                                    onTapDown: (context) {
                                      user["a"] = "1";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                    onTapUp: (context) {
                                      user["a"] = "0";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                  )),
                              Align(
                                  alignment: Alignment.bottomCenter,
                                  child: InkWell(
                                    child: ElevatedButton(
                                      onPressed: null,
                                      style: ElevatedButton.styleFrom(
                                        primary: const Color(0x00303134),
                                        shape: const CircleBorder(),
                                        padding: const EdgeInsets.all(20),
                                      ),
                                      child: Text(buttonCharacter[1]),
                                    ),
                                    onTapDown: (context) {
                                      user["b"] = "1";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                    onTapUp: (context) {
                                      user["b"] = "0";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                  )),
                              Align(
                                  alignment: Alignment.centerLeft,
                                  child: InkWell(
                                    child: ElevatedButton(
                                      onPressed: null,
                                      style: ElevatedButton.styleFrom(
                                        primary: const Color(0x00303134),
                                        shape: const CircleBorder(),
                                        padding: const EdgeInsets.all(20),
                                      ),
                                      child: Text(buttonCharacter[2]),
                                    ),
                                    onTapDown: (context) {
                                      user["x"] = "1";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                    onTapUp: (context) {
                                      user["x"] = "0";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                  )),
                              Align(
                                  alignment: Alignment.centerRight,
                                  child: InkWell(
                                    child: ElevatedButton(
                                      onPressed: null,
                                      style: ElevatedButton.styleFrom(
                                        primary: const Color(0x00303134),
                                        shape: const CircleBorder(),
                                        padding: const EdgeInsets.all(20),
                                      ),
                                      child: Text(buttonCharacter[3]),
                                    ),
                                    onTapDown: (context) {
                                      user["y"] = "1";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                    onTapUp: (context) {
                                      user["y"] = "0";
                                      String data = convert.jsonEncode(user);
                                      writeData(data);
                                    },
                                  )),
                            ],
                          ),
                        ),
                      ],
                    ),
            )
          ],
        )));
  }
}

编译并真机运行

flutter run

1.2_触摸坐标

安装库

flutter pub add positioned_tap_detector_2
flutter pub add flutter_reactive_ble

flutter代码

import 'dart:async';
import 'dart:convert' show utf8;
import 'dart:convert' as convert;
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:positioned_tap_detector_2/positioned_tap_detector_2.dart';
import 'package:flutter_reactive_ble/flutter_reactive_ble.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations(
          [DeviceOrientation.landscapeRight, DeviceOrientation.landscapeLeft])
      .then((_) {
    runApp(const MyApp());
  });
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'touch coordinates with BLE',
      debugShowCheckedModeBanner: false,
      home: const TouchCoordinates(),
      theme: ThemeData.dark(),
    );
  }
}

class TouchCoordinates extends StatefulWidget {
  const TouchCoordinates({Key? key}) : super(key: key);

  @override
  TouchCoordinatesState createState() => TouchCoordinatesState();
}

class TouchCoordinatesState extends State<TouchCoordinates> {
  var serviceId = Uuid.parse(
      '4fafc201-1fb5-459e-8fcc-c5c9c331914b'); // REPLACE WITH YOUR SERVICE_ID
  var characteristicId = Uuid.parse(
      'beb5483e-36e1-4688-b7f5-ea07361b26a8'); // REPLACE WITH YOUR CHARACTERISTIC_ID

  StreamSubscription? subscription;
  StreamSubscription<ConnectionStateUpdate>? connection;
  QualifiedCharacteristic? characteristic;
  final bleManager = FlutterReactiveBle();

  TapPosition _position = TapPosition(Offset.zero, Offset.zero);
  double screenWidth = 480;
  double screenHeight = 240;

  String connectionText = "";
  late Map<String, String> user = {
    "x": "0",
    "y": "0",
  };

  @override
  void initState() {
    super.initState();
    bleManager.statusStream.listen((status) {
      log("STATUS: $status");
      if (status == BleStatus.ready) initBle();
    });
  }

  @override
  void dispose() {
    subscription?.cancel();
    connection?.cancel();
    super.dispose();
  }

//停止扫描
  Future<void> stopScan() async {
    log('HF: stopping BLE scan');
    await subscription?.cancel();
    subscription = null;
  }

//初始化状态
  void initBle() {
    subscription?.cancel();

//扫描设备
    subscription = bleManager.scanForDevices(
        withServices: [serviceId],
        scanMode: ScanMode.lowLatency).listen((device) {
      log("SCAN FOUND: ${device.name}");
      stopScan();

//建立连接
      connection = bleManager
          .connectToDevice(
        id: device.id,
        servicesWithCharacteristicsToDiscover: {
          serviceId: [characteristicId]
        },
        connectionTimeout: const Duration(seconds: 2),
      )
          .listen((connectionState) {
        log("CONNECTING: $connectionState");
        if (connectionState.connectionState ==
            DeviceConnectionState.connected) {
          setState(() {
            characteristic = QualifiedCharacteristic(
                serviceId: serviceId,
                characteristicId: characteristicId,
                deviceId: device.id);
            connectionText = "一切就绪${device.name}";
          });
        } else {
          log("NOT CONNECTED");
          initBle(); // 尝试连接到设备,直到它在范围内。
        }
      }, onError: (Object error) {
        log("error on connect: $error");
      });
    }, onError: (obj, stack) {
      log('AN ERROR WHILE SCANNING:\r$obj\r$stack');
    });
  }

//写入数据
  writeData(String data) async {
    List<int> bytes = utf8.encode(data);
    await bleManager.writeCharacteristicWithResponse(characteristic!,value: bytes);
  }

  void _onTap(TapPosition position) {
    _updateState(position);
  }

  void _updateState(TapPosition position) {
    setState(() {
      _position = position;
    });
  }

  String _formatOffset(Offset offset) {
    user["x"] = "${offset.dx.toInt()}";
    user["y"] = "${offset.dy.toInt()}";
    String data = convert.jsonEncode(user);
    writeData(data);
    return "x:${user["x"]}, y:${user["y"]}";
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(connectionText),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                child: characteristic == null
                    ? const Center(
                        child: Text(
                          "Waiting...",
                          style: TextStyle(fontSize: 24, color: Colors.red),
                        ),
                      )
                    : Row(
                        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                        children: <Widget>[
                            Column(
                              children: [
                                PositionedTapDetector2(
                                  onTap: _onTap,
                                  child: Container(
                                    width: screenWidth,
                                    height: screenHeight,
                                    color: const Color(0xffccccff),
                                  ),
                                ),
                                Text(
                                    '坐标: ${_formatOffset(_position.relative!)}'),
                              ],
                            ),
                          ]),
              ),
            ],
          ),
        ));
  }
}

2_串口

flutter pub add flutter_libserialport
import 'dart:convert' show Utf8Decoder;
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_libserialport/flutter_libserialport.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Linux Serial Port Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Linux Serial Port Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<SerialPort> portList = [];
  SerialPort? _serialPort;
  List<Uint8List> receiveDataList = [];
  final textInputCtrl = TextEditingController();

  @override
  void initState() {
    super.initState();
    var i = 0;
    for (final name in SerialPort.availablePorts) {
      final sp = SerialPort(name);
      if (kDebugMode) {
        print('${++i}) $name');
        print('\tDescription: ${sp.description}');
        print('\tManufacturer: ${sp.manufacturer}');
        print('\tSerial Number: ${sp.serialNumber}');
        print('\tProduct ID: 0x${sp.productId?.toRadixString(16) ?? 00}');
        print('\tVendor ID: 0x${sp.vendorId?.toRadixString(16) ?? 00}');
      }
      portList.add(sp);
    }
    if (portList.isNotEmpty) {
      _serialPort = portList.first;
    }
  }

  void changedDropDownItem(SerialPort sp) {
    setState(() {
      _serialPort = sp;
    });
  }

  @override
  Widget build(BuildContext context) {
    var openButtonText = _serialPort == null
        ? 'N/A'
        : _serialPort!.isOpen
            ? 'Close'
            : 'Open';
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: SizedBox(
        height: double.infinity,
        child: Column(
          children: <Widget>[
            Expanded(
              flex: 1,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  DropdownButton(
                    value: _serialPort,
                    items: portList.map((item) {
                      return DropdownMenuItem(
                          value: item, child: Text("${item.name}"));
                    }).toList(),
                    onChanged: (e) {
                      setState(() {
                        changedDropDownItem(e as SerialPort);
                      });
                    },
                  ),
                  const SizedBox(
                    width: 50.0,
                  ),
                  OutlinedButton(
                    child: Text(openButtonText),
                    onPressed: () {
                      if (_serialPort == null) {
                        return;
                      }
                      if (_serialPort!.isOpen) {
                        _serialPort!.close();
                        debugPrint('${_serialPort!.name} closed!');
                      } else {
                        if (_serialPort!.open(mode: SerialPortMode.readWrite)) {
                          SerialPortConfig config = _serialPort!.config;
                          // https://www.sigrok.org/api/libserialport/0.1.1/a00007.html#gab14927cf0efee73b59d04a572b688fa0
                          // https://www.sigrok.org/api/libserialport/0.1.1/a00004_source.html
                          config.baudRate = 115200;
                          config.parity = 0;
                          config.bits = 8;
                          config.cts = 0;
                          config.rts = 0;
                          config.stopBits = 1;
                          config.xonXoff = 0;
                          _serialPort!.config = config;
                          if (_serialPort!.isOpen) {
                            debugPrint('${_serialPort!.name} opened!');
                          }
                          final reader = SerialPortReader(_serialPort!);
                          reader.stream.listen((data) {
                            debugPrint('received: $data');
                            receiveDataList.add(data);
                            setState(() {});
                          }, onError: (error) {
                            if (error is SerialPortError) {
                              debugPrint(
                                  'error: ${error.message.toString()}, code: ${error.errorCode}');
                            }
                          });
                        }
                      }
                      setState(() {});
                    },
                  ),
                ],
              ),
            ),
            Expanded(
              flex: 8,
              child: Card(
                margin: const EdgeInsets.all(10.0),
                child: ListView.builder(
                    itemCount: receiveDataList.length,
                    itemBuilder: (context, index) {
                      /*
                      OUTPUT for raw bytes
                      return Text(receiveDataList[index].toString());
                      */
                      /* output for string */
                      return Text(
                          const Utf8Decoder(allowMalformed: true).convert(receiveDataList[index]));
                    }),
              ),
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                Flexible(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(vertical: 10.0),
                    child: TextField(
                      enabled: (_serialPort != null && _serialPort!.isOpen)
                          ? true
                          : false,
                      controller: textInputCtrl,
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                      ),
                    ),
                  ),
                ),
                Flexible(
                  child: TextButton.icon(
                    onPressed: (_serialPort != null && _serialPort!.isOpen)
                        ? () {
                            if (_serialPort!.write(Uint8List.fromList(
                                    textInputCtrl.text.codeUnits)) ==
                                textInputCtrl.text.codeUnits.length) {
                              setState(() {
                                textInputCtrl.text = '';
                              });
                            }
                          }
                        : null,
                    icon: const Icon(Icons.send),
                    label: const Text("Send"),
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}