Enhance USB serial plugin for Android and iPadOS with DriverKit support

- Updated README.md to reflect platform support for iPadOS.
- Implemented isDriverAvailable method in FlutterUsbSerialPlugin for DriverKit readiness check.
- Added DriverKitSerialClient C source and header files for iPadOS integration.
- Created Swift Package for iPadOS support.
- Updated pubspec.yaml and flutter_usb_serial.dart to clarify platform compatibility.
- Enhanced tests to verify isDriverAvailable functionality.

Signed-off-by: Tony <tonylu@tony-cloud.com>
This commit is contained in:
Tony
2026-08-01 20:40:46 +08:00
parent 2d7fc43ea2
commit 17414656a9
11 changed files with 724 additions and 31 deletions
+25 -12
View File
@@ -1,18 +1,31 @@
# flutter_usb_serial
A new Flutter plugin project.
Flutter USB serial transport for Android and DriverKit-enabled iPadOS.
## Getting Started
## Platforms
This project is a starting point for a Flutter
[plug-in package](https://flutter.dev/to/develop-plugins),
a specialized package that includes platform-specific implementation code for
Android and/or iOS.
- Android uses `usb-serial-for-android` and the normal Android USB permission
flow.
- iPadOS 16+ uses Swift Package Manager and a user client exposed by a
USBDriverKit extension embedded in the host app. DriverKit USB support is
available only on iPads with an M-series chip.
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
The package contains the SwiftPM client plugin, not a reusable pre-signed
DriverKit extension. Apple provisions USB transport entitlements to the host
app and driver for a specific USB vendor. The host app must:
The plugin project was generated without specifying the `--platforms` flag, no platforms are currently supported.
To add platforms, run `flutter create -t plugin --platforms <platforms> .` in this directory.
You can also find a detailed instruction on how to add platforms in the `pubspec.yaml` at https://flutter.dev/to/pubspec-plugin-platforms.
1. Embed a DriverKit extension whose `IOUserClass` is
`DaliMasterEspressifUsbDriver`, or adapt the client service name.
2. Add `com.apple.developer.driverkit.communicates-with-drivers` to the app.
3. Sign the extension with `com.apple.developer.driverkit` and a
`com.apple.developer.driverkit.transport.usb` entitlement approved for its
USB vendor ID.
4. Enable Swift Package Manager integration for Flutter iOS plugins.
Call `FlutterUsbSerial.isDriverAvailable()` during startup before showing a USB
transport. It returns `true` only when a compatible driver service is loaded.
The DaliMaster host implementation is deliberately restricted to Espressif
vendor ID `0x303A` and the USB Serial/JTAG interface. Serial parameter and modem
line methods are accepted for API compatibility; the ESP USB Serial/JTAG bulk
transport does not use UART baud or line coding.
@@ -127,6 +127,7 @@ class FlutterUsbSerialPlugin :
override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"isDriverAvailable" -> result.success(true)
"listDevices" -> listDevices(result)
"requestPermission" -> requestPermission(call, result)
"openPort" -> openPort(call, result)
+37
View File
@@ -0,0 +1,37 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "flutter_usb_serial",
platforms: [
.iOS("16.0"),
],
products: [
.library(name: "flutter-usb-serial", targets: ["flutter_usb_serial"]),
],
dependencies: [
.package(name: "FlutterFramework", path: "../FlutterFramework"),
],
targets: [
.target(
name: "DriverKitSerialClient",
path: "Sources/DriverKitSerialClient",
publicHeadersPath: "include",
linkerSettings: [
.linkedFramework("IOKit"),
]
),
.target(
name: "flutter_usb_serial",
dependencies: [
"DriverKitSerialClient",
.product(name: "FlutterFramework", package: "FlutterFramework"),
],
path: "Sources/flutter_usb_serial",
resources: [
.process("Resources"),
]
),
]
)
@@ -0,0 +1,195 @@
#include "DriverKitSerialClient.h"
#include <IOKit/IOKitLib.h>
#include <mach/mach.h>
#include <string.h>
static const char *const kFlutterUsbDriverService =
"DaliMasterEspressifUsbDriver";
enum {
kFlutterUsbDriverGetInfo = 0,
kFlutterUsbDriverWrite = 1,
kFlutterUsbDriverRead = 2,
kFlutterUsbDriverControl = 3,
kFlutterUsbDriverConfigure = 4,
kFlutterUsbDriverSetLineState = 5,
kFlutterUsbDriverPurge = 6,
};
static bool FlutterUsbSerialDriverGetInfo(
io_connect_t connection,
FlutterUsbDriverDeviceInfo *info) {
size_t outputSize = sizeof(*info);
memset(info, 0, sizeof(*info));
kern_return_t result = IOConnectCallStructMethod(
connection, kFlutterUsbDriverGetInfo, NULL, 0, info, &outputSize);
return result == KERN_SUCCESS && outputSize == sizeof(*info) &&
info->protocolVersion == 1 &&
info->kind == FlutterUsbDriverKindSerial;
}
size_t FlutterUsbSerialDriverCopyDevices(
FlutterUsbDriverDeviceInfo *devices,
size_t capacity) {
CFMutableDictionaryRef matching =
IOServiceNameMatching(kFlutterUsbDriverService);
if (matching == NULL) {
return 0;
}
io_iterator_t iterator = IO_OBJECT_NULL;
kern_return_t result = IOServiceGetMatchingServices(
kIOMainPortDefault, matching, &iterator);
if (result != KERN_SUCCESS) {
return 0;
}
size_t count = 0;
io_service_t service = IO_OBJECT_NULL;
while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
io_connect_t connection = IO_OBJECT_NULL;
FlutterUsbDriverDeviceInfo info;
if (IOServiceOpen(service, mach_task_self_, 0, &connection) ==
KERN_SUCCESS &&
FlutterUsbSerialDriverGetInfo(connection, &info)) {
uint64_t registryEntryId = 0;
if (IORegistryEntryGetRegistryEntryID(service, &registryEntryId) ==
KERN_SUCCESS) {
info.registryEntryId = registryEntryId;
if (devices != NULL && count < capacity) {
devices[count] = info;
}
count++;
}
}
if (connection != IO_OBJECT_NULL) {
IOServiceClose(connection);
}
IOObjectRelease(service);
}
IOObjectRelease(iterator);
return count;
}
bool FlutterUsbSerialDriverIsAvailable(void) {
return FlutterUsbSerialDriverCopyDevices(NULL, 0) > 0;
}
int32_t FlutterUsbSerialDriverOpen(uint64_t registryEntryId,
uint32_t *connection) {
if (connection == NULL) {
return kIOReturnBadArgument;
}
*connection = IO_OBJECT_NULL;
CFMutableDictionaryRef matching =
IOServiceNameMatching(kFlutterUsbDriverService);
if (matching == NULL) {
return kIOReturnNoMemory;
}
io_iterator_t iterator = IO_OBJECT_NULL;
kern_return_t result = IOServiceGetMatchingServices(
kIOMainPortDefault, matching, &iterator);
if (result != KERN_SUCCESS) {
return result;
}
io_service_t service = IO_OBJECT_NULL;
result = kIOReturnNotFound;
while ((service = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
uint64_t candidateId = 0;
if (IORegistryEntryGetRegistryEntryID(service, &candidateId) ==
KERN_SUCCESS &&
candidateId == registryEntryId) {
io_connect_t opened = IO_OBJECT_NULL;
result = IOServiceOpen(service, mach_task_self_, 0, &opened);
if (result == KERN_SUCCESS) {
FlutterUsbDriverDeviceInfo info;
if (FlutterUsbSerialDriverGetInfo(opened, &info)) {
*connection = opened;
} else {
IOServiceClose(opened);
result = kIOReturnUnsupported;
}
}
IOObjectRelease(service);
break;
}
IOObjectRelease(service);
}
IOObjectRelease(iterator);
return result;
}
void FlutterUsbSerialDriverClose(uint32_t connection) {
if (connection != IO_OBJECT_NULL) {
IOServiceClose((io_connect_t)connection);
}
}
int32_t FlutterUsbSerialDriverWrite(uint32_t connection,
const uint8_t *bytes,
uint32_t length,
uint32_t *bytesWritten) {
if (connection == IO_OBJECT_NULL || bytes == NULL || bytesWritten == NULL) {
return kIOReturnBadArgument;
}
uint64_t output = 0;
uint32_t outputCount = 1;
kern_return_t result = IOConnectCallMethod(
(io_connect_t)connection, kFlutterUsbDriverWrite, NULL, 0, bytes, length,
&output, &outputCount, NULL, NULL);
*bytesWritten = result == KERN_SUCCESS ? (uint32_t)output : 0;
return result;
}
int32_t FlutterUsbSerialDriverRead(uint32_t connection,
uint8_t *bytes,
uint32_t capacity,
uint32_t timeoutMilliseconds,
uint32_t *bytesRead) {
if (connection == IO_OBJECT_NULL || bytes == NULL || bytesRead == NULL) {
return kIOReturnBadArgument;
}
const uint64_t inputs[] = {capacity, timeoutMilliseconds};
size_t outputSize = capacity;
kern_return_t result = IOConnectCallMethod(
(io_connect_t)connection, kFlutterUsbDriverRead, inputs, 2, NULL, 0,
NULL, NULL, bytes, &outputSize);
if (result == kIOReturnTimeout) {
*bytesRead = 0;
return KERN_SUCCESS;
}
*bytesRead = result == KERN_SUCCESS ? (uint32_t)outputSize : 0;
return result;
}
int32_t FlutterUsbSerialDriverConfigure(uint32_t connection,
uint32_t baudRate,
uint8_t dataBits,
uint8_t parity,
uint8_t stopBits) {
const uint64_t inputs[] = {baudRate, dataBits, parity, stopBits};
return IOConnectCallScalarMethod((io_connect_t)connection,
kFlutterUsbDriverConfigure, inputs, 4,
NULL, NULL);
}
int32_t FlutterUsbSerialDriverSetLineState(uint32_t connection,
bool dtr,
bool rts) {
const uint64_t inputs[] = {dtr ? 1U : 0U, rts ? 1U : 0U};
return IOConnectCallScalarMethod((io_connect_t)connection,
kFlutterUsbDriverSetLineState, inputs, 2,
NULL, NULL);
}
int32_t FlutterUsbSerialDriverPurge(uint32_t connection,
bool purgeWrite,
bool purgeRead) {
const uint64_t inputs[] = {purgeWrite ? 1U : 0U, purgeRead ? 1U : 0U};
return IOConnectCallScalarMethod((io_connect_t)connection,
kFlutterUsbDriverPurge, inputs, 2,
NULL, NULL);
}
@@ -0,0 +1,64 @@
#ifndef DRIVER_KIT_SERIAL_CLIENT_H
#define DRIVER_KIT_SERIAL_CLIENT_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
FlutterUsbDriverKindUnknown = 0,
FlutterUsbDriverKindSerial = 1,
FlutterUsbDriverKindHid = 2,
};
typedef struct {
uint64_t registryEntryId;
uint32_t protocolVersion;
uint32_t kind;
uint16_t vendorId;
uint16_t productId;
uint16_t maxInputPacketSize;
uint16_t maxOutputPacketSize;
uint8_t interfaceNumber;
uint8_t inputEndpoint;
uint8_t outputEndpoint;
uint8_t reserved;
} FlutterUsbDriverDeviceInfo;
bool FlutterUsbSerialDriverIsAvailable(void);
size_t FlutterUsbSerialDriverCopyDevices(
FlutterUsbDriverDeviceInfo *devices,
size_t capacity);
int32_t FlutterUsbSerialDriverOpen(uint64_t registryEntryId,
uint32_t *connection);
void FlutterUsbSerialDriverClose(uint32_t connection);
int32_t FlutterUsbSerialDriverWrite(uint32_t connection,
const uint8_t *bytes,
uint32_t length,
uint32_t *bytesWritten);
int32_t FlutterUsbSerialDriverRead(uint32_t connection,
uint8_t *bytes,
uint32_t capacity,
uint32_t timeoutMilliseconds,
uint32_t *bytesRead);
int32_t FlutterUsbSerialDriverConfigure(uint32_t connection,
uint32_t baudRate,
uint8_t dataBits,
uint8_t parity,
uint8_t stopBits);
int32_t FlutterUsbSerialDriverSetLineState(uint32_t connection,
bool dtr,
bool rts);
int32_t FlutterUsbSerialDriverPurge(uint32_t connection,
bool purgeWrite,
bool purgeRead);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,331 @@
import DriverKitSerialClient
import Flutter
import Foundation
private final class SerialPortHandle {
let portId: Int
let connection: UInt32
let info: FlutterUsbDriverDeviceInfo
let readQueue: DispatchQueue
var running = true
var dtr = false
var rts = false
init(portId: Int, connection: UInt32, info: FlutterUsbDriverDeviceInfo) {
self.portId = portId
self.connection = connection
self.info = info
readQueue = DispatchQueue(
label: "flutter_usb_serial.driverkit.\(portId)",
qos: .userInitiated
)
}
}
public final class FlutterUsbSerialPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
private static let methodChannelName = "flutter_usb_serial/methods"
private static let eventChannelName = "flutter_usb_serial/data"
private var eventSink: FlutterEventSink?
private var nextPortId = 1
private var ports: [Int: SerialPortHandle] = [:]
private let stateQueue = DispatchQueue(label: "flutter_usb_serial.driverkit.state")
public static func register(with registrar: FlutterPluginRegistrar) {
let instance = FlutterUsbSerialPlugin()
let methods = FlutterMethodChannel(
name: methodChannelName,
binaryMessenger: registrar.messenger()
)
registrar.addMethodCallDelegate(instance, channel: methods)
let events = FlutterEventChannel(
name: eventChannelName,
binaryMessenger: registrar.messenger()
)
events.setStreamHandler(instance)
}
public func onListen(
withArguments arguments: Any?,
eventSink events: @escaping FlutterEventSink
) -> FlutterError? {
eventSink = events
return nil
}
public func onCancel(withArguments arguments: Any?) -> FlutterError? {
eventSink = nil
return nil
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "isDriverAvailable":
result(FlutterUsbSerialDriverIsAvailable())
case "listDevices":
result(copyDevices().map(deviceMap))
case "requestPermission":
result(deviceInfo(from: call.arguments) != nil)
case "openPort":
openPort(arguments: call.arguments, result: result)
case "write":
write(arguments: call.arguments, result: result)
case "setParameters":
setParameters(arguments: call.arguments, result: result)
case "setDTR":
setLineState(arguments: call.arguments, dtr: true, result: result)
case "setRTS":
setLineState(arguments: call.arguments, dtr: false, result: result)
case "purgeHwBuffers":
purge(arguments: call.arguments, result: result)
case "close":
close(arguments: call.arguments, result: result)
default:
result(FlutterMethodNotImplemented)
}
}
private func copyDevices() -> [FlutterUsbDriverDeviceInfo] {
let count = FlutterUsbSerialDriverCopyDevices(nil, 0)
guard count > 0 else { return [] }
var devices = Array(repeating: FlutterUsbDriverDeviceInfo(), count: count)
let copied = devices.withUnsafeMutableBufferPointer { buffer in
FlutterUsbSerialDriverCopyDevices(buffer.baseAddress, buffer.count)
}
return Array(devices.prefix(min(copied, devices.count)))
}
private func deviceMap(_ info: FlutterUsbDriverDeviceInfo) -> [String: Any] {
let signedId = Int64(bitPattern: info.registryEntryId)
return [
"deviceId": NSNumber(value: signedId),
"vendorId": Int(info.vendorId),
"productId": Int(info.productId),
"deviceName": "Espressif USB Serial/JTAG",
"manufacturerName": "Espressif",
"serialNumber": "",
]
}
private func deviceInfo(from arguments: Any?) -> FlutterUsbDriverDeviceInfo? {
guard
let map = arguments as? [String: Any],
let number = map["deviceId"] as? NSNumber
else { return nil }
let registryId = UInt64(bitPattern: number.int64Value)
return copyDevices().first { $0.registryEntryId == registryId }
}
private func openPort(arguments: Any?, result: @escaping FlutterResult) {
guard
let map = arguments as? [String: Any],
let deviceNumber = map["deviceId"] as? NSNumber
else {
result(argumentError("deviceId is required"))
return
}
let registryId = UInt64(bitPattern: deviceNumber.int64Value)
guard let info = copyDevices().first(where: { $0.registryEntryId == registryId }) else {
result(pluginError("DEVICE_NOT_FOUND", "Espressif USB device is not available"))
return
}
var connection: UInt32 = 0
let openResult = FlutterUsbSerialDriverOpen(registryId, &connection)
guard openResult == 0 else {
result(driverError("OPEN_FAILED", "Could not open DriverKit user client", openResult))
return
}
let baudRate = (map["baudRate"] as? NSNumber)?.uint32Value ?? 9_600
let dataBits = (map["dataBits"] as? NSNumber)?.uint8Value ?? 8
let parity = (map["parity"] as? NSNumber)?.uint8Value ?? 0
let stopBits = (map["stopBits"] as? NSNumber)?.uint8Value ?? 1
let configureResult = FlutterUsbSerialDriverConfigure(
connection, baudRate, dataBits, parity, stopBits
)
guard configureResult == 0 else {
FlutterUsbSerialDriverClose(connection)
result(driverError("OPEN_FAILED", "Could not configure USB serial port", configureResult))
return
}
let handle: SerialPortHandle = stateQueue.sync {
let id = nextPortId
nextPortId += 1
let created = SerialPortHandle(portId: id, connection: connection, info: info)
ports[id] = created
return created
}
startReading(handle)
result(handle.portId)
}
private func startReading(_ handle: SerialPortHandle) {
handle.readQueue.async { [weak self, weak handle] in
guard let self, let handle else { return }
let capacity = max(Int(handle.info.maxInputPacketSize), 64)
var buffer = [UInt8](repeating: 0, count: capacity)
while self.stateQueue.sync(execute: { handle.running }) {
var bytesRead: UInt32 = 0
let readResult = buffer.withUnsafeMutableBytes { rawBuffer in
FlutterUsbSerialDriverRead(
handle.connection,
rawBuffer.bindMemory(to: UInt8.self).baseAddress,
UInt32(rawBuffer.count),
250,
&bytesRead
)
}
if readResult != 0 {
if self.stateQueue.sync(execute: { handle.running }) {
self.emit([
"portId": handle.portId,
"type": "error",
"message": self.driverMessage("USB read failed", readResult),
])
}
break
}
if bytesRead > 0 {
self.emit([
"portId": handle.portId,
"type": "data",
"data": FlutterStandardTypedData(
bytes: Data(buffer.prefix(Int(bytesRead)))
),
])
}
}
}
}
private func write(arguments: Any?, result: @escaping FlutterResult) {
guard
let map = arguments as? [String: Any],
let handle = port(from: map),
let typedData = map["data"] as? FlutterStandardTypedData
else {
result(argumentError("portId and data are required"))
return
}
var bytesWritten: UInt32 = 0
let writeResult = typedData.data.withUnsafeBytes { rawBuffer in
FlutterUsbSerialDriverWrite(
handle.connection,
rawBuffer.bindMemory(to: UInt8.self).baseAddress,
UInt32(rawBuffer.count),
&bytesWritten
)
}
if writeResult == 0 {
result(Int(bytesWritten))
} else {
result(driverError("WRITE_FAILED", "USB serial write failed", writeResult))
}
}
private func setParameters(arguments: Any?, result: @escaping FlutterResult) {
guard let map = arguments as? [String: Any], let handle = port(from: map) else {
result(argumentError("portId is required"))
return
}
let callResult = FlutterUsbSerialDriverConfigure(
handle.connection,
(map["baudRate"] as? NSNumber)?.uint32Value ?? 9_600,
(map["dataBits"] as? NSNumber)?.uint8Value ?? 8,
(map["parity"] as? NSNumber)?.uint8Value ?? 0,
(map["stopBits"] as? NSNumber)?.uint8Value ?? 1
)
completeVoid(callResult, code: "CONFIGURE_FAILED", result: result)
}
private func setLineState(
arguments: Any?,
dtr: Bool,
result: @escaping FlutterResult
) {
guard
let map = arguments as? [String: Any],
let handle = port(from: map),
let value = map["value"] as? Bool
else {
result(argumentError("portId and value are required"))
return
}
if dtr { handle.dtr = value } else { handle.rts = value }
let callResult = FlutterUsbSerialDriverSetLineState(
handle.connection, handle.dtr, handle.rts
)
completeVoid(callResult, code: "LINE_STATE_FAILED", result: result)
}
private func purge(arguments: Any?, result: @escaping FlutterResult) {
guard let map = arguments as? [String: Any], let handle = port(from: map) else {
result(argumentError("portId is required"))
return
}
let callResult = FlutterUsbSerialDriverPurge(
handle.connection,
map["purgeWriteBuffers"] as? Bool ?? true,
map["purgeReadBuffers"] as? Bool ?? true
)
completeVoid(callResult, code: "PURGE_FAILED", result: result)
}
private func close(arguments: Any?, result: @escaping FlutterResult) {
guard
let map = arguments as? [String: Any],
let portId = (map["portId"] as? NSNumber)?.intValue
else {
result(argumentError("portId is required"))
return
}
let handle = stateQueue.sync { () -> SerialPortHandle? in
guard let removed = ports.removeValue(forKey: portId) else { return nil }
removed.running = false
return removed
}
if let handle {
FlutterUsbSerialDriverClose(handle.connection)
}
result(nil)
}
private func port(from map: [String: Any]) -> SerialPortHandle? {
guard let portId = (map["portId"] as? NSNumber)?.intValue else { return nil }
return stateQueue.sync { ports[portId] }
}
private func completeVoid(
_ callResult: Int32,
code: String,
result: @escaping FlutterResult
) {
if callResult == 0 {
result(nil)
} else {
result(driverError(code, "DriverKit operation failed", callResult))
}
}
private func emit(_ event: [String: Any]) {
DispatchQueue.main.async { [weak self] in self?.eventSink?(event) }
}
private func argumentError(_ message: String) -> FlutterError {
pluginError("INVALID_ARGUMENT", message)
}
private func pluginError(_ code: String, _ message: String) -> FlutterError {
FlutterError(code: code, message: message, details: nil)
}
private func driverError(_ code: String, _ message: String, _ value: Int32) -> FlutterError {
FlutterError(code: code, message: driverMessage(message, value), details: value)
}
private func driverMessage(_ message: String, _ value: Int32) -> String {
String(format: "%@ (IOReturn 0x%08X)", message, UInt32(bitPattern: value))
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array/>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
+3 -2
View File
@@ -1,7 +1,8 @@
/// Flutter plugin for USB serial communication on Android.
/// Flutter plugin for USB serial communication on Android and iPadOS.
///
/// Uses `usb-serial-for-android` (https://github.com/mik3y/usb-serial-for-android)
/// as the underlying Android library.
/// as the underlying Android library. iPadOS uses an app-embedded DriverKit
/// extension supplied by the host application.
library;
import 'dart:async';
+18 -5
View File
@@ -2,7 +2,8 @@ part of '../flutter_usb_serial.dart';
/// Main entry point for the flutter_usb_serial plugin.
///
/// Only supported on Android.
/// Supported on Android and on iPadOS through an app-embedded DriverKit
/// extension.
class FlutterUsbSerial {
FlutterUsbSerial._();
@@ -18,6 +19,15 @@ class FlutterUsbSerial {
return _rawEventStream!;
}
/// Whether the native USB serial backend is ready.
///
/// Android always provides its USB host backend. On iPadOS this probes for
/// the DriverKit user-client service, so it is `false` until the matching
/// app-embedded driver has loaded successfully.
static Future<bool> isDriverAvailable() async {
return await _methods.invokeMethod<bool>('isDriverAvailable') ?? false;
}
/// Returns the list of currently attached USB serial devices.
static Future<List<UsbSerialDevice>> listDevices() async {
final result = await _methods.invokeMethod<List<Object?>>('listDevices');
@@ -70,7 +80,8 @@ class FlutterUsbSerial {
final dataStream = _eventStream
.where((e) => e['portId'] == portId)
.transform(StreamTransformer<Map<Object?, Object?>, Uint8List>.fromHandlers(
.transform(
StreamTransformer<Map<Object?, Object?>, Uint8List>.fromHandlers(
handleData: (e, sink) {
if (e['type'] == 'data') {
final raw = e['data'];
@@ -80,11 +91,13 @@ class FlutterUsbSerial {
sink.add(Uint8List.fromList(raw.cast<int>()));
}
} else if (e['type'] == 'error') {
sink.addError(Exception(
e['message'] as String? ?? 'USB IO error'));
sink.addError(
Exception(e['message'] as String? ?? 'USB IO error'),
);
}
},
));
),
);
return UsbSerialPort._(portId, dataStream);
}
+3 -1
View File
@@ -1,5 +1,5 @@
name: flutter_usb_serial
description: "Flutter plugin for USB serial communication on Android using usb-serial-for-android."
description: "Flutter USB serial plugin for Android and iPadOS DriverKit."
version: 0.1.0
environment:
@@ -22,3 +22,5 @@ flutter:
android:
package: com.dalimaster.flutter_usb_serial
pluginClass: FlutterUsbSerialPlugin
ios:
pluginClass: FlutterUsbSerialPlugin
+25 -1
View File
@@ -1,10 +1,16 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_usb_serial/flutter_usb_serial.dart';
import 'package:flutter_usb_serial/flutter_usb_serial_method_channel.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('$MethodChannelFlutterUsbSerial can be constructed', () {
expect(MethodChannelFlutterUsbSerial(), isA<MethodChannelFlutterUsbSerial>());
expect(
MethodChannelFlutterUsbSerial(),
isA<MethodChannelFlutterUsbSerial>(),
);
});
test('UsbSerialDevice maps fields from method channel payloads', () {
@@ -25,4 +31,22 @@ void main() {
expect(device.serialNumber, 'SN-42');
expect(device.toMap()['deviceId'], 7);
});
test(
'isDriverAvailable forwards the native DriverKit readiness result',
() async {
const channel = MethodChannel('flutter_usb_serial/methods');
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, (call) async {
expect(call.method, 'isDriverAvailable');
return true;
});
addTearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(channel, null);
});
expect(await FlutterUsbSerial.isDriverAvailable(), isTrue);
},
);
}