c# 串口 多线程_PuTTY不够好用,自己动手写个串口终端

45a69ffc-db15-eb11-8da9-e4434bdf6706.png

本来《键盘手焊还不够?里面跑个Python更过瘾》的下一篇是要介绍Python版键盘实现的,但是!在CircuiPython代码编写过程中串口终端putty用得不够称手,于是就写了个新的串口终端——terminal-s,用了大概100行代码。相比puttyterminal-s不用我们去设备管理器中找串口号了,如果电脑就一个串口,就直接打开,如果有多个串口,就提供一个串口列表供选择,另外terminal-s还可以在命令行中使用(比如VS Code的Terminal里面),而且是跨平台的。

串口终端在嵌入式开发里面会经常用得到,这里就先介绍一下这个terminal-s,Python版键盘实现就留在下一篇吧(代码已经在GitHub上了)。

电脑里已装有pythonpip 的小伙伴可以快速体验一下,运行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包含tcltkinter等没有用到包,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打包~

Terminal S​github.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值