I'm using pyserial to open two ports, and then write to each what I read from the other. I then have a physical com port connected to one of these ports and a virtual com port connected to the other. The virtual com port is in turn connected to a second virtual com port to which my simulator connects:
Hardware device <> COM1 <> Python Script <> VCOM2 <> VCOM3 <> Simulator
I can see that the communication is correctly entering and exiting the com ports in my script but something isn't right since the the hardware is failing to communicate correctly with the simulator.
I have an old c app that I can run in place of the Python Script and this works correctly. However, it is really badly written and I've no real interest in fixing all its bugs. So I'm hoping I can replace this app with a python script. I eventually wish to log the data passing through the ports with a timestamp.
I am using the correct baud rate in both cases, however I seem to be missing something. Should I be transferring signals between each port, DTR for example? pyserial has these functions:
sendBreak(duration=0.25)
setBreak(level=True)
setRTS(level=True)
setDTR(level=True)
getCTS()
getDSR()
getRI()
getCD()
What signals am I interested in?
EDIT:
When I poll these values for each port:
getCTS(), getDSR(), getRI(), getCD()
I Get:
True, False, False, True COM1
False, False, False, False VCOM2
However, I see that CD becomes false sometimes. How do I transfer this out through VCOM2 or do I need to do this?
EDIT:
Here's my code. Once communication starts the script locks up and I need to restart my computer to release the port. I can't kill the associated python process on Windows 7...
import serial
class NewMonitor():
def __init__(self, com_port_1, com_port_2):
self.read_time_in_seconds = 0.1
self.serialPort1 = serial.Serial(com_port_1, 9600, timeout=self.read_time_in_seconds, rtscts=True, dsrdtr=True)
self.serialPort2 = serial.Serial(com_port_2, 9600, timeout=self.read_time_in_seconds, rtscts=True, dsrdtr=True)
try:
while True:
item = self.serialPort1.read()
self.serialPort2.write(item)
self.serialPort2.setRTS(self.serialPort1.getCTS())
self.serialPort2.setDTR(self.serialPort1.getDSR())
item = self.serialPort2.read()
self.serialPort1.write(item)
self.serialPort1.setRTS(self.serialPort2.getCTS())
self.serialPort1.setDTR(self.serialPort2.getDSR())
finally:
self.serialPort1.close()
self.serialPort2.close()
you cannot ignore the signals, especially if you use HW based flow-control. I assume that you at least have to link the following signals between both ports besides RX(read) and TX(write)
CTS -> RTS
DSR -> DTR
Comments about your code:
You defined a timeout, this means the IO function will block (even if it is only for a short amount of time). Consider setting it to 0
You call read without parameter. This will only read a single byte. Any reason why you do not want to read more at once? Would reduce the overhead.
Consider adding a condition to exit the while loop. At the moment the code will run until pyserial throws an exception.
Related
I am very new, learning Python specifically geared toward hardware (serial port and TCP/IP device) testing.
I have been trying to get PySerial based code to work and keep hitting roadblocks. Running Python 3.10.8 on Windows 10.
I worked through the 'import serial' problem (uninstalled and reinstalled Python); the serial.Serial problem (needed to add 'from serial import *). Now, it seems like all of the read syntax does not work. All I want to do at this point is open the port, read and print data - from here I will start working on which data I want).
Here is the code I am working with (this was found in a couple of places on the internet):
#test_sport
import serial
from serial import *
s = serial.Serial(port='COM9', baudrate=9600)
serial_string = ""
while(1):
# Wait until there is data waiting in the serial buffer
if(serialPort.in_waiting > 0):
# Read data out of the buffer until a carraige return / new line is found
serial_string = serial.readline()
# Print the contents of the serial data
print(serial_string.decode('Ascii'))
# Tell the device connected over the serial port that we recevied the data!
# The b at the beginning is used to indicate bytes!
#serialPort.write(b"Thank you for sending data \r\n")
Running this results in an error on serialPort.in_waiting (says serialPort not defined) if I change that to serial.in_waiting (says serial has no attribute 'in_waiting' (PySerial API site says this is correct(?). I've also tried simple commands like serial.read(), serial.readline(), ser.read(), etc. All fail for attributes.
Is the PySerial documentation online current? Does anyone know where to find basic serial port examples?
Thank you!
I'm having a weird issue with pyserial, using Python 3.6.9, running under WSL Ubuntu 18.4.2 LTS
I've set up a simple function to send GCODE commands to a serial port:
def gcode_send(data):
print("Sending: " + data.strip())
data = data.strip() + "\n" # Strip all EOL characters for consistency
s.write(data.encode()) # Send g-code block to grbl
grbl_out = s.readline().decode().strip()
print(grbl_out)
It sort of works, but every command I send is 'held' until the next is sent.
e.g.
I send G0 X0 > the device doesn't react
I send G0 X1 > the device reacts to G0 X0
I send G1 X0 > the device reacts to G0 X1
and so on...
My setup code is:
s = serial.Serial(com, 115200)
s.write("\r\n\r\n".encode()) # Wake up grbl
time.sleep(2) # Wait for grbl to initialize
s.flushInput() # Flush startup text in serial input
I can work around the delay for now, but it's quite annoying and I can't find anyone else experiencing the same. Any idea what could be causing this?
There might be a lot of problems here, but rest assured that the pyserial is not causing it. It uses the underlying OS's API to communicate with the UART driver. That being said you first have to test your code with real Linux to see whether WSL is causing it. I.e. whether a Linux and Windows UART buffers are correctly synced.
I am sorry that I cannot tell whether a problem is in your code or not because I do not know the device you are using, so I cannot guess what is happening on its end of communication channel. Have in mind that Windows alone can act weirdly in best of circumstances, so, prepare yourself for some frustrations here. Check your motherboard or USB2Serial converter drivers or whatever hw you are using.
Next thing, you should know that sometimes, communication gets confusing if timeouts aren't set. Why? Nobody really knows. So try setting timeouts. Check whether you need software Xon/Xoff turned on or not, and other RS232 parameters that might be required by the device you are communicating with.
Also, see what is going on with s.readline(), I wouldn't personally use it. Timeouts might help or you can use s.read(1024) with timeouts. I do not remember right now, but see whether pyserial supports asynchronous communication. If it does, you can try using it instead of standard blocking mode.
Also, check whether you have to forcefully flush the serial buffer after s.write() or add a sleep after it. It might happen that the device doesn't get the message but the read request is activated. As the device didn't receive the command it doesn't respond. After you send another command, IO buffer is flushed and the previous one is delivered and so forth. Serial communication is fun, but when it hits a snag it can be a real P in the A, believe me.
Ow, a P.S. Check whether the device sends "\r\n\r\n" or "\r\n" only, or "\r" or "\n" in response. s.readline() might get confused. For a start, try putting there two s.readline()s one after another and print out each output. If the device sends double EOL then the one s.readline() is stopping on the empty line and your program receives an empty response, when you send another command s.readline() goes through the buffer and returns a full line that is already there but not read before.
Here it goes. The code promissed in the comment. Big portions of it removed and error checks too.
It is a typing terminal for using PyS60 Python console on Nokia smartphones in the Symbian series via bluetooth. Works fantastically.
from serial import *
from thread import start_new_thread as thread
from time import sleep
import sys, os
# Original code works on Linux too
# The following code for gettin one character from stdin without echoing it on terminal
# has its Linux complement using tricks from Python's stdlib getpass.py module
# I.e. put the terminal in non-blocking mode, turn off echoing and use sys.stdin.read(1)
# Here is Win code only (for brevity):
import msvcrt
def getchar ():
return msvcrt.getch()
def pause ():
raw_input("\nPress enter to continue . . .")
port = raw_input("Portname: ")
if os.name=="nt":
nport = ""
for x in port:
if x.isdigit(): nport += x
port = int(nport)-1
try:
s = Serial(port, 9600)
except:
print >> sys.stderr, "Cannot open the port!\nThe program will be closed."
pause()
sys.exit(1)
print "Port ready!"
running = 1
def reader():
while running:
try:
msg = s.read()
# If timeout is set
while msg=="":
msg = s.read()
sys.stdout.write(msg)
except: sleep(0.001)
thread(reader,())
while 1:
try: c = getchar()
except Exception, e:
running = 0
print >> sys.stderr, e
s.write('\r\n\x04')
break
if c=='\003' or c=='\x04':
running = 0
s.write('\r\n\x04')
break
s.write(c)
s.close()
pause()
I want to access the serial port using Crystal lang.
I have following code in python. I want to write the equivalent Crystal-lang code for a pet project.
import serial
def readSerData():
s = ser.readline()
if s:
print(s)
result = something(s) #do other stuff
return result
if __name__ == '__main__':
ser = serial.Serial("/dev/ttyUSB0", 9600)
while True:
data = readSerData()
#do something with data
I couldn't find any library for accessing the serial port.
What is the proper way for accessing serial port in crystal-lang?
Thanks in advance.
It is easier to answer this question in multiple parts to really cover it all:
Q: How do I access a serial port on linux/bsd?
A: Open it as a file. On linux/bsd a serial connection is established the moment a device is plugged in, and is then listed somewhere under /dev/ (these days, usually as /dev/ttyUSB0). In order to access this connection you simply open it like you would a regular file. Sometimes this is actually good enough to start communicating with the device as modern hardware typically works with all baud rates and default flags.
Q: How do I configure a serial/tty device on linux/bsd?
A: Set termios flags on the file. If you do need to configure your connection to set things like baud rate, IXON/IXOFF etc, you can do it before even running your program using stty if it is available. Eg. to set the baud rate you could run: stty -F /dev/ttyUSB0 9600. And after this is set up you can just open it as a file and start using it.
You can spawn stty from crystal using Process.run if you wanted an easy way to configure the device from your app. I would probably recommend this approach over the next solution..
Q: How do I set termios flags from crystal, without using stty?
A: Use the termios posix functions directly.
Crystal actually provides FileDescriptor handles with a few common termios settings such as cooked, which means it has minimal termios bindings already. We can start by using the existing code for our inspiration:
require "termios" # See above link for contents
#Open the file
serial_file = File.open("/dev/ttyACM0")
raise "Oh no, not a TTY" unless serial_file.tty?
# Fetch the unix FD. It's just a number.
fd = serial_file.fd
# Fetch the file's existing TTY flags
raise "Can't access TTY?" unless LibC.tcgetattr(fd, out mode) == 0
# `mode` now contains a termios struct. Let's enable, umm.. ISTRIP and IXON
mode.c_iflag |= (Termios::InputMode::ISTRIP | Termios::InputMode::IXON).value
# Let's turn off IXOFF too.
mode.c_iflag &= ~Termios::InputMode::IXOFF.value
# Unfun discovery: Termios doesn't have cfset[io]speed available
# Let's add them so changing baud isn't so difficult.
lib LibC
fun cfsetispeed(termios_p : Termios*, speed : SpeedT) : Int
fun cfsetospeed(termios_p : Termios*, speed : SpeedT) : Int
end
# Use the above funcs to set the ispeed and ospeed to your nominated baud rate.
LibC.cfsetispeed(pointerof(mode), Termios::BaudRate::B9600)
LibC.cfsetospeed(pointerof(mode), Termios::BaudRate::B9600)
# Write your changes to the FD.
LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode))
# Done! Your serial_file handle is ready to use.
To set any other flags, refer to the termios manual, or this nice serial guide I just found.
Q: Is there a library to do all this for me?
A: No :( . Not that I can see, but it would be great if someone made it. It's probably not much work for someone to make one if they had a vested interest :)
I noticed with my board from DIY drones a strange behavior when I use my custom firmware.
Here is an example function which is called in my firmware running on an Arduino board:
void send_attitude(float roll, float pitch, float yaw) {
hal.console->printf("{\"type\":\"sens_attitude\",\"roll\":%.4f,\"pitch\":%.4f,\"yaw\":%.4f}\n",
roll, pitch, yaw);
}
As you can see, the code just writing a message in the serial port set in setup (hal.uartA).
I call this function every 0.5s:
inline void medium_loop() {
static int timer = 0;
int time = hal.scheduler->millis() - timer;
// send every 0.5 s
if(time > 500) {
send_attitude(OUT_PIT, OUT_ROL, OUT_YAW);
timer = hal.scheduler->millis();
}
}
Now to the strange thing. If I use the serial monitor or read the board with another program or script everything is fine. Every 0.5s the proper LED is blinking and message is shown. But if I don't read it out, after appr. 10s the LED is flushing up continuously and no connection/communication is possible anymore. I have to unplug the board then. The same behavior is observed the other way round. If I send to my board over serial port (in my case USB) and don't flush the input buffer, the LED is flushing up continuously and I get a timeout. The following code works:
def send_data(line):
# calc checksum
chk = chksum(line)
# concatenate msg and chksum
output = "%s*%x\r\n" % (line, chk)
try:
bytes = ser.write(output)
except serial.SerialTimeoutException as e:
logging.error("Write timeout on serial port '{}': {}".format(com_port, e))
# Flush input buffer, if there is still some unprocessed data left
# Otherwise the APM 2.5 control boards stucks after some command
ser.flush() # Try to send old message
ser.flushInput() # Delete what is still inside the buffer
If I comment out this line:
ser.flushInput() # Delete what is still inside the buffer
I don't use more settings then this.
I get (depending on the message interval) a timeout sooner or later. In my case I send every 20ms a signal which results in a timeout after ~10s. Also dependent on the length of message. Bigger messages cause it faster than smaller ones.
My settings are shown in the following snippets. Client side python code:
com_port = '/dev/ttyACM0'
baud_rate = '115200'
try:
ser = serial.Serial(com_port, baud_rate, timeout=0.1, writeTimeout=0.1, rtscts=1)
The if these timeouts happen, then I also get one if I set the timeout to something like 2s. In my case I need a very low latency, which is indeed possible if I keep reading and flushing. Firmware code from my Arduino:
void setup() {
// Set baud rate when connected to RPi
hal.uartA->begin(115200);
hal.console->printf("Setup device ..\n");
// Followed by motor, compass, barometer initialization
My questions are:
What exactly happens with my board?
Why it is not reacting anymore if I just write in my serial port without reading or flushing the buffer?
Is it really a buffer or driver problem associated with this strange behavior and is this problem related to all Arduino boards or maybe just mine APM 2.5 from DIY drones?
Last but not least: I was finding no functions in the library which are targeting such problems. Are there maybe any I don't know?
The complete source code is #google code: https://code.google.com/p/rpicopter/source/browse/
What board are you using and what processor does it have? My guess would be that your board is based on the ATmega32U4, or some other microcontroller that has a built-in USB module. If so, I have seen similar behavior before here is what I think is happening:
There is a buffer on your microcontroller to hold serial data going to the computer. There is a buffer in the computer's USB serial driver to hold serial received from the chip. Since you are not reading bytes from the COM port, the buffer on the computer will fill up. Once the buffer on the computer fills up, it stops requesting data from the microcontroller. Therefore, the buffer on the microcontroller will eventually fill up.
Once the microcontroller's buffer is full, how do you expect printf command to behave? For simplicity, the printf you are using is probably designed to just wait in a blocking loop until buffer space is available and then send the next character, until the message is done. Since buffer space will never be available, your program gets stuck in an infinite loop.
A better strategy would be to check to see if enough buffer space is available before calling printf. The code might look something like this:
if(console_buffer_space() > 80)
{
hal.console->printf(...);
}
I don't know if this is possible in the DIY drones firmware, and I don't know if the max buffer space can actually ever reach 80, so you will have to research this a bit.
I don't understand the use of:
ser.flush() # Try to send old message
ser.flushInput() # Delete what is still inside the buffer
Lets say your device is connected to PC and the python code is writing the (line, chk):
ser.flush() - why are you using it?
ser.flushInput() - will "delete" the Serial input buffer at the PC
It looks like other people have the same problem. And thanks to the Mod-Braniac who deleted my minimal example. My bet is, that's a problem with Arduino USB controller chip or the firmware on it.
I have one peripheral device (say hardware circuit with microcontroller). I have to iput some commands to this peripheral device via serial communication. These commands are embedded into a python script.
I am using USB-Serial cable to connect peripheral device to PC.
Now I have to write the code in pyserial so that PC will automatically detect the com port on which Peripheral device is connected and connects the devide with PC successfully.(loop back can be possible)
Currently I am using following Code in Pyserial. I have explicitely mentioned that Peripheral is connected to PC on COM1 ---
try:
self.ser = serial.Serial(0)
#self.ser.port='/dev/ttyS1'
self.ser.baudrate = 9600
self.ser.bytesize = serial.EIGHTBITS
self.ser.parity = serial.PARITY_NONE
self.ser.stopbits = serial.STOPBITS_ONE
self.ser.timeout = 1
self.ser.xonxoff = False #disable software flow control
self.ser.rtscts = False #disable hardware (RTS/CTS) flow control
self.ser.dsrdtr = False #disable hardware (DSR/DTR) flow control
self.ser.writeTimeout = 2 #timeout for write
except Exception, e:
print "error open serial port: " + str(e)
Please let me know that how can a COM port is automatically detected and gets connect afterwards?
This is a common issue and can be solved by checking for a specific return code (usually an identification string) from the peripheral device. Here's an example, using pyserial:
from serial.tools import list_ports
def locate_port():
"""Attempt to locate the serial port to which the device
is connected."""
status_request_string = 'OI;' # Output information
expected_response = 'DISPENSEMATE'
device_port = None
for port_name, port_desc, hw_id in list_ports.comports():
with serial.Serial(port=port_name, **device_serial_settings) as ser:
ser.write(status_request_string)
if ser.readline().startswith(expected_response):
device_port = port_name
break
if not device_port:
raise UserWarning('Could not find a serial port belonging to '
'the asymtek dispensemate.')
return device_port
Usually, the manual of the device you're communicating with has at least one command that does not change the state of the device, but merely echoes back your last line or returns its configuration, hardware ROM version or simply its name. It is this response (and the command that requests it), that you 'll need to fill in for expected_response and status_request_string respectively. The device_serial_settings is a dictionary that contains the parameters such as baudrate and parity bits; everything needed to connect properly to the device, except for its name.
As you can see, the code above was written for an Asymtek Dispensemate (an old one too and thus difficult to get support for).
If you call that function, you can use its return value to simply connect to the device:
port = locate_port()
my_device = serial.Serial(port, **device_serial_settings)
There is one caveat though: if the computer is connected to several serial devices that are all powered on, it is possible that you send an illegal command to the other devices. In the best case, they simply reply with an error code and their state will be unaffected, but the command could also make changes to these devices, so check all other peripherals for their dictionaries of "allowed opcodes".