Flutter USB串口通信插件usb_serial的使用

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

Flutter USB串口通信插件usb_serial的使用

Flutter 插件 usb_serial 为 Android 设备提供了与 USB 串行设备交互的能力。以下将详细介绍如何使用此插件,包括设置、基本用法和一个完整的示例应用程序。

获取开始

添加依赖

在项目的 pubspec.yaml 文件中添加 usb_serial 依赖:

dependencies:
  usb_serial: ^0.5.2

然后,在 Dart 文件顶部导入 usb_serial 包:

import 'package:usb_serial/usb_serial.dart';

配置 AndroidManifest.xml

为了使应用程序能够检测到特定的 USB 设备连接,需向 AndroidManifest.xml 中添加 intent-filter 和 meta-data:

<intent-filter>
    <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>

<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
    android:resource="@xml/device_filter" />

并将 device_filter.xml 文件放置于 res/xml 目录下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 例如:FTDI FT232R UART -->
    <usb-device vendor-id="1027" product-id="24577" />
    <!-- 其他设备... -->
</resources>

使用异步API

下面是一个简单的例子,展示了如何枚举已连接的 USB 设备,并尝试打开第一个找到的端口进行读写操作:

onPressed: () async {
  List<UsbDevice> devices = await UsbSerial.listDevices();
  print(devices);

  UsbPort? port;
  if (devices.isEmpty) return;

  port = await devices.first.create();

  bool openResult = await port.open();
  if (!openResult) {
    print("Failed to open");
    return;
  }

  await port.setDTR(true);
  await port.setRTS(true);

  port.setPortParameters(115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE);

  // 监听输入流并打印接收到的数据
  port.inputStream.listen((Uint8List event) {
    print(event);
    port.close();
  });

  // 发送一些数据
  await port.write(Uint8List.fromList([0x10, 0x00]));
}

使用事务API

事务 API 提供了更高级别的抽象来处理消息终止符或带有长度字节的消息头。下面是如何创建一个基于字符串终止符(如 \r\n)的事务的例子:

Transaction<String> transaction = Transaction.stringTerminated(port.inputStream, Uint8List.fromList([13, 10]));

// 监听所有传入的消息
transaction.stream.listen((String data) {
  print(data);
});

// 发送异步消息
port.write(Uint8List.fromList([65, 66, 13, 10]));

// 执行事务 - 写入并等待响应
var response = await transaction.transaction(port, Uint8List.fromList([65, 66, 13, 10]), Duration(seconds: 1));
print("The response was $response");

完整示例应用

这里提供了一个完整的 Flutter 应用程序代码,它实现了上述功能,并允许用户通过 UI 界面选择要连接的设备、发送文本以及查看接收到的数据。

import 'dart:async';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:usb_serial/transaction.dart';
import 'package:usb_serial/usb_serial.dart';

void main() => runApp(MyApp());

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

class _MyAppState extends State<MyApp> {
  UsbPort? _port;
  String _status = "Idle";
  List<Widget> _ports = [];
  List<Widget> _serialData = [];

  StreamSubscription<String>? _subscription;
  Transaction<String>? _transaction;
  UsbDevice? _device;

  TextEditingController _textController = TextEditingController();

  Future<bool> _connectTo(device) async {
    _serialData.clear();

    if (_subscription != null) {
      _subscription!.cancel();
      _subscription = null;
    }

    if (_transaction != null) {
      _transaction!.dispose();
      _transaction = null;
    }

    if (_port != null) {
      _port!.close();
      _port = null;
    }

    if (device == null) {
      _device = null;
      setState(() {
        _status = "Disconnected";
      });
      return true;
    }

    _port = await device.create();
    if (await (_port!.open()) != true) {
      setState(() {
        _status = "Failed to open port";
      });
      return false;
    }
    _device = device;

    await _port!.setDTR(true);
    await _port!.setRTS(true);
    await _port!.setPortParameters(115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE);

    _transaction = Transaction.stringTerminated(_port!.inputStream as Stream<Uint8List>, Uint8List.fromList([13, 10]));

    _subscription = _transaction!.stream.listen((String line) {
      setState(() {
        _serialData.add(Text(line));
        if (_serialData.length > 20) {
          _serialData.removeAt(0);
        }
      });
    });

    setState(() {
      _status = "Connected";
    });
    return true;
  }

  void _getPorts() async {
    _ports = [];
    List<UsbDevice> devices = await UsbSerial.listDevices();
    if (!devices.contains(_device)) {
      _connectTo(null);
    }
    print(devices);

    devices.forEach((device) {
      _ports.add(ListTile(
          leading: Icon(Icons.usb),
          title: Text(device.productName!),
          subtitle: Text(device.manufacturerName!),
          trailing: ElevatedButton(
            child: Text(_device == device ? "Disconnect" : "Connect"),
            onPressed: () {
              _connectTo(_device == device ? null : device).then((res) {
                _getPorts();
              });
            },
          )));
    });

    setState(() {
      print(_ports);
    });
  }

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

    UsbSerial.usbEventStream!.listen((UsbEvent event) {
      _getPorts();
    });

    _getPorts();
  }

  @override
  void dispose() {
    super.dispose();
    _connectTo(null);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      appBar: AppBar(
        title: const Text('USB Serial Plugin example app'),
      ),
      body: Center(
          child: Column(children: <Widget>[
        Text(_ports.isNotEmpty ? "Available Serial Ports" : "No serial devices available", style: Theme.of(context).textTheme.titleLarge),
        ..._ports,
        Text('Status: $_status\n'),
        Text('info: ${_port.toString()}\n'),
        ListTile(
          title: TextField(
            controller: _textController,
            decoration: InputDecoration(
              border: OutlineInputBorder(),
              labelText: 'Text To Send',
            ),
          ),
          trailing: ElevatedButton(
            child: Text("Send"),
            onPressed: _port == null
                ? null
                : () async {
                    if (_port == null) {
                      return;
                    }
                    String data = _textController.text + "\r\n";
                    await _port!.write(Uint8List.fromList(data.codeUnits));
                    _textController.text = "";
                  },
          ),
        ),
        Text("Result Data", style: Theme.of(context).textTheme.titleLarge),
        ..._serialData,
      ])),
    ));
  }
}

以上就是关于 usb_serial 插件的基本介绍和使用方法。希望这些信息对你有所帮助!如果你有任何问题或者需要进一步的帮助,请随时提问。


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

1 回复

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


当然,以下是如何在Flutter应用中使用usb_serial插件来实现USB串口通信的示例代码。usb_serial插件允许你通过USB接口与串行设备进行通信,这在需要硬件交互的应用中非常有用。

首先,确保你的Flutter项目已经配置好并添加了usb_serial依赖。在pubspec.yaml文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  usb_serial: ^0.3.0  # 请检查最新版本号

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

Android权限配置

AndroidManifest.xml文件中添加必要的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.yourapp">

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

    <application
        ...>
        ...
    </application>
</manifest>

iOS权限配置

Info.plist文件中添加USB设备的权限描述(如果你的应用需要支持iOS):

<key>UIRequiredDeviceCapabilities</key>
<array>
    <string>usb-external-accessory</string>
</array>
<key>NSUSBVendorIDs</key>
<array>
    <dict>
        <key>NSUSBVendorID</key>
        <integer>0xXXXX</integer> <!-- 替换为你的设备的Vendor ID -->
    </dict>
</array>

Flutter代码示例

以下是一个简单的Flutter应用示例,它展示了如何使用usb_serial插件来列出连接的USB设备并与其中一个设备进行通信。

import 'package:flutter/material.dart';
import 'package:usb_serial/usb_serial.dart';
import 'dart:typed_data';
import 'dart:convert';

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

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

class _MyAppState extends State<MyApp> {
  UsbDevice? _selectedDevice;
  UsbPort? _selectedPort;
  UsbSerial? _serial;

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

  Future<void> _listUsbDevices() async {
    List<UsbDevice> devices = await UsbSerial.listDevices();
    print("Available devices: ${devices.map((d) => d.productId).toList()}");
    // 在这里你可以更新UI来显示设备列表
  }

  Future<void> _openPort(UsbDevice device, UsbPort port) async {
    setState(() {
      _selectedDevice = device;
      _selectedPort = port;
    });

    _serial = await UsbSerial.fromDevice(device);
    await _serial!.openPort(port);
    _serial!.setPortParameters(9600, UsbSerial.DATABITS_8, UsbSerial.STOPBITS_1, UsbSerial.PARITY_NONE);
    _serial!.setEncoding(Utf8Codec());

    _serial!.inputStream!.listen((Uint8List data) {
      String message = String.fromCharCodes(data);
      print("Received: $message");
      // 更新UI显示接收到的数据
    });
  }

  Future<void> _sendData(String data) async {
    if (_serial != null && _serial!.isPortOpen) {
      _serial!.write(data);
      print("Sent: $data");
    } else {
      print("Port is not open!");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('USB Serial Communication'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              // 假设你已经有了显示设备列表和选择端口的UI逻辑
              // 这里只是示例,所以直接省略
              ElevatedButton(
                onPressed: () async {
                  // 假设你已经选择了设备和端口
                  UsbDevice? device = _selectedDevice;
                  UsbPort? port = _selectedPort?.first; // 简单起见,选择第一个端口
                  if (device != null && port != null) {
                    await _openPort(device, port);
                  } else {
                    print("No device or port selected!");
                  }
                },
                child: Text('Open Port'),
              ),
              ElevatedButton(
                onPressed: () async {
                  await _sendData('Hello, Serial Port!');
                },
                child: Text('Send Data'),
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _serial?.closePort();
    super.dispose();
  }
}

注意

  1. 权限处理:在实际应用中,你需要处理权限请求,尤其是在Android上。
  2. UI更新:上面的示例代码省略了设备列表和端口选择的UI部分,你需要根据自己的需求来构建这部分UI。
  3. 错误处理:添加适当的错误处理逻辑,比如处理设备连接失败、端口打开失败等情况。
  4. 插件版本:确保你使用的是最新版本的usb_serial插件,并查阅其文档以获取最新的API信息和最佳实践。

这个示例提供了一个基本的框架,你可以根据自己的需求进行扩展和修改。

回到顶部