如何使用固定的输入行编写Python终端应用程序?
我正在尝试编写一个终端应用程序,通过与Arduino微控制器交互。以下功能非常重要:如何使用固定的输入行编写Python终端应用程序?,python,terminal,spyder,curses,ansi-escape,Python,Terminal,Spyder,Curses,Ansi Escape,我正在尝试编写一个终端应用程序,通过与Arduino微控制器交互。以下功能非常重要: 将传入消息打印到命令行 允许用户向串行端口输入输出消息。在打印新的传入消息时,输入应该是可能的 原则上,这应该是可能的。但当用户开始打字时,我正在努力打印收到的消息 为简单起见,我编写了以下测试脚本,每秒模拟传入消息。发出的消息只会回显到带有前缀“>”的命令行: 在Spyder IDE中,结果非常完美: 但在iterm2(Mac OS)中,输出非常混乱: 因为我想在VisualStudio代码中使用这个
- 将传入消息打印到命令行
- 允许用户向串行端口输入输出消息。在打印新的传入消息时,输入应该是可能的
- 使用图书馆。这解决了我将文本打印到不同区域的问题。但我失去了无休止的滚动,因为诅咒定义了它自己的全屏窗口
- 使用移动光标。这可能是一个可行的解决方案,但我只是不想让它发挥作用。它总是破坏用户输入的底线。我可能需要调整时间,但我仍然没有做到这一点
- 使用不同的解释器。我已经尝试过Python和iPython,但没有成功。这可能是Spyder解释器中更微妙的设置
pyserial
,它是100%可靠的。键是userread\u until()
。我已经包括了我的包装器类以供说明。(当我没有Arduino时,还有一个模拟模式)
对!!我找到了一个解决方案:与的结合使您可以使用“一个上下文管理器,确保其中的打印语句不会破坏用户界面”来处理这个问题 以下是一个最低限度的工作示例:
#!/usr/bin/env python3
from prompt_toolkit import PromptSession
from prompt_toolkit.patch_stdout import patch_stdout
import asyncio
import time
async def echo():
while True:
print(time.time())
await asyncio.sleep(1)
async def read():
session = PromptSession()
while True:
with patch_stdout():
line = await session.prompt_async("> ")
print(line.upper())
loop = asyncio.get_event_loop()
loop.create_task(echo())
loop.create_task(read())
loop.run_forever()
我不确定这会有什么帮助。很抱歉我的问题可能有误导性,但阅读Arduino的文章不是我的问题。在我的最小示例中,我甚至生成了完全合成的消息。问题在于如何在不干扰当前未提交的用户输入的情况下显示
read_的结果,直到
。
import serial # pip install PySerial
from serial.tools import list_ports
import pty, os # for creating virtual serial interface
from serial import Serial
from typing import Optional
class SerialInterface:
# define constants which control how class works
FULLEMULATION=0
SERIALEMULATION=1
URLEMULATION=2
FULLSOLUTION=3
# define private class level variables
__emulate:int = FULLEMULATION
__ser:Serial
__port:str = ""
def __init__(self, emulate:int=FULLEMULATION, port:str="") -> None:
self.__buffer:list = []
self.__emulate = emulate
self.__port = port
#self.listports()
# setup connection to COM/serial port
# emulation sets up a virtual port, but this has not been working
if emulate == self.FULLSOLUTION:
self.__ser = serial.Serial(port, 9600)
elif emulate == self.SERIALEMULATION:
master, slave = pty.openpty()
serialport = os.ttyname(slave)
self.__ser = serial.Serial(port=serialport, baudrate=9600, timeout=1)
elif emulate == self.URLEMULATION:
self.__ser = serial.serial_for_url("loop://")
# useful to show COM/serial ports on a computer
@staticmethod
def listports() -> list:
for p in serial.tools.list_ports.comports():
print(p, p.device)
serialport = p.device
return serial.tools.list_ports.comports()
def read_until(self, expected:bytes=b'\n', size:Optional[int]=None) -> bytes:
if self.__emulate == self.FULLEMULATION:
return self.__buffer.pop()
else:
return self.__ser.read_until(expected, size)
# note it is important to have \n on end of every write to allow data to be read item by item
def write(self, bytes:bytes=b'') -> None:
if self.__emulate == self.FULLEMULATION:
self.__buffer.append(bytes)
else:
self.__ser.write(bytes)
def dataAvail(self) -> bool:
if self.__emulate == self.FULLEMULATION:
return len(self.__buffer) > 0
else:
return self.__ser.inWaiting() > 0
def close(self) -> None:
self.__ser.close()
def mode(self) -> int:
return self.__emulate
#!/usr/bin/env python3
from prompt_toolkit import PromptSession
from prompt_toolkit.patch_stdout import patch_stdout
import asyncio
import time
async def echo():
while True:
print(time.time())
await asyncio.sleep(1)
async def read():
session = PromptSession()
while True:
with patch_stdout():
line = await session.prompt_async("> ")
print(line.upper())
loop = asyncio.get_event_loop()
loop.create_task(echo())
loop.create_task(read())
loop.run_forever()