Initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
/// Flutter plugin for USB serial communication on Android.
|
||||
///
|
||||
/// Uses `usb-serial-for-android` (https://github.com/mik3y/usb-serial-for-android)
|
||||
/// as the underlying Android library.
|
||||
library;
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
part 'src/usb_serial_device.dart';
|
||||
part 'src/usb_serial_port.dart';
|
||||
part 'src/flutter_usb_serial.dart';
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'flutter_usb_serial_platform_interface.dart';
|
||||
|
||||
/// An implementation of [FlutterUsbSerialPlatform] that uses method channels.
|
||||
class MethodChannelFlutterUsbSerial extends FlutterUsbSerialPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('flutter_usb_serial');
|
||||
|
||||
@override
|
||||
Future<String?> getPlatformVersion() async {
|
||||
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||
return version;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'flutter_usb_serial_method_channel.dart';
|
||||
|
||||
abstract class FlutterUsbSerialPlatform extends PlatformInterface {
|
||||
/// Constructs a FlutterUsbSerialPlatform.
|
||||
FlutterUsbSerialPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static FlutterUsbSerialPlatform _instance = MethodChannelFlutterUsbSerial();
|
||||
|
||||
/// The default instance of [FlutterUsbSerialPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelFlutterUsbSerial].
|
||||
static FlutterUsbSerialPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [FlutterUsbSerialPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(FlutterUsbSerialPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<String?> getPlatformVersion() {
|
||||
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
part of '../flutter_usb_serial.dart';
|
||||
|
||||
/// Main entry point for the flutter_usb_serial plugin.
|
||||
///
|
||||
/// Only supported on Android.
|
||||
class FlutterUsbSerial {
|
||||
FlutterUsbSerial._();
|
||||
|
||||
static const _methods = MethodChannel('flutter_usb_serial/methods');
|
||||
static const _events = EventChannel('flutter_usb_serial/data');
|
||||
|
||||
static Stream<Map<Object?, Object?>>? _rawEventStream;
|
||||
|
||||
static Stream<Map<Object?, Object?>> get _eventStream {
|
||||
_rawEventStream ??= _events
|
||||
.receiveBroadcastStream()
|
||||
.cast<Map<Object?, Object?>>();
|
||||
return _rawEventStream!;
|
||||
}
|
||||
|
||||
/// Returns the list of currently attached USB serial devices.
|
||||
static Future<List<UsbSerialDevice>> listDevices() async {
|
||||
final result = await _methods.invokeMethod<List<Object?>>('listDevices');
|
||||
if (result == null) return [];
|
||||
return result
|
||||
.whereType<Map<Object?, Object?>>()
|
||||
.map(UsbSerialDevice.fromMap)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Requests Android USB permission for [device] if not already granted.
|
||||
///
|
||||
/// Returns `true` if permission was granted (or was already held).
|
||||
static Future<bool> requestPermission(UsbSerialDevice device) async {
|
||||
final granted = await _methods.invokeMethod<bool>('requestPermission', {
|
||||
'deviceId': device.deviceId,
|
||||
});
|
||||
return granted ?? false;
|
||||
}
|
||||
|
||||
/// Opens the first port of [device] with the given serial parameters.
|
||||
///
|
||||
/// [baudRate] defaults to 9600.
|
||||
/// [dataBits] must be 5, 6, 7, or 8. Defaults to 8.
|
||||
/// [parity] defaults to [UsbSerialParity.none].
|
||||
/// [stopBits] defaults to [UsbSerialStopBits.one].
|
||||
///
|
||||
/// The returned [UsbSerialPort] streams incoming data via [UsbSerialPort.inputStream].
|
||||
/// Call [UsbSerialPort.close] when done.
|
||||
static Future<UsbSerialPort> openPort(
|
||||
UsbSerialDevice device, {
|
||||
int baudRate = 9600,
|
||||
int dataBits = 8,
|
||||
UsbSerialParity parity = UsbSerialParity.none,
|
||||
UsbSerialStopBits stopBits = UsbSerialStopBits.one,
|
||||
}) async {
|
||||
final portId = await _methods.invokeMethod<int>('openPort', {
|
||||
'deviceId': device.deviceId,
|
||||
'baudRate': baudRate,
|
||||
'dataBits': dataBits,
|
||||
'parity': parity.index,
|
||||
'stopBits': _stopBitsIndex(stopBits),
|
||||
});
|
||||
if (portId == null) {
|
||||
throw PlatformException(
|
||||
code: 'OPEN_FAILED',
|
||||
message: 'Failed to open USB serial port for device ${device.deviceId}',
|
||||
);
|
||||
}
|
||||
|
||||
final dataStream = _eventStream
|
||||
.where((e) => e['portId'] == portId)
|
||||
.transform(StreamTransformer<Map<Object?, Object?>, Uint8List>.fromHandlers(
|
||||
handleData: (e, sink) {
|
||||
if (e['type'] == 'data') {
|
||||
final raw = e['data'];
|
||||
if (raw is Uint8List) {
|
||||
sink.add(raw);
|
||||
} else if (raw is List) {
|
||||
sink.add(Uint8List.fromList(raw.cast<int>()));
|
||||
}
|
||||
} else if (e['type'] == 'error') {
|
||||
sink.addError(Exception(
|
||||
e['message'] as String? ?? 'USB IO error'));
|
||||
}
|
||||
},
|
||||
));
|
||||
|
||||
return UsbSerialPort._(portId, dataStream);
|
||||
}
|
||||
|
||||
static int _stopBitsIndex(UsbSerialStopBits sb) {
|
||||
switch (sb) {
|
||||
case UsbSerialStopBits.one:
|
||||
return 1;
|
||||
case UsbSerialStopBits.onePointFive:
|
||||
return 3; // UsbSerialPort.STOPBITS_1_5
|
||||
case UsbSerialStopBits.two:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
part of '../flutter_usb_serial.dart';
|
||||
|
||||
/// Represents a USB serial device found during enumeration.
|
||||
class UsbSerialDevice {
|
||||
/// Unique device ID (Android USB device ID).
|
||||
final int deviceId;
|
||||
|
||||
/// Vendor ID (VID).
|
||||
final int vendorId;
|
||||
|
||||
/// Product ID (PID).
|
||||
final int productId;
|
||||
|
||||
/// Human-readable device name (product string from descriptor, may be empty).
|
||||
final String deviceName;
|
||||
|
||||
/// Manufacturer string (may be empty).
|
||||
final String manufacturerName;
|
||||
|
||||
/// Serial number string (may be empty).
|
||||
final String serialNumber;
|
||||
|
||||
const UsbSerialDevice({
|
||||
required this.deviceId,
|
||||
required this.vendorId,
|
||||
required this.productId,
|
||||
required this.deviceName,
|
||||
required this.manufacturerName,
|
||||
required this.serialNumber,
|
||||
});
|
||||
|
||||
factory UsbSerialDevice.fromMap(Map<Object?, Object?> map) {
|
||||
return UsbSerialDevice(
|
||||
deviceId: (map['deviceId'] as int?) ?? 0,
|
||||
vendorId: (map['vendorId'] as int?) ?? 0,
|
||||
productId: (map['productId'] as int?) ?? 0,
|
||||
deviceName: (map['deviceName'] as String?) ?? '',
|
||||
manufacturerName: (map['manufacturerName'] as String?) ?? '',
|
||||
serialNumber: (map['serialNumber'] as String?) ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() => {
|
||||
'deviceId': deviceId,
|
||||
'vendorId': vendorId,
|
||||
'productId': productId,
|
||||
'deviceName': deviceName,
|
||||
'manufacturerName': manufacturerName,
|
||||
'serialNumber': serialNumber,
|
||||
};
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'UsbSerialDevice(id=$deviceId, vid=0x${vendorId.toRadixString(16).padLeft(4, '0')}, '
|
||||
'pid=0x${productId.toRadixString(16).padLeft(4, '0')}, name="$deviceName")';
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
part of '../flutter_usb_serial.dart';
|
||||
|
||||
/// Parity modes for serial communication.
|
||||
enum UsbSerialParity { none, odd, even, mark, space }
|
||||
|
||||
/// Stop bits for serial communication.
|
||||
enum UsbSerialStopBits { one, onePointFive, two }
|
||||
|
||||
/// An open serial port handle returned by [FlutterUsbSerial.openPort].
|
||||
///
|
||||
/// Call [close] when done to release native resources.
|
||||
class UsbSerialPort {
|
||||
final int _portId;
|
||||
|
||||
UsbSerialPort._(this._portId, this._dataStream);
|
||||
|
||||
final Stream<Uint8List> _dataStream;
|
||||
|
||||
/// Stream of raw bytes received on this port.
|
||||
Stream<Uint8List> get inputStream => _dataStream;
|
||||
|
||||
static const _methods = MethodChannel('flutter_usb_serial/methods');
|
||||
|
||||
/// Write [data] to the port.
|
||||
///
|
||||
/// Returns the number of bytes actually written, or throws on error.
|
||||
Future<int> write(Uint8List data) async {
|
||||
final written = await _methods.invokeMethod<int>('write', {
|
||||
'portId': _portId,
|
||||
'data': data,
|
||||
});
|
||||
return written ?? 0;
|
||||
}
|
||||
|
||||
/// Close the port and release native resources.
|
||||
Future<void> close() async {
|
||||
await _methods.invokeMethod<void>('close', {'portId': _portId});
|
||||
}
|
||||
|
||||
int get portId => _portId;
|
||||
}
|
||||
Reference in New Issue
Block a user