本来《键盘手焊还不够?里面跑个Python更过瘾》的下一篇是要介绍Python版键盘实现的,但是!在CircuiPython代码编写过程中串口终端putty
用得不够称手,于是就写了个新的串口终端——terminal-s
,用了大概100行代码。相比putty
,terminal-s
不用我们去设备管理器中找串口号了,如果电脑就一个串口,就直接打开,如果有多个串口,就提供一个串口列表供选择,另外terminal-s
还可以在命令行中使用(比如VS Code的Terminal里面),而且是跨平台的。
串口终端在嵌入式开发里面会经常用得到,这里就先介绍一下这个terminal-s
,Python版键盘实现就留在下一篇吧(代码已经在GitHub上了)。
电脑里已装有python
和pip
的小伙伴可以快速体验一下,运行pip install terminal-s
安装,然后输入terminal-s
启动 。Windows中,可以用 Win + r 输入terminal-s
以单独窗口运行,设置串口参数也是支持的:
Usage: terminal-s [OPTIONS]
Options:
-p, --port TEXT serial port name
-b, --baudrate INTEGER set baud reate
--parity [N|E|O|S|M] set parity
-s, --stopbits INTEGER set stop bits
-l list serial ports
-h, --help Show this message and exit.
串口终端的功能,简单地说,就是转发用户的输入到串口设备和显示串口设备的输出。这里用了两个线程实现,主线程收、发串口数据,另一个线程用来读取用户输入。终端的数据通常是兼容VT100的,而各个系统内置的命令行环境都会兼容VT100,利用这一点,只需额外处理方向键、Home和End等特殊键即可。代码实现如下:
import os
os.system('title Terminal S')
from collections import deque
import threading
import sys
import colorama
import click
from getch import getch
import serial
from serial.tools import list_ports
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
@click.command(context_settings=CONTEXT_SETTINGS)
@click.option('-p', '--port', default=None, help='serial port name')
@click.option('-b', '--baudrate', default=115200, help='set baud reate')
@click.option('--parity', default='N', type=click.Choice(['N', 'E', 'O', 'S', 'M']), help='set parity')
@click.option('-s', '--stopbits', default=1, help='set stop bits')
@click.option('-l', is_flag=True, help='list serial ports')
def main(port, baudrate, parity, stopbits, l):
if port is None:
ports = list_ports.comports()
if not ports:
print('--- No serial port available ---')
return
if len(ports) == 1:
port = ports[0][0]
else:
print('--- Available Ports ----')
for i, v in enumerate(ports):
print('--- {}: {}'.format(i, v))
if l:
return
raw = input('--- Select port index: ')
try:
n = int(raw)
port = ports[n][0]
except:
return
try:
device = serial.Serial(port=port,
baudrate=baudrate,
bytesize=8,
parity=parity,
stopbits=stopbits,
timeout=0.1)
except:
print('--- Failed to open {} ---'.format(port))
return
print('--- Press Ctrl+] to quit ---')
queue = deque()
def read_input():
while device.is_open:
ch = getch()
# print(ch)
if ch == b'x1d': # 'ctrl + ]' to quit
break
if ch == b'x00' or ch == b'xe0': # arrow keys' escape sequences
ch2 = getch()
conv = { b'H': b'A', b'P': b'B', b'M': b'C', b'K': b'D' }
if ch2 in conv:
# Esc[
queue.append(b'x1b[' + conv[ch2])
else:
queue.append(ch + ch2)
else:
queue.append(ch)
colorama.init()
thread = threading.Thread(target=read_input)
thread.start()
while thread.is_alive():
try:
length = len(queue)
if length > 0:
device.write(b''.join(queue.popleft() for _ in range(length)))
line = device.readline()
if line:
print(line.decode(), end='', flush=True)
except IOError:
print('Device is disconnected')
break
except UnicodeDecodeError:
print([x for x in line])
device.close()
if __name__ == "__main__":
main()
还可以用pyinstaller
把代码打包成exe,这样在没有安装python
的电脑也可以跑:
git clone https://github.com/makerdiary/terminal-s
pip install pyinstaller
cd terminal-s
pyinstaller -F terminal_s/terminal.py
用pyinstaller
默认参数打包的exe包含tcl
、tkinter
等没有用到包,exe接近10MB了,而且启动也比较慢。可以把没有用到的包手动屏蔽掉,并配置可以放在文件terminal-s.spec
里:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['terminal_sterminal.py'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=['tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter', 'lib2to3', 'ssl', 'bz2', 'lzma', 'curses'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
excluded_binaries = ['libcrypto-1_1.dll']
a.binaries = TOC([x for x in a.binaries if x[0] not in excluded_binaries])
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='terminal-s',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True)
然后运行pyinstaller terminal-s.spec
,生成的exe在dist
目录,裁剪后大小为5.32MB,小了不少,不过相对C/C++写的还是要大很多,如果按这个思路用C重写一遍,可以非常小巧。
另外,还可以用GitHub Actions自动把代码发布到PyPI和自动调用pyinstaller
打包~