Flutter串口通信插件flutter_serial的使用

发布于 1周前 作者 phonegap100 来自 Flutter

Flutter Serial!

An Android Plugin for Serial Communication which allows you to read and write data through available ports.

Description

This plugin enables communication with:

  • USB
  • RS232
  • RS485
  • UART

Supported Platforms:

  • Android: arm65-v8a, armeabi, armeabi-v7a, mips, mips64, x86, x86_64

Connection Setup

  • User Interface connected to an RS232 converter sending data to a machine.
  • USB-A to micro USB cable used to connect a commercial Android intelligent display terminal running Android 11 for debugging purposes.

Tablet Tablet Connection

Features

  • No root required on Android
  • Easy to use
  • Using listeners for reading messages

Installation

Add a dependency to your pubspec.yaml:

dependencies:
  flutter_serial: latest_version

Include the flutter_serial package at the top of your Dart file:

import 'package:flutter_serial/flutter_serial.dart';

Usage

If you encounter any issues, please refer to the API docs and the sample code in the example directory. The example directory has a sample application that demonstrates the features of this plugin.

Android Setup

Initialization

The first step is to call the startSerial() method and subscribe to the StreamSubscription.

Start Serial

The startSerial method will open the transaction stream.

FlutterSerial flutterSerial = FlutterSerial();

@override
void initState() {
  super.initState();
  flutterSerial.startSerial().listen(_updateConnectionStatus);
  getSerialList();
}

void _updateConnectionStatus(SerialResponse? result) async {
  logData = result!.logChannel ?? "";
  receivedData = result.readChannel ?? "";
}

Serial Response

In SerialResponse, you will get the following types:

  1. Log Channel (type: String): Response when you open/close any port or transmit data (TX).
  2. Read Channel (type: String): Received data (RX).

Available Ports

The getAvailablePorts() method will return all available ports on the device.

serialList = await flutterSerial.getAvailablePorts();

Open

The openPort method will open the serial communication. It has three required parameters:

  • DataFormat dataFormat
  • String serialPort
  • int baudRate
flutterSerial.openPort(
  dataFormat: DataFormat.ASCII,
  serialPort: serialList.first,
  baudRate: flutterSerial.baudRateList.first
);

Close

The closePort method will close the port if you have opened any port.

flutterSerial.closePort();

Send Command

The sendCommand method will send your message. It has one required parameter:

  • String message
flutterSerial.sendCommand(message: "message");

Clear

  • clearLog method will clear the Log channel.
  • clearRead method will clear the Read channel.
flutterSerial.clearLog();
flutterSerial.clearRead();

Destroy

The destroy method will eliminate the resources.

@override
void dispose() {
  flutterSerial.destroy();
  super.dispose();
}

Baud Rate

To get the standard baud rates list, call FlutterSerial().baudRateList. It will return an integer list of standard baud rates.

Data Format

The data format is used to convert the data type. To pass the data format in the open() method parameter:

  • For ASCII format: DataFormat.ASCII
  • For HEX String format: DataFormat.HEX_STRING

Example Code

Here is a complete example demonstrating the usage of the flutter_serial plugin:

import 'dart:ui';

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter_serial/flutter_serial.dart';

void main() {
  DartPluginRegistrant.ensureInitialized();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Serial Communication',
      home: HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String logData = "";
  String receivedData = "";
  String selectedPort = "Select Port";
  int selectedBaudRate = FlutterSerial().baudRateList.first;
  List<String>? serialList = [];
  DataFormat format = DataFormat.ASCII;
  String message = "";
  FlutterSerial flutterSerial = FlutterSerial();
  int selectedNavigationIndex = 0;

  @override
  void initState() {
    super.initState();
    flutterSerial.startSerial().listen(_updateConnectionStatus);
    getSerialList();
  }

  getSerialList() async {
    serialList = await flutterSerial.getAvailablePorts();
  }

  @override
  void dispose() {
    flutterSerial.destroy();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        resizeToAvoidBottomInset: true,
        appBar: AppBar(
          title: const Text('Flutter Serial Communication Example'),
        ),
        body: Column(
          children: [
            const SizedBox(height: 10),
            Expanded(
              child: Row(
                children: [
                  NavigationRail(
                    destinations: [
                      buildNavigationRailDestination(
                        icon: Icons.settings,
                        index: 0,
                        labelText: "Setting",
                      ),
                      buildNavigationRailDestination(
                        icon: Icons.print,
                        index: 1,
                        labelText: "Print",
                      ),
                    ],
                    onDestinationSelected: (value) {
                      setState(() {
                        selectedNavigationIndex = value;
                      });
                    },
                    selectedIndex: selectedNavigationIndex,
                  ),
                  getChild()
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  NavigationRailDestination buildNavigationRailDestination({
    required IconData icon,
    required String labelText,
    required int index,
  }) =>
      NavigationRailDestination(
        icon: Icon(
          icon,
          size: 40,
          color: index == selectedNavigationIndex ? Colors.blue : Colors.black,
        ),
        label: Text(labelText),
      );

  Widget _buildBody(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return SizedBox(
      height: size.height,
      width: size.width - 80,
      child: Padding(
        padding: const EdgeInsets.all(10.0),
        child: SingleChildScrollView(
          child: Column(
            children: [
              const Divider(),
              sendCommand(),
              const Divider(),
              response()
            ],
          ),
        ),
      ),
    );
  }

  Widget getChild() {
    switch (selectedNavigationIndex) {
      case 0:
        return setupButton(context);

      case 1:
        return _buildBody(context);
      default:
        return setupButton(context);
    }
  }

  Widget setupButton(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return SizedBox(
      width: size.width - 80,
      height: size.height,
      child: Column(
        children: [
          const Divider(),
          operations(),
          StatefulBuilder(
            builder: (context, state) {
              return Container(
                height: 400,
                padding: const EdgeInsets.only(left: 20, right: 20),
                decoration: const BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(26),
                    topRight: Radius.circular(26),
                  ),
                ),
                child: ListView(
                  physics: const ScrollPhysics(),
                  shrinkWrap: true,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      verticalDirection: VerticalDirection.down,
                      children: <Widget>[
                        Padding(
                          padding: const EdgeInsets.all(12.0),
                          child: Center(
                            child: Container(
                              height: 5,
                              width: 100,
                              decoration: BoxDecoration(
                                color: Colors.grey,
                                borderRadius: BorderRadius.circular(90),
                              ),
                            ),
                          ),
                        ),
                        const Divider(),
                        listTile(
                          widget: DropdownButtonHideUnderline(
                            child: DropdownButton<String>(
                              isExpanded: true,
                              borderRadius: BorderRadius.circular(10),
                              hint: Text(
                                selectedPort,
                                style: mediumStyle.apply(fontSizeFactor: 0.9),
                              ),
                              items: serialList!.map((String? value) {
                                return DropdownMenuItem<String>(
                                  value: value,
                                  child: Text(value!, style: mediumStyle),
                                );
                              }).toList(),
                              onChanged: (p0) {
                                updateSelectPort(state, p0!);
                              },
                            ),
                          ),
                          title: "Serial Port:",
                        ),
                        listTile(
                          widget: DropdownButtonHideUnderline(
                            child: DropdownButton<int>(
                              isExpanded: true,
                              borderRadius: BorderRadius.circular(10),
                              hint: Text(
                                selectedBaudRate.toString(),
                                style: mediumStyle.apply(fontSizeFactor: 0.9),
                              ),
                              menuMaxHeight: 400.0,
                              items: flutterSerial.baudRateList.map((int? value) {
                                return DropdownMenuItem<int>(
                                  value: value,
                                  child: Text(value.toString(), style: mediumStyle),
                                );
                              }).toList(),
                              onChanged: (p0) {
                                updateSelectBaudRate(state, p0!);
                              },
                            ),
                          ),
                          title: 'Select the BaudRate:',
                        ),
                        listTile(
                          widget: Row(
                            children: [
                              Expanded(
                                child: CheckboxListTile(
                                  title: const Text("ASCII"),
                                  value: format == DataFormat.ASCII ? true : false,
                                  onChanged: (newValue) {
                                    updateDataFormat(state, DataFormat.ASCII);
                                  },
                                  controlAffinity: ListTileControlAffinity.leading,
                                ),
                              ),
                              Expanded(
                                child: CheckboxListTile(
                                  title: const Text("HEX String"),
                                  value: format == DataFormat.HEX_STRING ? true : false,
                                  onChanged: (newValue) {
                                    updateDataFormat(state, DataFormat.HEX_STRING);
                                  },
                                  controlAffinity: ListTileControlAffinity.leading,
                                ),
                              ),
                            ],
                          ),
                          title: "Data Format",
                        ),
                      ],
                    ),
                  ],
                ),
              );
            },
          ),
        ],
      ),
    );
  }

  Widget response() {
    return Column(
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Column(
              children: [
                const Text("Received Data", style: mediumStyle),
                const SizedBox(height: 10),
                button(name: "Empty", onPress: () => flutterSerial.clearRead()),
              ],
            ),
            Column(
              children: [
                const Text("Operation Log", style: mediumStyle),
                const SizedBox(height: 10),
                button(name: "Empty", onPress: () => flutterSerial.clearLog()),
              ],
            ),
          ],
        ),
        const SizedBox(height: 10),
        SizedBox(
          height: MediaQuery.of(context).size.height * 0.4,
          child: Row(
            children: [
              log(data: receivedData),
              const VerticalDivider(thickness: 2),
              log(data: logData),
            ],
          ),
        ),
      ],
    );
  }

  Widget log({required String data}) {
    return Expanded(
      child: Scrollbar(
        child: SingleChildScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          scrollDirection: Axis.vertical,
          child: Text(
            data,
            style: const TextStyle(fontSize: 16.0, color: Colors.black),
          ),
        ),
      ),
    );
  }

  Widget sendCommand() {
    return Material(
      elevation: 4,
      color: const Color(0xFFF2F2F7),
      borderRadius: BorderRadius.circular(12),
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 10),
        child: Row(
          children: [
            Expanded(
              child: TextField(
                decoration: const InputDecoration(hintText: "Write send Command"),
                onChanged: (value) => message = value,
              ),
            ),
            button(name: "Send", onPress: () => flutterSerial.sendCommand(message: message))
          ],
        ),
      ),
    );
  }

  Widget operations() {
    return Material(
      elevation: 4,
      color: const Color(0xFFF2F2F7),
      borderRadius: BorderRadius.circular(12),
      child: Padding(
        padding: const EdgeInsets.all(9.0),
        child: Column(
          children: [
            const SizedBox(height: 10),
            Text("OPERATION", style: mediumStyle.apply(color: Colors.black54)),
            const SizedBox(height: 10),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                button(name: "Open", onPress: () => flutterSerial.openPort(
                  dataFormat: format,
                  serialPort: selectedPort,
                  baudRate: selectedBaudRate
                )),
                button(name: "Close", onPress: () => flutterSerial.closePort())
              ],
            ),
          ],
        ),
      ),
    );
  }

  void _updateConnectionStatus(SerialResponse? result) async {
    setState(() {
      logData = result!.logChannel ?? "";
      receivedData = result.readChannel ?? "";
    });
  }

  Widget button({required String name, required Function() onPress}) {
    return SizedBox(
      height: 40,
      width: 130,
      child: ElevatedButton(
        onPressed: onPress,
        child: Text(name, style: mediumStyle),
      ),
    );
  }

  Future<void> updateSelectPort(StateSetter updateState, String value) async {
    updateState(() {
      setState(() {
        selectedPort = value;
      });
    });
  }

  Future<void> updateDataFormat(StateSetter updateState, DataFormat value) async {
    updateState(() {
      setState(() {
        format = value;
      });
    });
  }

  Future<void> updateSelectBaudRate(StateSetter updateState, int value) async {
    updateState(() {
      setState(() {
        selectedBaudRate = value;
      });
    });
  }

  Widget listTile({required Widget widget, required String title}) {
    return Padding(
      padding: const EdgeInsets.all(12.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(title, textAlign: TextAlign.left, style: mediumStyle),
          const SizedBox(height: 10),
          Container(
            decoration: BoxDecoration(
              color: Colors.white,
              border: Border.all(color: Colors.black38),
              borderRadius: BorderRadius.circular(10.0),
            ),
            padding: const EdgeInsets.only(left: 8.0),
            child: widget,
          ),
        ],
      ),
    );
  }
}

const mediumStyle = TextStyle(fontSize: 20, fontWeight: FontWeight.bold);

This example demonstrates how to set up and use the flutter_serial plugin to communicate over a serial port in a Flutter application.


更多关于Flutter串口通信插件flutter_serial的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter串口通信插件flutter_serial的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_serial插件进行串口通信的示例代码。请注意,为了成功运行以下代码,你需要确保已经正确安装并配置了flutter_serial插件以及相关的依赖项。

1. 添加依赖项

首先,在你的pubspec.yaml文件中添加flutter_serial插件的依赖项:

dependencies:
  flutter:
    sdk: flutter
  flutter_serial: ^x.y.z  # 请替换为最新版本号

然后运行flutter pub get来安装依赖项。

2. 配置权限

由于串口通信需要访问设备硬件,你需要在AndroidManifest.xml(对于Android)和Info.plist(对于iOS)中添加必要的权限。

Android

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

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.usb.host" />

iOS

ios/Runner/Info.plist中添加:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app needs access to Bluetooth</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app needs access to Bluetooth</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
    <string>external-accessory</string>
</array>

3. 初始化插件并打开串口

在你的Flutter项目中,你可以这样初始化flutter_serial插件并尝试打开串口:

import 'package:flutter/material.dart';
import 'package:flutter_serial/flutter_serial.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  SerialPort? _serialPort;
  List<int> _dataBuffer = [];
  String _output = "";

  @override
  void initState() {
    super.initState();
    initSerial();
  }

  Future<void> initSerial() async {
    try {
      // 初始化插件
      await SerialPort.init();

      // 列出所有可用的串口
      List<SerialPortInfo> ports = await SerialPort.listPorts();
      print("Available ports: ${ports.map((p) => p.portName).join(", ")}");

      // 打开第一个串口(这里假设第一个是可用的串口,实际应用中你可能需要让用户选择)
      if (ports.isNotEmpty) {
        _serialPort = await SerialPort.open(ports[0].portName, 9600);
        
        // 设置数据接收监听器
        _serialPort!.inputStream.listen(
          (Uint8List data) {
            setState(() {
              _dataBuffer.addAll(data);
              _output = String.fromCharCodes(_dataBuffer);
              _dataBuffer.clear();
            });
          },
          onError: (error) {
            print("Error reading: $error");
          },
          onDone: () {
            print("Done");
          },
          cancelOnError: true
        );

        print("Port opened successfully.");
      } else {
        print("No ports available.");
      }
    } catch (e) {
      print("Error initializing serial: $e");
    }
  }

  Future<void> sendData(String data) async {
    if (_serialPort != null && _serialPort!.isOpen) {
      List<int> dataToSend = data.codeUnits;
      _serialPort!.write(dataToSend);
      print("Data sent: $data");
    } else {
      print("Port is not open.");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Serial Communication'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              Text("Received Data:\n$_output"),
              SizedBox(height: 20),
              TextField(
                decoration: InputDecoration(labelText: 'Send Data'),
                onSubmitted: (value) {
                  sendData(value);
                },
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _serialPort?.close();
    super.dispose();
  }
}

注意事项

  1. 权限处理:在实际应用中,你需要处理用户权限请求,特别是Android 6.0及以上版本需要动态请求权限。
  2. 错误处理:上面的代码示例中包含了基本的错误处理,但在实际应用中你可能需要更详细的错误处理和恢复逻辑。
  3. 平台差异:不同平台的串口通信行为可能有所不同,特别是iOS和Android之间。因此,在实际部署之前,请确保在目标平台上进行充分的测试。

希望这个示例能帮助你成功地在Flutter项目中使用flutter_serial插件进行串口通信。

回到顶部