skio_usb_serial
Send and receive data with USB serial devices: Arduino and ESP32 boards, USB-to-serial adapters, instruments. Android over USB OTG, and Chrome or Edge with Web Serial.
- ESP32
- STM32
- Nordic nRF
- Arduino
- CP210x
- CH340
- FTDI
skio connects Flutter apps to USB serial devices and USB cameras,
on Android and in the browser, with macOS, Windows, Linux and iPad coming soon.
It binds native APIs directly with jnigen and
dart:js_interop, so there are no platform channels, and the logic lives in
testable Dart.
$ flutter pub add skio_usb_serialIllustration of the calls in the code sample below.
Packages
Each plugin covers one transport. They share permissions, errors and device matching through skio_core.
Send and receive data with USB serial devices: Arduino and ESP32 boards, USB-to-serial adapters, instruments. Android over USB OTG, and Chrome or Edge with Web Serial.
USB cameras (endoscopes, microscopes, webcams) on Android and the web: live preview, JPEG photos, the camera's snapshot button, and your choice of resolution.
The shared building blocks: one permission API, one error family, device filters and opt-in logging for every skio plugin.
How it works
skio calls the platform USB APIs through generated bindings. Buffering, framing and timeouts run in Dart, where you can test them. The diagram shows serial; skio_uvc_camera takes the same path to UVCAndroid on Android and getUserMedia in the browser.
LineReader, one HardwareException type.
jnigen → UsbManager
Direct JNI calls, USB OTG.
js_interop → Web Serial
Chrome and Edge.
What it looks like
Open a port and exchange bytes, or open a camera and take a photo. Errors come from one sealed type, so a switch can handle every case.
DeviceFilter to match ports by vendor and product IDSerialConfig for baud rate, data bits, parity, stop bits and flow controldtr / rts set in the config, so ESP32 and Arduino boards don't reset on openLineReader for lines and UTF-8 split across readsUvcSize to choose the camera resolution from what it supportsSkioLog opt-in logs, down to every byte, for bug reportsfinal ports = await UsbSerialPort.list();
final port = await UsbSerialPort.open(ports.first,
config: const SerialConfig(baudRate: 115200));
port.input.listen((bytes) => print(bytes));
await port.write(utf8.encode('hello\n'));
await port.close();
final cameras = await UvcCamera.devices();
await UvcCamera.access.requestAccess(cameras.first);
final cam = await UvcCamera.open(cameras.first,
preferred: const [UvcSize(1920, 1080), UvcSize(1280, 720)]);
// in build(): UvcPreview(camera: cam)
final photo = await cam.capture(quality: 90);
cam.buttonPresses.listen((_) => cam.capture());
try {
// ... call any skio plugin
} on HardwareException catch (e) {
final advice = switch (e) {
AccessDenied() => 'Please allow access.',
Disconnected() || DeviceNotFound() => 'Reconnect the device.',
DeviceBusy() => 'Close other apps using it.',
_ => e.message,
};
}
Platform support
All three packages are on pub.dev and have been tested with real hardware on Android and the web. Desktop and iPad support is on the way.
| Package | Android | Web | Notes |
|---|---|---|---|
| skio_usb_serial | stableUSB OTG, Android 7.0+ | stableWeb Serial, Chrome and Edge | Tested with ESP32, STM32 and Nordic boards on vivo, OPPO, Samsung and POCO phones, and in Chrome on macOS. |
| skio_uvc_camera | stableUSB OTG, Android 7.0+ | stablegetUserMedia; no snapshot button | Preview, JPEG photos, snapshot button and resolution choice. Tested with a USB camera on a POCO phone (Android 16). |
| skio_core | pure Dart | pure Dart | No platform code. Each plugin includes it. |
Get started
Each package's README on pub.dev explains every feature with examples.
One command each. skio_core comes with them.
$ flutter pub add skio_usb_serial
$ flutter pub add skio_uvc_cameraA serial terminal and a USB camera viewer, for an Android phone or Chrome.
$ git clone https://github.com/skio-flutter/skio
$ cd skio/packages/skio_usb_serial/example
$ flutter runBrowsers only list serial ports the user has picked. Call request() from a button tap.
FilledButton(
onPressed: () => UsbSerialPort.request(),
child: const Text('Choose port'),
)Swap the platform for a fake in your tests. Your own logic runs as plain Dart.
import 'package:skio_usb_serial/platform_interface.dart';
UsbSerialPlatform.instance = MyFakePlatform();Principles
A few rules every package follows.
skio is open source and free to use. Stars, bug reports and pull requests all help it grow.