import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:flserial/flserial_exception.dart'; import 'flserial_bindings_generated.dart'; const String _libName = 'flserial'; const int _serialIOBuffLen = 1024 * 8; /// The dynamic library in which the symbols for [FlserialBindings] can be found. final DynamicLibrary dylib = () { if (Platform.isMacOS || Platform.isIOS) { return DynamicLibrary.open('$_libName.framework/$_libName'); } if (Platform.isAndroid || Platform.isLinux) { return DynamicLibrary.open('lib$_libName.so'); } if (Platform.isWindows) { return DynamicLibrary.open('$_libName.dll'); } throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}'); }(); /// The bindings to the native functions in [_dylib]. final FlserialBindings bindings = FlserialBindings(dylib); /// Native pointer to string conversion String nativeInt8ToString(Pointer pointer, {bool allowMalformed = true}) { var ptrName = pointer.cast(); final ptrNameCodeUnits = pointer.cast(); var list = ptrNameCodeUnits.asTypedList(ptrName.length); return utf8.decode(list, allowMalformed: allowMalformed); } /// String conversion to native pointer, memory should be free outside function Pointer stringToNativeInt8(String str, {Allocator allocator = calloc}) { final units = utf8.encode(str); final result = allocator(units.length + 1); final nativeString = result.asTypedList(units.length + 1); nativeString.setAll(0, units); nativeString[units.length] = 0; return result.cast(); } /// List conversion to native pointer, memory should be free outside function Pointer int8ListToPointerInt8(Uint8List units, Pointer pointer, {Allocator allocator = calloc}) { final nativeString = pointer.asTypedList(units.length + 1); //blobBytes nativeString.setAll(0, units); nativeString[units.length] = 0; return pointer.cast(); } Pointer listToPointerDouble(List sample) { final Pointer pointer = calloc.allocate(sample.length, alignment: 8); final nativeSamples = pointer.asTypedList(sample.length); nativeSamples.setAll(0, sample); return pointer.cast(); } /// Native pointer conversion to list Int8List nativeInt8ToInt8List(Pointer pointer) { var ptrName = pointer.cast(); final ptrNameCodeUnits = pointer.cast(); var list = ptrNameCodeUnits.asTypedList(ptrName.length); return list; } /// Open serial port status enum FlOpenStatus { open, closed, error, } /// Class for incoming data handling class FlSerialEventArgs { FlSerialEventArgs(this.serial, this.len, this.cts, this.dsr); final FlSerial serial; final int len; final bool cts, dsr; } /// Main serial wrapper class over ffi class FlSerial { int flh = -1; bool prevCTS = false; bool prevDSR = false; var onSerialData = StreamController(); Pointer serialReadBuff = Pointer.fromAddress(0); Pointer serialWriteBuff = Pointer.fromAddress(0); /// Init should be called at program start, after FlSerial creation /// Function is used to make array of internal port structs for 16 parallel processing ports int init() { return bindings.fl_init(16); } /// Setting callback to native code. Used for incoming data signal void setCallback(int flh, {required DartflcallbackFunction callback}) { final nativeCallable = NativeCallable.listener(callback); bindings.fl_set_callback(flh, nativeCallable.nativeFunction); } /// Listing serial ports. Platform depend static List listPorts() { List list = List.empty(growable: true); Allocator allocator = calloc; var result = allocator(1024); for (int i = 0; i < 255; i++) { int len = bindings.fl_ports(i, 1024, result); if (len > 0) { String resultStr = result.cast().toDartString(length: len); list.add(resultStr); } else { break; } } allocator.free(result); return list; } /// Internal function for serial port handler checking int _checkFLH(int handler) { if (handler >= 0 && handler < MAX_PORT_COUNT) { return FlError.FL_ERROR_OK; } throw FlSerialException(FlError.FL_ERROR_HANDLER, msg: "$handler"); } /// Function for openning serial port FlOpenStatus openPort(String portname, int baudrate) { prevCTS = false; prevDSR = false; Pointer ptr = stringToNativeInt8(portname); flh = bindings.fl_open(flh, ptr, baudrate); calloc.free(ptr); int error = bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_LAST_ERROR, -1); int flerror = FlError.FL_ERROR_UNKNOW; if (error > 0) { /// error mapping String msg = "Port open error: $portname"; switch (error) { case 1: case 3: flerror = FlError.FL_ERROR_PORT_NOT_EXIST; msg += " port not exist"; break; case 2: flerror = FlError.FL_ERROR_PORT_ALLREADY_OPEN; msg += " port allready open"; break; } bindings.fl_close(flh); flh = -1; throw FlSerialException(flerror, msg: msg); } onSerialData = StreamController(); Allocator allocator = calloc; serialReadBuff = allocator(_serialIOBuffLen); serialWriteBuff = allocator(_serialIOBuffLen); setCallback( flh, callback: (cflh, len) async { if (len > 0) { onSerialData.add(FlSerialEventArgs(this, len, prevCTS, prevDSR)); } }, ); return isOpen(); } /// Function check is serial port opened FlOpenStatus isOpen() { if (flh < 0) { return FlOpenStatus.closed; } int error = bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_LAST_ERROR, -1); if (error > 0) { return FlOpenStatus.error; } int nres = bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_IS_PORT_OPEN, -1); if (nres == 0) { return FlOpenStatus.closed; } return FlOpenStatus.open; } /// Get last error message. Return empty string if no error String getLastError() { _checkFLH(flh); int res = bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_LAST_ERROR, -1); String strres = ""; switch (res) { case FlError.FL_ERROR_OK: strres = "0: None"; break; case FlError.FL_ERROR_PORT_ALLREADY_OPEN: strres = "$res: Port allready open"; break; case FlError.FL_ERROR_PORT_NOT_EXIST: strres = "$res: Port not exist"; break; default: strres = "-1: Unknow error"; break; } return strres; } /* Future _readLineStatus() async { _checkFLH(flh); bool currCTS = getCTS(); bool currDSR = getDSR(); if(currCTS != prevCTS || prevDSR != currDSR) { prevCTS = currCTS; prevDSR = currDSR; onSerialData.broadcast(FlSerialEventArgs( readBuff.length, Uint8List(0), currCTS, currDSR)); } return 0; }*/ /// Internal function to read serial data from lower layer with desired len. If no data is present, empty list is returned Uint8List _readList(int len) { _checkFLH(flh); if (len > _serialIOBuffLen) { len = _serialIOBuffLen; } int intres = bindings.fl_read(flh, len, serialReadBuff); if (intres <= 0) { return Uint8List(0); } final ptrNameCodeUnits = serialReadBuff.cast(); var list = ptrNameCodeUnits.asTypedList(intres); return list; } /// Read full read bufer from serial port Uint8List readList() { _checkFLH(flh); return readListLen(1024 * 8); } /// Read desired len from serial port buffer Uint8List readListLen(int len) { _checkFLH(flh); return _readList(len); } /// Read single byte int read() { _checkFLH(flh); Uint8List data = _readList(1); if (data.isNotEmpty) { return data.first; } return -1; } /// Write list data to serial port int write(Uint8List data) { _checkFLH(flh); Pointer ptr = int8ListToPointerInt8(data, serialWriteBuff); int res = bindings.fl_write(flh, data.length, ptr); return res; } /// Close serial port and free resources int closePort() { _checkFLH(flh); bindings.fl_close(flh); onSerialData.close(); Allocator allocator = calloc; if (serialReadBuff.value != 0) { allocator.free(serialReadBuff); } if (serialWriteBuff.value != 0) { allocator.free(serialWriteBuff); } flh = -1; return flh; } /// Uniwersal function to control serial port int ctrl(int cmd, int value) { _checkFLH(flh); return bindings.fl_ctrl(flh, cmd, value); } /// Function set RTS line in serial port int setRTS(bool value) { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_RTS, value ? 1 : 0); } /// Function set CTS line in serial port bool getCTS() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_GET_CTS, 0) > 0 ? true : false; } /// Get status of DTR line int setDTR(bool value) { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_DTR, value ? 1 : 0); } /// Get status of DSR line bool getDSR() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_GET_DSR, 0) > 0 ? true : false; } /// Set byte size to 5 bits int setByteSize5() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_BYTESIZE_5, 0); } /// Set byte size to 6 bits int setByteSize6() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_BYTESIZE_6, 0); } /// Set byte size to 7 bits int setByteSize7() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_BYTESIZE_7, 0); } /// Set byte size to 8 bits int setByteSize8() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_BYTESIZE_8, 0); } /// Set bit parity to none int setBitParityNone() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_PARITY_NONE, 0); } /// Set bit parity to odd int setBitParityOdd() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_PARITY_ODD, 0); } // Set bit parity to even int setBitParityEven() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_PARITY_EVEN, 0); } // Set bit parity to mark int setBitParityMark() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_PARITY_MARK, 0); } // Set bit parity to space int setBitParitySpace() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_PARITY_SPACE, 0); } /// Set stop bits to one int setStopBits1() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_STOPBITS_ONE, 0); } /// Set stop bits to one and half int setStopBits1_5() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_STOPBITS_ONE_POINT_FIVE, 0); } /// Set stop bits to two int setStopBits2() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_STOPBITS_TWO, 0); } /// Disable flow control int setFlowControlNone() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_FLOWCONTROL_NONE, 0); } /// Set hardware flow control int setFlowControlHardware() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_FLOWCONTROL_HARDWARE, 0); } /// Set xon/xoff flow control (software) int setFlowControlSoftware() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_SET_FLOWCONTROL_SOFTWARE, 0); } /// Waiting for status line change int setWaitStatusChange() { _checkFLH(flh); return bindings.fl_ctrl(flh, FlCtrl.FL_CTRL_GET_STATUS_CHANGE, 0); } /// Free resources. Serial port should not be used after calling this function int free() { return bindings.fl_free(); } }