|
#!/usr/bin/env python |
|
# -*- coding: utf-8 -*- |
|
|
|
from __future__ import division |
|
from __future__ import print_function |
|
from __future__ import unicode_literals |
|
|
|
"""Test which baud rates you can successfully communicate at using a given |
|
serial port (or USB-to-serial adapter) and serial device. |
|
|
|
To test an Arduino compatible, use in conjunction with ohmybaud.ino. |
|
""" |
|
|
|
import os |
|
import sys |
|
import serial |
|
from collections import namedtuple |
|
from math import ceil |
|
from serial.tools.list_ports import comports |
|
|
|
# How many seconds to test for per baud rate. |
|
TEST_DURATION_TARGET = 2.5 |
|
|
|
SAFE_BAUD_RATE = 9600 |
|
BAUD_RATES = ( |
|
2400, |
|
4800, |
|
7200, |
|
9600, |
|
14400, |
|
19200, |
|
38400, |
|
57600, |
|
115200, |
|
128000 |
|
) |
|
|
|
Capability = namedtuple('Capability', ('pc_to_device', 'device_to_pc')) |
|
|
|
def serial_without_dtr(port, *args, **kwargs): |
|
"""Construct a Serial object with DTR immediately disabled. |
|
On systems where pySerial supports this, this will prevent an Arduino from |
|
resetting when a serial connection is opened. On other systems, a hardware |
|
workaround (e.g. a 10 µF capacitor between RST and ground) is needed. |
|
""" |
|
ser = serial.Serial(None, *args, **kwargs) |
|
ser.port = port |
|
ser.dtr = 0 |
|
if port is not None: # To match what pySerial does. |
|
ser.open() |
|
return ser |
|
|
|
def single_exchange_works(port, baud_rate): |
|
with serial_without_dtr(port, baud_rate, timeout=1) as ser: |
|
try: |
|
ser.write(b'\xA5') |
|
return ser.read(1) == b'\xA5' |
|
except serial.SerialTimeoutException: |
|
return False |
|
|
|
def send_start_command(port, baud_rate): |
|
with serial_without_dtr(port, baud_rate, timeout=1) as ser: |
|
ser.write(b'\x01') |
|
assert ser.read(1) == b'\x01' |
|
|
|
def test_baud_rate(port, baud_rate, test_length): |
|
timeout = ((1 / (baud_rate / 8)) * test_length) * 1.5 |
|
bytes_to_write = bytearray(range(0x100)) |
|
bytes_to_write = bytes_to_write * int(ceil((test_length / 0x100))) |
|
bytes_to_write = bytes_to_write[:test_length] |
|
|
|
pc_to_device = True |
|
device_to_pc = True |
|
with serial_without_dtr(port, baud_rate, timeout=timeout) as ser: |
|
try: |
|
ser.write(bytes_to_write) |
|
except serial.SerialTimeoutException: |
|
pc_to_device = False |
|
|
|
try: |
|
received_bytes = ser.read(test_length) |
|
except serial.SerialTimeoutException: |
|
device_to_pc = False |
|
else: |
|
i = 0 |
|
for n in received_bytes: |
|
if n != i: |
|
device_to_pc = False |
|
break |
|
i += 1 |
|
i %= 0x100 |
|
|
|
with serial_without_dtr(port, SAFE_BAUD_RATE, timeout=1) as ser: |
|
ser.write(b'\x00') |
|
error_code = ser.read(1)[0] |
|
pc_to_device = error_code == 0 |
|
|
|
return Capability(pc_to_device, device_to_pc) |
|
|
|
def main(*args): |
|
SCRIPT_NAME = os.path.split(__file__)[-1] |
|
try: |
|
port = args[0] |
|
except IndexError: |
|
print("Usage: {} <serial port>".format(SCRIPT_NAME)) |
|
print("Examples:") |
|
print(" {} /dev/ttyUSB0".format(SCRIPT_NAME)) |
|
print(" {} COM5".format(SCRIPT_NAME)) |
|
print() |
|
print("Currently available serial ports:") |
|
for port in sorted(comports()): |
|
description = port.description if port.description \ |
|
and port.description != 'n/a' else None |
|
if description is not None: |
|
parenthetical_index = description.find(' ({})'.format(port.device)) |
|
if parenthetical_index != -1: |
|
description = description[:parenthetical_index] |
|
print(" {}{}".format( |
|
port.device, |
|
" ({})".format(description) if description else '' |
|
)) |
|
return 1 |
|
|
|
if not single_exchange_works(port, SAFE_BAUD_RATE): |
|
print("The \"known working\" baud rate of {} does not work, so tests cannot be performed.".format(SAFE_BAUD_RATE), file=sys.stderr) |
|
return 1 |
|
|
|
send_start_command(port, SAFE_BAUD_RATE) |
|
|
|
for rate in BAUD_RATES: |
|
# Divided by two since the test is done in both directions. |
|
test_length = (TEST_DURATION_TARGET / 2) / (1 / (rate / 8)) |
|
test_length = int(ceil(test_length)) |
|
try: |
|
print("{} baud: ".format(rate), end='') |
|
sys.stdout.flush() |
|
pc_to_device, device_to_pc = test_baud_rate(port, rate, test_length) |
|
if pc_to_device and device_to_pc: |
|
print("OK!") |
|
elif pc_to_device and not device_to_pc: |
|
print("Error receiving from device.") |
|
elif not pc_to_device and device_to_pc: |
|
print("Error transmitting to device.") |
|
else: |
|
print("Couldn't transmit either way.") |
|
except OSError: |
|
print("Could not open port {}. It may be in use... or just non-existent.".format(port), file=sys.stderr) |
|
|
|
return 0 |
|
|
|
if __name__ == '__main__': |
|
sys.exit(main(*sys.argv[1:])) |