multi_serial_port 0.1.1 copy "multi_serial_port: ^0.1.1" to clipboard
multi_serial_port: ^0.1.1 copied to clipboard

PlatformAndroid

Android multi-port serial plugin based on Android-SerialPort-API. Bind devices by USB physical path; API aligned with serialport_plus.

example/lib/main.dart

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:multi_serial_port/multi_serial_port.dart';

import 'connection_panel.dart';
import 'console_panel.dart';
import 'serial_log.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'multi_serial_port',
      theme: ThemeData(
        colorSchemeSeed: Colors.indigo,
        useMaterial3: true,
      ),
      home: const TerminalPage(),
    );
  }
}

class TerminalPage extends StatefulWidget {
  const TerminalPage({super.key});

  @override
  State<TerminalPage> createState() => _TerminalPageState();
}

class _TerminalPageState extends State<TerminalPage> {
  static const _maxEntries = 500;

  final _port = FlutterSerialPort('example');
  final _input = TextEditingController();
  final _scroll = ScrollController();
  final _entries = <LogEntry>[];

  StreamSubscription<Uint8List>? _subscription;

  List<PortOption> _options = const [];
  String? _selectedPath;
  int _baudRate = 115200;
  int _dataBits = 8;
  int _parity = 0;
  int _stopBits = 1;

  bool _showHex = false;
  bool _sendHex = false;
  String _lineEnding = '\n';
  int _rxBytes = 0;
  int _txBytes = 0;

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

  @override
  void dispose() {
    _subscription?.cancel();
    _port.close();
    _input.dispose();
    _scroll.dispose();
    super.dispose();
  }

  Future<void> _refresh() async {
    final usbDevices = await _port.getAllPhysicalDevices();
    final devicePaths = await _port.getAllDevicesPath();
    if (!mounted) return;

    final options = PortOption.from(usbDevices, devicePaths);
    setState(() {
      _options = options;
      if (!options.any((o) => o.path == _selectedPath)) {
        _selectedPath = options.isEmpty ? null : options.first.path;
      }
    });
    _log(LogEntry.info('检测到 ${options.length} 个端口'));
  }

  Future<void> _toggle() async {
    if (_port.isOpen) {
      await _subscription?.cancel();
      _subscription = null;
      await _port.close();
      _log(LogEntry.info('串口已关闭'));
      setState(() {});
      return;
    }

    final path = _selectedPath;
    if (path == null) return;

    // 先订阅再打开,避免刚打开就到来的数据被漏掉。
    _subscription = _port.read().listen((data) => _log(LogEntry.rx(data)));
    final ok = await _port.open(
      path,
      _baudRate,
      _dataBits,
      _parity,
      _stopBits,
    );
    if (!ok) {
      await _subscription?.cancel();
      _subscription = null;
      _log(LogEntry.info('打开失败:$path'));
    } else {
      _log(LogEntry.info('已打开 ${_port.devicePath} @$_baudRate'));
    }
    setState(() {});
  }

  Future<void> _send() async {
    final text = _input.text;
    if (text.isEmpty) return;

    final Uint8List data;
    if (_sendHex) {
      final parsed = parseHex(text);
      if (parsed == null) {
        _log(LogEntry.info('十六进制格式有误,需成对的 0-9A-F'));
        return;
      }
      data = parsed;
    } else {
      data = Uint8List.fromList(utf8.encode(text + _lineEnding));
    }

    if (await _port.write(data)) {
      _log(LogEntry.tx(data));
    } else {
      _log(LogEntry.info('发送失败'));
    }
  }

  void _log(LogEntry entry) {
    setState(() {
      final length = entry.bytes?.length ?? 0;
      if (entry.kind == LogKind.rx) _rxBytes += length;
      if (entry.kind == LogKind.tx) _txBytes += length;

      _entries.add(entry);
      if (_entries.length > _maxEntries) {
        _entries.removeRange(0, _entries.length - _maxEntries);
      }
    });
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_scroll.hasClients) {
        _scroll.jumpTo(_scroll.position.maxScrollExtent);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('串口调试'),
        actions: [
          Padding(
            padding: const EdgeInsets.only(right: 16),
            child: Center(
              child: Text(_port.isOpen ? '已连接' : '未连接'),
            ),
          ),
        ],
      ),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(12),
          child: Column(
            children: [
              ConnectionPanel(
                options: _options,
                selectedPath: _selectedPath,
                baudRate: _baudRate,
                dataBits: _dataBits,
                parity: _parity,
                stopBits: _stopBits,
                isOpen: _port.isOpen,
                onPathChanged: (value) => setState(() => _selectedPath = value),
                onBaudRateChanged: (value) => setState(() => _baudRate = value),
                onDataBitsChanged: (value) => setState(() => _dataBits = value),
                onParityChanged: (value) => setState(() => _parity = value),
                onStopBitsChanged: (value) => setState(() => _stopBits = value),
                onRefresh: _refresh,
                onToggle: _toggle,
              ),
              const SizedBox(height: 12),
              Expanded(
                child: ConsolePanel(
                  entries: _entries,
                  controller: _scroll,
                  showHex: _showHex,
                  rxBytes: _rxBytes,
                  txBytes: _txBytes,
                  onShowHexChanged: (value) => setState(() => _showHex = value),
                  onClear: () => setState(() {
                    _entries.clear();
                    _rxBytes = 0;
                    _txBytes = 0;
                  }),
                ),
              ),
              const SizedBox(height: 12),
              SendPanel(
                controller: _input,
                sendHex: _sendHex,
                lineEnding: _lineEnding,
                enabled: _port.isOpen,
                onSendHexChanged: (value) => setState(() => _sendHex = value),
                onLineEndingChanged: (value) =>
                    setState(() => _lineEnding = value),
                onSend: _send,
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
150
points
80
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Android multi-port serial plugin based on Android-SerialPort-API. Bind devices by USB physical path; API aligned with serialport_plus.

Repository (GitHub)
View/report issues

Topics

#serial #serialport #usb #android #plugin

License

MIT (license)

Dependencies

flutter

More

Packages that depend on multi_serial_port

Packages that implement multi_serial_port