Using pyserial on a Raspberry Pi to read Arduino output - WebDVM part 2
This series describes how to build a simple web-based DVM (digital volt meter) that can display up to six voltages in a web browser.
It's a really useful hack if you need to measure several voltages at once.
In part 1 you saw how to send analog voltage data over a serial link to a Raspberry Pi. For testing purposes you displayed the data in a terminal window on the Pi.
In this part you will use read that data using Python on the Pi and print the data to standard output.
In the next part you'll turn your Python script into a dynamic application that displays the 6 voltages in a browser, updating the web page as the values change.
The code (available on github) uses the pyserial package. It's a simple approach, but there are a few issues to find your way around. You'll see the problems and solutions below.
pyserial is a Python package that allows Python programs to read and write data using serial ports. You can install it via pip.
pip3 install pyserial
When you connect the Arduino to the Pi via a USB cable, the Arduino connection appears as a serial port.
That's where the first problem bit me.
For part one of this series I used a Raspberry Pi zero with an Arduino Uno.
When I connected them the Arduino connection showed up as device /dev/ttyACM0.
For part two, I decided to use an Arduino Nano instead of the Uno. The Nano is smaller and cheaper than the Uno. Like the Uno it has six analog ports, so it looked ideal.
When I ran the software it fell over.
I did a quick check listing the teletype devices in /dev/tty*
There was no /dev/ttyACM0 but there was another new device: /dev/ttyUSB0.
I'd forgotten that the Nano uses a different approach for its USB-to-serial converter, and the Pi sees it as a different device.
So the software now starts with a check to see which device is available.
Once the script has found which prot to use, pyserial reads from that device in much the same way as you would read from or write to a file.
Unlike a file, though, data will only be available to read once the Arduino has sent it! For that reason, the script wraps the serial connection in a buffer.
It also needs to convert the serial input from a series of bytes to a unicode string.
Unfortunately, there is no way to synchronise the Pi and Arduino when they first connect. This means it's possible that the Python program starts reading an invalid sequence of bytes, so the program needs to cope with an error when the bytes are converted to a string.
Here's the code that does all that:
Next the program checks to see that a non-empty line was actually received, prints it to standard output, and flushes the newly printed information. That last step is necessary to make sure that new data is printed out as soon as it has been received. That will become vital in part three of this series.
Part three will describe how to make the anaolg data available in a browser that updates in real time. While you're waiting you can take a look at the code on github.
It's a really useful hack if you need to measure several voltages at once.
In part 1 you saw how to send analog voltage data over a serial link to a Raspberry Pi. For testing purposes you displayed the data in a terminal window on the Pi.
In this part you will use read that data using Python on the Pi and print the data to standard output.
In the next part you'll turn your Python script into a dynamic application that displays the 6 voltages in a browser, updating the web page as the values change.
The code (available on github) uses the pyserial package. It's a simple approach, but there are a few issues to find your way around. You'll see the problems and solutions below.
Pyserial
pyserial is a Python package that allows Python programs to read and write data using serial ports. You can install it via pip.
pip3 install pyserial
When you connect the Arduino to the Pi via a USB cable, the Arduino connection appears as a serial port.
That's where the first problem bit me.
For part one of this series I used a Raspberry Pi zero with an Arduino Uno.
When I connected them the Arduino connection showed up as device /dev/ttyACM0.
For part two, I decided to use an Arduino Nano instead of the Uno. The Nano is smaller and cheaper than the Uno. Like the Uno it has six analog ports, so it looked ideal.
When I ran the software it fell over.
I did a quick check listing the teletype devices in /dev/tty*
There was no /dev/ttyACM0 but there was another new device: /dev/ttyUSB0.
I'd forgotten that the Nano uses a different approach for its USB-to-serial converter, and the Pi sees it as a different device.
So the software now starts with a check to see which device is available.
devices = [port.device for port in list_ports.comports()] ports = [port for port in devices if port in ['/dev/ttyACM0','/dev/ttyUSB0']] if len(ports) != 1: raise Exception('cannot identify port to use') port = ports[0]
Once the script has found which prot to use, pyserial reads from that device in much the same way as you would read from or write to a file.
Unlike a file, though, data will only be available to read once the Arduino has sent it! For that reason, the script wraps the serial connection in a buffer.
It also needs to convert the serial input from a series of bytes to a unicode string.
Unfortunately, there is no way to synchronise the Pi and Arduino when they first connect. This means it's possible that the Python program starts reading an invalid sequence of bytes, so the program needs to cope with an error when the bytes are converted to a string.
Here's the code that does all that:
with serial.Serial(port, 9600, timeout=TIMEOUT_SECONDS) as ser: # We use a Bi-directional BufferedRWPair so people who copy + adapt can write as well as read sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser)) while True: try: line = sio.readline() except UnicodeDecodeError: continue # decode error - keep calm and carry on
Next the program checks to see that a non-empty line was actually received, prints it to standard output, and flushes the newly printed information. That last step is necessary to make sure that new data is printed out as soon as it has been received. That will become vital in part three of this series.
if len(line) > 0: print(line) sys.stdout.flush() # to avoid buffering, needed for websocketd
Part three will describe how to make the anaolg data available in a browser that updates in real time. While you're waiting you can take a look at the code on github.
regarding the naming of devices /tty* on linux machines, you can use udev tools to reason about manufacture meta data about what device was plugged in and statically bind a usb device to a tty name. This is useful as well if you have multiple usb devices plugging in and out of the system
ReplyDelete