FengChendian / serial_port_win32

A flutter SerialPort library using win32 API.
BSD 3-Clause "New" or "Revised" License
31 stars 9 forks source link

Getting SetCommError when started again after closing the port. #38

Open MANOJKUMAR26 opened 1 month ago

MANOJKUMAR26 commented 1 month ago

Hi Team, I am trying to read serial data through USB for flutter windows desktop application. The data gets read the first time properly but when I stop the port and start the reading again, I am getting setCommState error. I am using : serial_port_win32: ^1.3.0

Below is the code I am using to open the port and read the data and close the port.

late SerialPort port; void _getPortsAndClose() { port.close(); showToast( 'Readings stopped successfully', duration: Duration(seconds: 3), position: const ToastPosition(align: Alignment.topRight), backgroundColor: Colors.green ); }

void _getPortsAndOpen(Device selectedDevice) { final List portInfoLists = SerialPort.getPortsWithFullMessages(); ports = SerialPort.getAvailablePorts(); String datacopy = ''; print(portInfoLists); print(ports); if (ports.isNotEmpty) {

  port = SerialPort(selectedDevice.portName,
      openNow: false,BaudRate:9600, ReadIntervalTimeout: 1, ReadTotalTimeoutConstant: 2);
  port.open();

  String incomingData = '';
  String completeData = '';

  // while (port.isOpened) {
  // incomingData = '';
  port.readBytesOnListen(8, (value) {
    print('check vlaue ${utf8.decode(value)}');
  // if (utf8.decode(value) != '+' && utf8.decode(value) != ',') {
  incomingData += utf8.decode(value);
  print('check incoming data ${incomingData.length}');
  if(incomingData.length > 50) {
      String trimmedData = incomingData.substring(35);
      incomingData = trimmedData;
  }
  List<String> parts = incomingData.split(',');
  final regex = RegExp(r'\+\d+(?:\.\d+)?'); // Matches numbers optionally followed by a decimal point
  List<double> extractedValues = [];
  for (var part in parts) {
    final match = regex.matchAsPrefix(part);
    if (match != null) {
      // Extract the captured group (the number) and convert to double
      extractedValues.add(double.parse(match.group(0)!.substring(1))); // Remove leading "+"
    }
  }

  if (extractedValues.length >= 2) {
    completeData = extractedValues[extractedValues.length - 2].toString();
  }

  setState(() {data = completeData;});
  }
  );
  }

  Can you help me resolve this issue please?
  Below are the attempts I did to fix the issue:
  1) Tries to set stop bits to 0 and 1, didn't help.
  2) Tried giving delay before stopping the port so that if there is any queue it can complete but that also didn't help.
FengChendian commented 1 month ago

Did you re-create a same device name SerialPort? It shoude be single-instance mode.

I see you use port = SerialPort(selectedDevice.portName, openNow: false,BaudRate:9600, ReadIntervalTimeout: 1, ReadTotalTimeoutConstant: 2); every time.

And do you know the info of SetCommState error?

MANOJKUMAR26 commented 1 month ago

Hello @FengChendian , Thanks for your response.

Actually in my scenario, the same device can be connected to different COM ports, Now I tried creating singleton class for serial port and called the same in the method like below: class SerialPortSingleton { static final SerialPortSingleton _instance = SerialPortSingleton._internal(); SerialPort? port;

factory SerialPortSingleton() { return _instance; }

SerialPortSingleton._internal();

void initialize(String portName) { if (port == null) { port = SerialPort(portName, openNow: false, BaudRate: 9600, ReadIntervalTimeout: 1, ReadTotalTimeoutConstant: 2); } }

void openPort() { if (port != null && !port!.isOpened) { port!.open(); } }

void closePort() { if (port != null && port!.isOpened) { port!.close(); } } }

And in the method I am calling as below: if (ports.isNotEmpty) { SerialPortSingleton().initialize(selectedDevice.portName); SerialPortSingleton().openPort(); print(SerialPortSingleton().port?.isOpened);

For closing port: SerialPortSingleton().port?.close();

But like I said first time when I started the reading it showed perfectly. After stopping the port and starting again, I am still getting the same error as below: flutter: [Port Name: COM5, FriendlyName: USB-SERIAL CH340 (COM5)]
flutter: [COM5] Another exception was thrown: Exception: SetCommState error flutter: [Port Name: COM5, FriendlyName: USB-SERIAL CH340 (COM5)]
flutter: [COM5] Another exception was thrown: Exception: Open port failed, error is 5

I don't have much info on these errors.

FengChendian commented 1 month ago

Hello @FengChendian , Thanks for your response.

Actually in my scenario, the same device can be connected to different COM ports, Now I tried creating singleton class for serial port and called the same in the method like below: class SerialPortSingleton { static final SerialPortSingleton _instance = SerialPortSingleton._internal(); SerialPort? port;

factory SerialPortSingleton() { return _instance; }

SerialPortSingleton._internal();

void initialize(String portName) { if (port == null) { port = SerialPort(portName, openNow: false, BaudRate: 9600, ReadIntervalTimeout: 1, ReadTotalTimeoutConstant: 2); } }

void openPort() { if (port != null && !port!.isOpened) { port!.open(); } }

void closePort() { if (port != null && port!.isOpened) { port!.close(); } } }

And in the method I am calling as below: if (ports.isNotEmpty) { SerialPortSingleton().initialize(selectedDevice.portName); SerialPortSingleton().openPort(); print(SerialPortSingleton().port?.isOpened);

For closing port: SerialPortSingleton().port?.close();

But like I said first time when I started the reading it showed perfectly. After stopping the port and starting again, I am still getting the same error as below: flutter: [Port Name: COM5, FriendlyName: USB-SERIAL CH340 (COM5)] flutter: [COM5] Another exception was thrown: Exception: SetCommState error flutter: [Port Name: COM5, FriendlyName: USB-SERIAL CH340 (COM5)] flutter: [COM5] Another exception was thrown: Exception: Open port failed, error is 5

I don't have much info on these errors.

Do you use port.close() before you re-open it? Error code 5 means that Access is denied. It happened when your serial port is occupied (not closed in last instance). If you stop port by plugging USB, the port will not be closed properly in dart.

image

MANOJKUMAR26 commented 1 month ago

Hello @FengChendian , I have made sure (checked during debug mode also) to close the port before I try to re-open but still I am getting this error. Like you said If I remove the usb plug and insert the plug again, it works but keeping it inserted always and trying to close and open the port programmatically is causing the problem. Can you help me to fix this issue please?

FengChendian commented 3 weeks ago

I tested it. And I think it's due to the basic dependency ( win32 package) is too older to implement CloseHandler Function. I will try to update it to the latest version. You can try it again.

FengChendian commented 3 weeks ago

In example folder, you can see

  void _send() async {
    if (!port.isOpened) {
      port.open();
    }
    port.writeBytesFromString("AT", includeZeroTerminator: false);
    print(await port.readBytesUntil(Uint8List.fromList("T".codeUnits)));
    port.close();
  }

I use this code to test close and re-open. And it worked perfectly with latest win32 5.7.2. But not worked on win32 5.0.5.

Please test serial port win32 1.4.0 version. If bug exists in the new version, please give me feedback.

MANOJKUMAR26 commented 2 weeks ago

Hello @FengChendian , Even after upgrading to 1.4.2 version, I am facing the same issue. First time it opens the ports and reads properly. During the call of port.close function I can see in the debug mode that isOpened is changing to false. But still second time when i tried to open and read the port, getting "_Exception (Exception: SetCommState error)"

The below is where exactly I am facing the issue: image

Can you please help me out with this?

FengChendian commented 2 weeks ago

Hello @FengChendian , Even after upgrading to 1.4.2 version, I am facing the same issue. First time it opens the ports and reads properly. During the call of port.close function I can see in the debug mode that isOpened is changing to false. But still second time when i tried to open and read the port, getting "_Exception (Exception: SetCommState error)"

The below is where exactly I am facing the issue: image

Can you please help me out with this?

Um... It's really strange.

I see readData function is in your call stack. Are you still reading when port is closed? I mean that you use readOnListen and has a stream. The library doesn't cancel stream when port is closed. Maybe a uncanceled readOnListen stream can cause setCommState error in some specific platform, I guess.

MANOJKUMAR26 commented 2 weeks ago

Hello @FengChendian , Below is my flutter and dart version -

Sharing you the example code repository which is causing the error. https://github.com/MANOJKUMAR26/serial_win_32_sample_project/tree/test

Can you please check and suggest if I am going wrong somewhere.

FengChendian commented 1 week ago

The bug should be fixed in version 2.1.3. Thanks for @halildurmus.

@MANOJKUMAR26 By the way, do you use code working with Virtual Serial Port ? In my pc, this bug occurs only if I test it on Virtual Port.

Here is the bug fixed test:

image

I change some your codes for read function. Because:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:sample_project/SerialPortSingleton.dart';
import 'package:serial_port_win32/serial_port_win32.dart';
import 'package:sample_project/main.dart';
import 'package:english_words/english_words.dart';
import 'package:timer_builder/timer_builder.dart';
import 'package:intl/intl.dart';
import 'package:flutter/services.dart';
import 'package:oktoast/oktoast.dart';

class ReadingScreen extends StatefulWidget {
  const ReadingScreen({Key? key}) : super(key: key);

  @override
  ReadingPage createState() => ReadingPage();
}

class ReadingPage extends State<ReadingScreen> {
  var ports = <String>[];
  late String data = '0.0';
  bool light = true;
  bool startReadingFlag = false;
  String startTime = '';
  String endTime = '';
  String userName = '';
  late int startDate;
  final sendData = Uint8List.fromList(List.filled(4, 1, growable: false));
  SerialPort? serialSingle;

  @override
  void initState() {
    super.initState();
  }

  void _getPortsAndClose() {
    serialSingle?.close();
    showToast('Readings stopped successfully',
        duration: Duration(seconds: 3),
        position: const ToastPosition(align: Alignment.topRight),
        backgroundColor: Colors.green);
  }

  void _getPortsAndOpen() {
    final List<PortInfo> portInfoLists = SerialPort.getPortsWithFullMessages();
    ports = SerialPort.getAvailablePorts();
    String datacopy = '';
    print(portInfoLists);
    print(ports);
    if (ports.isNotEmpty) {
      // Initialising the port name here
      serialSingle ??= SerialPort('COM1', openNow: false);

      serialSingle?.open();
      print(serialSingle?.isOpened);

      String incomingData = '';
      String completeData = '';

      // while (port.isOpened) {
      // incomingData = '';
      serialSingle?.readBytes(8, timeout: Duration(seconds: 10)).then((value) {
        print('check vlaue ${utf8.decode(value)}');
        // if (utf8.decode(value) != '+' && utf8.decode(value) != ',') {
        incomingData += utf8.decode(value);
        print('check incoming data ${incomingData.length}');
        if (incomingData.length > 50) {
          String trimmedData = incomingData.substring(35);
          incomingData = trimmedData;
        }
        List<String> parts = incomingData.split(',');
        print('check parts $parts');
        final regex = RegExp(
            r'[+-]\d+(?:\.\d+)?'); // Matches numbers optionally followed by a decimal point
        List<double> extractedValues = [];
        for (var part in parts) {
          final match = regex.matchAsPrefix(part);
          if (match != null) {
            // Extract the captured group (the number) and convert to double
            extractedValues.add(double.parse(
                match.group(0)!.substring(1))); // Remove leading "+"
          }
        }

        if (extractedValues.length >= 2) {
          completeData = extractedValues[extractedValues.length - 2].toString();
        }
        print('check data and complete data $data, $completeData');
        setState(() {
          data = completeData;
        });
      });
    }
  }

  void _getStartDateWithTime(DateTime data) {
    startDate = data.millisecondsSinceEpoch;
  }

  String getSystemTime() {
    var now = DateTime.now();
    return DateFormat("hh:mm:ss a").format(now);
  }

  String getSystemDateAndTime(String inputFormat) {
    var now = DateTime.now();
    DateFormat.yMd().add_jm();
    var formattedMonthYear = DateFormat(inputFormat);
    String formatMonthYear = formattedMonthYear.format(now);
    return formatMonthYear;
  }

  @override
  Widget build(BuildContext context) {
    var appState = context.watch<MyAppState>();
    var pair = appState.current;
    IconData icon;
    if (appState.favorites.contains(pair)) {
      icon = Icons.favorite;
    } else {
      icon = Icons.favorite_border;
    }

    return Container(
      padding: const EdgeInsets.fromLTRB(40, 20, 20, 40),
      color: Colors.white,
      child: Row(
        // mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          Column(
            children: <Widget>[
              const SizedBox(
                  width: 500,
                  child: Text(
                    'Setup',
                    textAlign: TextAlign.left,
                    style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                  ))
            ],
          ),
          const SizedBox(height: 10),
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              (!startReadingFlag)
                  ? (ElevatedButton.icon(
                      onPressed: () {
                        _getPortsAndOpen();
                        setState(() {
                          startReadingFlag = true;
                          startTime = getSystemTime();
                        });
                        // appState.toggleFavorite();
                      },
                      icon: const Icon(Icons.play_arrow),
                      style: const ButtonStyle(
                          foregroundColor:
                              WidgetStatePropertyAll<Color>(Colors.white),
                          backgroundColor: WidgetStatePropertyAll<Color>(
                              Color.fromRGBO(0, 145, 255, 1))),
                      label: const Text('START READING',
                          style: TextStyle(color: Colors.white)),
                    ))
                  : (ElevatedButton.icon(
                      onPressed: () {
                        _getPortsAndClose();
                        setState(() {
                          startReadingFlag = false;
                          endTime = getSystemTime();
                        });
                      },
                      icon: const Icon(Icons.stop),
                      label: const Text('STOP READING',
                          style: TextStyle(color: Colors.white)),
                      style: const ButtonStyle(
                          foregroundColor:
                              WidgetStatePropertyAll<Color>(Colors.white),
                          backgroundColor: WidgetStatePropertyAll<Color>(
                              Color.fromRGBO(255, 111, 0, 1))),
                    )),
            ],
          ),
          const SizedBox(width: 100),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const SizedBox(
                  child: Text(
                'Readings',
                textAlign: TextAlign.left,
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
              )),
              Padding(
                padding: const EdgeInsets.only(top: 20, bottom: 20),
                child: TimeCard(
                    startTime: startTime,
                    startDateWithTime: _getStartDateWithTime,
                    endTime: endTime,
                    startReadingFlag: startReadingFlag),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Padding(
                      padding: const EdgeInsets.only(right: 20),
                      child: SizedBox(
                        height: 220,
                        width: 230,
                        child: BigCard(pair: pair, data: data),
                      ))
                ],
              ),
            ],
          ),
        ],
      ),
    );
  }
}

class BigCard extends StatelessWidget {
  const BigCard({super.key, required this.pair, required this.data});

  final WordPair pair;
  final String data;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final style = theme.textTheme.displayMedium!
        .copyWith(color: theme.colorScheme.onSecondary, fontSize: 50);

    return Card(
      color: theme.colorScheme.secondary,
      child: Center(
        child: Text(
          data,
          style: const TextStyle(fontSize: 30, color: Colors.white),
          semanticsLabel: data,
        ),
      ),
    );
  }
}

class TimeCard extends StatelessWidget {
  const TimeCard(
      {super.key,
      required this.startTime,
      required this.endTime,
      required this.startDateWithTime,
      required this.startReadingFlag});

  final String startTime;
  final String endTime;
  final void Function(DateTime) startDateWithTime;
  final bool startReadingFlag;

  String getSystemTime() {
    var now = DateTime.now();
    // DateFormat.yMd().add_jm().format(now)
    startDateWithTime(now);
    return DateFormat("hh:mm:ss a").format(now);
  }

  String getSystemDate(String inputFormat) {
    var now = DateTime.now();
    var formattedMonthYear = DateFormat(inputFormat);
    String formatMonthYear = formattedMonthYear.format(now);
    return formatMonthYear;
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Card(
        color: theme.colorScheme.secondary,
        child: Padding(
          padding: const EdgeInsets.all(15),
          child: Row(
            children: [
              Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Text(getSystemDate('d'),
                      style:
                          const TextStyle(fontSize: 30, color: Colors.white)),
                  Text(getSystemDate('MMMM y'),
                      style: const TextStyle(fontSize: 20, color: Colors.white))
                ],
              ),
              const SizedBox(width: 80),
              Column(
                children: [
                  const Text('Start time',
                      style: TextStyle(fontSize: 15, color: Colors.white)),
                  SizedBox(
                      width: 130,
                      height: 50,
                      child: Card(
                          child: Center(
                              child: Text(
                        startTime,
                        style: const TextStyle(
                            color: Color(0xff2d386b),
                            fontSize: 16,
                            fontWeight: FontWeight.w700),
                      ))))
                ],
              ),
              const SizedBox(width: 20),
              Column(
                children: [
                  const Text('End time',
                      style: TextStyle(fontSize: 15, color: Colors.white)),
                  SizedBox(
                      width: 130,
                      height: 50,
                      child: Card(
                          child: Center(
                              child:
                                  (startReadingFlag == true && startTime != '')
                                      ? TimerBuilder.periodic(
                                          const Duration(seconds: 1),
                                          builder: (context) {
                                          return Text(
                                            getSystemTime(),
                                            style: const TextStyle(
                                                color: Color(0xff2d386b),
                                                fontSize: 16,
                                                fontWeight: FontWeight.w700),
                                          );
                                        })
                                      : (startTime == '' &&
                                              startReadingFlag == false)
                                          ? (null)
                                          : Text(
                                              endTime,
                                              style: const TextStyle(
                                                  color: Color(0xff2d386b),
                                                  fontSize: 16,
                                                  fontWeight: FontWeight.w700),
                                            ))))
                ],
              ),
            ],
          ),
        ));
  }
}

The version 2.x lib now has three re-design API. readBytes is similar with readOnListen. But it has a timeout parameter.

print(await port.readBytesUntil(Uint8List.fromList("T".codeUnits))); /// '\0' is not included
/// or
var read = port.readBytes(18, timeout: Duration(milliseconds: 10))..then((onValue) => print(onValue));
var result = await read;
print(result);
/// or
var fixedBytesRead = port.readFixedSizeBytes(2)..then((onValue) => print(onValue));
await fixedBytesRead;
/// see more in small example
MANOJKUMAR26 commented 1 week ago

Hello @FengChendian , I am using the physical device (USB Serial port) to test the code. I upgraded the library version to serial_port_win32: ^2.1.3 and tried using the above code which you shared removing singleton code and I see the error still exist and this time I am not able to open the port from the first time only. image

FengChendian commented 1 week ago

@MANOJKUMAR26

EDIT: I notice your USB-SERIAL is CH340. So, I use a ch340 chip to test. And I found it may enter ERROR_IO_PENDING in some older drivers. I published version 2.1.5 to fix it.

image

MANOJKUMAR26 commented 1 week ago

Hello @FengChendian , Now I can open and close the port but when I open the port every time, I am getting below attached error. I am not able to read the data after I open the port, not sure if this is because of the below attached error. Previously readBytesOnListen function used to show the correct reading from the same device but now with this new read functions (tried all 3 read function) I am getting just array of zeros. image image Can you please suggest

FengChendian commented 1 week ago

It's a really weird bug……

So,


Some questions

@MANOJKUMAR26

MANOJKUMAR26 commented 1 week ago

Sharing you the driver version for the first error: image image

Also Sorry, As you suggested It was my mistake on updating the baud rate. I have updated it to 9600 and tried to use readBytes function but I am getting only one value. Actually the device value keeps changing and all the changing of numbers should be visible on screen but It is sending only one initial value. I tried using print(await serialSingle!.readBytesUntil(Uint8List.fromList("T".codeUnits))); function also but I am not getting any data using this function and when I close the port I am getting below error for readBytesUntil Function. image

FengChendian commented 1 week ago

@MANOJKUMAR26

  1. You use readBytes once, so it reads once. It should be true behaviour. If you want to read loop, you can use code like:
    for (int i=0; i < 100; i++) {
    var a = await readBytes();
    /// some things
    }

It is a basic loop read. Also, you can use stream like:

Stream<Uint8List> process() async* {
  for (int i=0; i < 100; i++) {
  var a = await readBytes();
  yield a;
  }
}

https://dart.dev/libraries/async/creating-streams

In version 1.x, you can use readOnListen to loop read. But it can't be directly controlled by users. So, I remove it in 2.x.

BTW, if you use readBytes many times and you read same things. You need to consider that your device sends wrong data.

  1. I think ClearCommError is a reasonable exception.

You use readBytesUntil to read something, but return conditions is not satisfied for some reasons. Your read is still not completed. If you close the port, you will still read after close port (Maybe I should add some warning in close port and stop read). You need to ensure that all I/O function is done when you close it.

And maybe your device doesn't send string ends with 'T'. For example, it sends 'xx t' ends with 't'. They are different codes. Or device sends "Xxxxxxxxxxxxxxxxx" but doesn't contain "T", function will not be completed.