skio_usb_serial 0.1.0
skio_usb_serial: ^0.1.0 copied to clipboard
USB serial port for Flutter on Android (USB OTG) and web (Web Serial): CP210x, CH340, FTDI, CDC-ACM. Direct JNI via jnigen, no platform channels.
skio_usb_serial #
USB serial ports for Flutter on Android (USB OTG) and the web (Web Serial in desktop Chrome and Edge). Works with CP210x, CH340/CH9102, FTDI, PL2303, CDC-ACM and ESP32/RP2040 native USB. Android calls the OS directly through JNI (jnigen), so there are no platform channels, and all queueing and framing logic is Dart you can unit-test.
import 'dart:convert';
import 'package:skio_usb_serial/skio_usb_serial.dart';
final ports = await UsbSerialPort.list();
final device = ports.first;
final access = await UsbSerialPort.access.requestAccess(device);
if (!access.isUsable) return;
final port = await UsbSerialPort.open(
device,
config: const SerialConfig(baudRate: 115200), // 8N1 by default
);
port.input.transform(const LineReader()).listen(print);
await port.write(utf8.encode('hello\n'));
await port.close();
Platforms #
| Android | Web | |
|---|---|---|
| How | USB host (OTG) + usb-serial-for-android | Web Serial API |
| Browsers / OS | Android 7.0+ (API 24) | Chrome and Edge on desktop, https or localhost |
| Finding ports | list() shows attached adapters |
request() opens the browser chooser (from a tap); list() shows ports granted before |
| Permission | USB dialog per device: access.requestAccess(device) |
Picking the port in the chooser is the permission |
| Attach/detach events | Yes (polled every second while listened) | Yes |
| Parity mark/space, 1.5 stop bits, DTR/DSR and XON/XOFF flow control | Depends on the chip | Not supported (Unsupported) |
Tested hardware #
| Adapter | Platform | Result |
|---|---|---|
| CH340 (1a86:7523) | Android 16, POCO M7 5G, USB OTG | ✅ list, permission, open, receive, send |
| CH340 (1a86:7523) | Chrome on macOS, Web Serial | ✅ choose port, open, receive, send |
Tried another adapter or phone? Please
open an issue with the result
and your SkioLog output.
Setup #
Android: nothing to add. The plugin declares android.hardware.usb.host
as optional, ships R8 keep rules, and needs no runtime permission. The
usb-serial-for-android library comes from JitPack; the plugin adds that
repository for its group only.
Web: serve over https (or localhost). Call UsbSerialPort.request() from a
button's onPressed, because browsers only show the chooser after a user
gesture.
Features #
- List ports and narrow them with
DeviceFilter(vendorId:, productId:) - Baud rate, data bits, parity, stop bits and flow control via
SerialConfig - Byte
Streaminput that buffers until you listen - Writes in call order, never blocking the UI thread
- DTR/RTS with
setSignals(), anddtr/rtsinSerialConfigto set them on open openDelayfor boards that reset when the port opensLineReaderhandles lines and UTF-8 characters split across reads- Every error is a
HardwareExceptionfromskio_core:AccessDenied,DeviceNotFound,DeviceBusy,Disconnected,OperationTimeout,Unsupported,ProtocolError
Troubleshooting #
| Symptom | Cause and fix |
|---|---|
| ESP32 or Arduino restarts when you connect | DTR/RTS toggle the board's reset line. Open with SerialConfig(baudRate: 115200, dtr: false, rts: false). |
| Lines arrive cut in half | USB delivers chunks, not messages. Use LineReader or your own framing. |
DeviceBusy on the web |
The port is open in another tab or app. Close it there. |
DeviceBusy on Android |
Another app holds the device. Unplug and replug, then open again. |
| No ports on Android | The phone needs USB OTG, and some need OTG enabled in settings. Try a powered hub for boards that draw more current. |
| Chooser doesn't open on the web | request() wasn't called from a user gesture, or the page isn't https/localhost. |
Debug logging #
Logging is off by default. Turn it on to see opens, closes, errors and, at
trace, every byte sent and received:
SkioLog.level = LogLevel.trace;
SkioLog.records.listen(print);
// 2026-09-24T10:15:02.114 TRACE skio_usb_serial [/dev/bus/usb/001/002]: RX 5: 4f 4b 0d 0a 3e
Attach this output to bug reports. Nothing is sent anywhere by skio.
Testing your app without hardware #
import 'package:skio_usb_serial/platform_interface.dart';
UsbSerialPlatform.instance = MyFakePlatform(); // extends UsbSerialPlatform
Example #
example/ is a general-purpose serial terminal: pick a port, set
line settings, view text or hex, send text or hex, toggle DTR/RTS.
The skio family #
| Package | Purpose |
|---|---|
skio_core |
Shared types: access status, errors, device filters |
skio_usb_serial |
USB serial port (this package) |
skio_uvc_camera |
USB Video Class camera |
Licences #
skio_usb_serial is BSD-3-Clause. On Android it bundles usb-serial-for-android (MIT).
Source and issues: github.com/skio-flutter/skio.