I have a temperature measurement device with which i can communicate using pyserial module on COM port. I can read and write from and to the device from USB interface.
Now the device hangs sometimes and i can no longer read or write values to the device. The python script always hangs on the following initialization function
serial.Serial(port='COM13', baudrate=9600)
and shows no response until i have to kill the cmd terminal. The traceback after killing the terminal is as follows.
Traceback (most recent call last):
File "C:\scripts\test.py", line 4, in <module>
serial.Serial('COM5', baudrate=9600)
File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\site-packages\serial\serialwin32.py", line 33, in __init__
super(Serial, self).__init__(*args, **kwargs)
File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\site-packages\serial\serialutil.py", line 244, in __init__
self.open()
File "C:\Users\User1\AppData\Local\Programs\Python\Python310\lib\site-packages\serial\serialwin32.py", line 54, in open
self._port_handle = win32.CreateFile(
KeyboardInterrupt
In my opinion a hanged device is like a powered off device. I only want to raise exception if it cannot initialize the connection after 3 seconds. I have read about timeout, but it also creates no effect.
Freezing inside the serial constructor means that something is wrong on the usb-to-serial level, while the serial backend might still function properly. Problem with driver or hardware can't have a general solution.
Some thoughts:
Terminating the process, while a read operation is ongoing may leave the serial port handle in an open state, preventing further connections. Thus, using the read timeout is a must.
serial.Serial("COM3", 115200, timeout=1)
Sometimes converter may stall inside the driver, rendering timeouts useless, and only disconnecting the deivce would help. We've encountered this behavior with cheap chineese adapters.
You may switch to non-blocking operations, using in_waiting field, which contains the number of bytes in the receive buffer. Calling read() with this value will cause it to return almost immediately. Of course you'll have to implement manual message assembly.
Related
I am using pySerial to communicate to a microcontroller over USB, Most of the communication is initiated by the desktop python script which sends a command packet and waits for reply.
But there is also a alert packet that may be sent by the microcontroller without a command from the python script. In this case I need to monitor the read stream for any alerts.
For handling alerts, I dedicate a seperate process to call readline() and loop around it, like so:
def serialMonitor(self):
while not self.stopMonitor:
self.lock.acquire()
message = self.stream.readline()
self.lock.release()
self.callback(message)
inside a class. The function is then started in a seperate process by
self.monitor = multiprocessing.Process(target = SerialManager.serialMonitor, args = [self])
Whenever a command packet is send, the command function needs to take back control of the stream, for which it must interrupt the readline() call which is in blocking. How do I interrupt the readline() call? Is there any way to terminate a process safely?
You can terminate a multiprocessing process with .terminate(). Is this safe? Probably it's alright for a readline case.
However, this is not how I would handle things here. As I read your scenario, there are two possibilities:
MCU initiates alert package
Computer sends data to MCU (and MCU perhaps responds)
I assume the MCU will not send an alert package whilst an exchange is going on initiated by the computer.
So I would just initiate the serial object with a small timeout, and leave it in a loop when I'm not using it. My overall flow would go like this:
ser = Serial(PORT, timeout=1)
response = None
command_to_send = None
running = True
while running: # event loop
while running and not command_to_send and not line:
try:
line = ser.readline()
except SerialTimeoutException:
pass
if not command_to_send:
process_mcu_alert(line)
else:
send_command(command_to_send)
command_to_send = None
response = ser.readline()
This is only a sketch, as it would need to be run in a thread or subprocess, since readline() is indeed blocking, so you need some thread-safe way of setting command_to_send and running (used to exit gracefully) and getting response, and you likely want to wrap all this state up in a class. The precise implementation of that depends upon what you are doing, but the principle is the same---have one loop which handles reading and writing to the serial port, have it timeout to respond relatively quickly (you can set a smaller timeout if you need to), and have it expose some interface you can handle.
Sadly to my knowledge python has no asyncio compatible serial library, otherwise that approach would seem neater.
I am making an application where a Raspberry Pi reads serial data from the /dev/rfcomm0 file. I have created a daemon to listen for incoming RFCOMM connections and launch the Python program responsible for reading the incoming serial data with execvp call. Upon daemon initialization, I close stdin, stdout, and stderr in accordance with the daemon tutorial I found on http://www.netzmafia.de/skripten/unix/linux-daemon-howto.html. Essentially this link says to do the following:
Fork off the parent process.
Change file mode mask (umask)
Open any logs for writing.
Create a unique Session ID (SID)
Change the current working directory to a safe place.
Close standard file descriptors.
Enter actual daemon code.
My daemon will not work unless I remove close(STDIN_FILENO) from my code. If I do not remove that code, I get the following error and I do not know how to fix it.
File "/home/BluetoothProject/RFCOMMOut.py", line 7, in <module>
gpio.setup(led_pin, gpio.OUT)
RuntimeError: Not running on a RPi!
Note: I was able to print my error by removing close(STDERR_FILNO)
I am new to python and am trying to run a prewritten program but keep recieving an error here is the whole error.
Traceback (most recent call last):
File "C:\source\python\pill.py", line 29, in <module>
ser.open()
File "C:\Python27\lib\site-packages\serial\serialwin32.py", line 66, in open
raise SerialException("could not open port %r: %r" % (self.portstr, ctypes.WinError()))
SerialException: could not open port 'COM7': WindowsError(5, 'Access is denied.')
I am the admin as well
You may have another program using the COM port, for example a test program like firmata_test.exe that our course work told use to use.
One course module told us to run that program, which is a Windows executable that opens the COM port and monitors the various pins on the board and lets us toggle their state High or Low. The next module told us to run some Python code in the IDLE code editor that would connect to the board and write to the pin state to turn an LED on and off. The Python code failed with the Access is denied message, because the COM port was already in use by firmata_test.exe, though the error message did not mention this specifically (because the Python code did not know why it could not open the port, only that it could not).
Close any other software such as firmata_test.exe that may be using the COM port and run your Python code again.
Is there a way to connect to an RFC2217 networked serial port with Twisted Python?
Pyserial seems to support it via the serial.serial_for_url("rfc2217://...") function. And they indicate that twisted uses pyserial for managing serial connections, however twisted.internet.serialport.SerialPort seems to expect a port name or number which suggests it is just passing this to the serial.Serial constructor.
I can use socat to create a PTY externally and pass the dev name to twisted which works fine, but I was wondering if I can bypass this step by using the pyserial support directly.
socat PTY,link=/dev/myport TCP:192.168.1.222:9001
Edit: The pyserial faq suggests this modification for instantiating serial objects:
try:
s = serial.serial_for_url(...)
except AttributeError:
s = serial.Serial(...)
Not sure if this helps though...
I have come to the conclusion that using Pyserial's RFC2217 support with Twisted Python is non-trivial. Pyserial's implementation of RFC2217, besides being currently experimental, uses threads to manage the socket connection which they state as being a problem for select based applications:
The current implementation starts a thread that keeps reading from the (internal) socket. The thread is managed automatically by the rfc2217.Serial port object on open()/close(). However it may be a problem for user applications that like to use select instead of threads.
It is fairly straight forward to subclass t.i.serialport.SerialPort and overwrite the _serialFactory method (which creates the pyserial object to be used for accessing the serial port)
class SerialPort(serialport.SerialPort):
def _serialFactory(self, dev, *args, **kwargs):
" pyserial recommends the following for supporting serial urls "
try:
return serial.serial_for_url(dev)
except AttributeError:
return serial.Serial(dev, *args, **kwargs)
However, the resulting object lacks a file descriptor and so the fileno() method (used internally by t.i._posixserialport) throws an exception.
--- <exception caught here> ---
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/twisted/internet/base.py", line 1204, in mainLoop
self.doIteration(t)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/twisted/internet/selectreactor.py", line 105, in doSelect
[], timeout)
File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/twisted/internet/_posixserialport.py", line 48, in fileno
return self._serial.fd
exceptions.AttributeError: 'Serial' object has no attribute 'fd'
The current workarounds are to either use socat as described in the question, or for the network serial server I'm using (Brainboxes ES-842) you can configure it in "Raw TCP" mode instead of "Telnet/RFC2217" mode and just use your existing protocol over a TCP connection (as long as you're not depending on flow control or other serial control lines and can use a predefined fixed baud rate).
so i have a motion sensor connected to an avr micro that is communicating with my python app via usb. im using pyserial to do the comm. during my script i have an infinate loop checking for data from the avr micro. before this loop i start a timer with signal.alarm() that will call a function to end a subprocess. when this alarm goes it interrupts the pyserial comm and the program exits completly. i get the error that pyserial read() is interrupted. is there any way around this issue. any help would be awesome
The problem is that your alarm will interrupt the read from the serial port, which isn't at all what you want.
It sounds like you probably want to break this into two threads that do work separately.
You are using alarm(), which send a signal, and pyserial, which does reads and writes to a serial port. When you are reading or writing to a device like that, and the SIGALRM signal is received, a read() or a write() call is interrupted so the signal can be handled.
As signals are handled in userspace, and reading and writing is actually handled by the kernel, this makes things rather ugly. This is a know wart of the way signals are handled, and dates back to the very early UNIX days.
Code that handles signals correctly in python may look like:
import errno
while True:
try:
data = read_operation()
except OSError, e:
if getattr(e, 'errno', errno.EINTR):
continue
raise
else:
break