1

Raspberry Pi 4 B pPython code basically does this:

fptr = open('/dev/ttyACMwa', 'rb')
... etc ...
fptr is used by AsyncReader
... etc ...

on shutdown:

loginf("before close")
TT1 = time.time()
fptr.close()
TT2 = time.time()
loginf("after  close took {} sec".format(TT2 - TT1))

problem: loginf reports 30 seconds (this is a driver so shutdown of service takes 30 seconds to one minute ... too long !!!!)

AsyncReader runs in a thread and this is the main part:

    def run(self):
        logdbg("start async reader for %s" % self.getName())
        self._running = True
        for line in iter(self._fd.readline, ''):
            line = line.rstrip()
            # all our files are binary                                                              
            line = line.decode()
            if line:
                self._queue.put(line)
            if not self._running:
                break

/dev/ttyACMwa is a symbolic link to one of the /dev/ttyACM0 ... /dev/ttyACM4 files. /dev/ttyACMwa is created by a udev rule. the device is an Arduino, an Adafruit itsy bitsy M4; MCU (microcontroller) is ATSAMD51; it is set to 9600 baud; there are no hubs; it is a straight connection from Raspberry Pi USB A port to micro USB port on Arduino.

2 Answers 2

1

Make sure your thread is not stuck in the readline call when you close the file. And not just set the _running flag to False, but actually make sure the thread joined back.

Perhaps a more practical advice is to use pyserial which already handles timeouts properly (using select).

0

Following advice of Dmitry Grigoryev, instead of just opening 'ttyACMx' use

_fptr_thpusb = serial.Serial(self._fname_thpusb, timeout);

Also do not read from the file descriptor _fptr_thpusb unless it has data as determined by

ready,_,_ = select.select([self._fd], [], [], timeout);

See code marked by "#solution"; this does not block because read always has data;

class AsyncReader(threading.Thread):
    def __init__(self, fd, queue1, label):
        threading.Thread.__init__(self)
        self._fd = fd
        self._queue = queue1
        self._running = False
        self.setDaemon(True)
        self.setName(label)

    def run(self):
        self._running = True
        # for line in iter(self._fd.readline, ''):                                                  
        while self._running:
            # added --------                                                                      
            timeout = 10
            # this is the solution
            ready,_,_ = select.select([self._fd], [], [], timeout)
            if not ready:
                continue
            line = self._fd.readline()
            # added --------                                                                       
            line = line.rstrip()
            # all our files are binary                                                              
            line = line.decode()
            if not line:
                continue
            self._queue.put(line)

    def stop_running(self):
        self._running = False


class ProcManager(object):
   def startup(self, fname_thpusb, cmd, path=None, ld_library_path=None):
            self._process = subprocess.Popen(cmd.split(' '),
                                             env=env,
                                             stdout=subprocess.PIPE,
                                             stderr=subprocess.PIPE)
            # self._fptr_thpusb = open(self._fname_thpusb, "rb")                                    
            timeout = 5
            # this is the solution
            self._fptr_thpusb = serial.Serial(self._fname_thpusb, timeout)
            self.thpusb_reader = AsyncReader(
                self._fptr_thpusb, self.merge_out_queue, "thp-thread")
            self.thpusb_reader.start()
            self.stdout_reader = AsyncReader(
                self._process.stdout, self.merge_out_queue, "stdout-thread")
            self.stdout_reader.start()
            self.stderr_reader = AsyncReader(
                self._process.stderr, self.stderr_queue, 'stderr-thread')
            self.stderr_reader.start()

      def shutdown(self):
        self._process.kill()
        # originally close() was before the shutdown of the readers                
        self.thpusb_reader.stop_running()
        self.thpusb_reader.join(0.5)
        self.stdout_reader.stop_running()
        self.stdout_reader.join(0.5)
        self.stderr_reader.stop_running()
        self.stderr_reader.join(0.5)
        # originally this was before the shutdown of the readers                   
        self._fptr_thpusb.close()
        self._process.stdout.close()
        self._process.stderr.close()
        if self._process.poll() is None:
            logerr('process did not respond to kill, shutting down anyway')
        self._process = None
        if self.thpusb_reader.is_alive():
            loginf('timed out waiting for %s' % self.thpusb_reader.getName())
        self.thpusb_reader = None
        if self.stdout_reader.is_alive():
            loginf('timed out waiting for %s' % self.stdout_reader.getName())
        self.stdout_reader = None
        if self.stderr_reader.is_alive():
            loginf('timed out waiting for %s' % self.stderr_reader.getName())
        self.stderr_reader = None
        loginf('shutdown complete')
    def get_stderr(self):
        lines = []
        while not self.stderr_queue.empty():
            # lines.append(self.stderr_queue.get().decode())                                        
            lines.append(self.stderr_queue.get())
        return lines
    def get_stdout(self):
        lines = []
        while self.running():
            try:
                # Fetch the output line. For it to be searched, Python 3                            
                # requires that it be decoded to unicode. Decoding does no                          
                # harm under Python 2:                                                              
                # line = self.merge_out_queue.get(True, 3).decode()                                 
                line = self.merge_out_queue.get(True, 3)
                m = ProcManager.TS.search(line)
                if m and lines:
                    yield lines
                    lines = []
                lines.append(line)
            except queue.Empty:
                yield lines
                lines = []
        yield lines
1
  • Please edit and put any additional info in the answer and not in the comments. Comments are ephemeral in nature and tends to get deleted after a while. Thanks. Jan 8, 2023 at 17:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.