如何使用twisted处理R / W的多个串口?

时间:2015-05-22 13:01:49

标签: python serial-port twisted

通过扭曲的手指教程,看到了SO问题:

然而,我还不能(还)编写一个可以读取和读取的扭曲程序。从多个串行端口写入,特别是在协议涉及读取单行或多行的情况下,并相应地写回设备。

我要做的是打开2对(即总共4个)串口,用于2个调制解调器。与调制解调器的通信使用Hayes AT命令集。虽然大多数与调制解调器的命令/响应交换是通过命令端口进行的,但是对于每个调制解调器,只有很少的诊断信息可通过诊断端口获得。诊断信息应该导致状态机(设备状态,连接状态)被修改。

这是一个粗略的骨架程序,我理解为潜在的方法(基于单端口示例):

class CommandProtocol(LineOnlyReceiver):
    def connectionMade(self):
        log.msg("Connected to command port")

    def lineReceived(self, line):
        print repr(line)
        processCommandLine(line)

class DiagnosticProtocol(LineOnlyReceiver):
    def connectionMade(self):
        log.msg("Connected to diag port")

    def lineReceived(self, line):
        print repr(line)
        processDiagnosticLine(line)

...

# modem1 ports
cmdPort[0] = SerialPort(CommandProtocol, "/dev/ttyUSB0", reactor, 115200)
diagPort[0] = SerialPort(DiagnosticProtocol, "/dev/ttyUSB1", reactor, 115200)
# modem2 ports
cmdPort[1] = SerialPort(CommandProtocol, "/dev/ttyUSB3", reactor, 115200)
diagPort[1] = SerialPort(DiagnosticProtocol, "/dev/ttyUSB4", reactor, 115200)

然而,我很遗憾,我如何做以下事情:

  • 我如何/在何处接受来自用户的CLI输入,然后触发向调制解调器发送一组AT命令?
  • 关联ttyUSB0&命令端口上收到的信息。调制解调器1的ttyUSB1,调制解调器2的另一对类似吗?请注意,每个调制解调器都有自己的状态机(设备状态和连接状态)
  • twisted是否提供了应用程序管理多个状态机的任何机制?
  • 由于调制解调器已拔下,可能会破坏与调制解调器的USB串行连接,并在插入后重新建立连接。如何检测此类事件并将相应设备端口的监控添加到反应堆?目前,我正在主要应用程序中静态地执行此操作。

1 个答案:

答案 0 :(得分:6)

请注意您的示例代码

我没有看到你在将它们注册到反应堆之前实例化你的类。我希望这会很糟糕。这是我的运行代码的类似片段:

# stuff to process messages coming from the serial port
class SerialEater(basic.LineReceiver):
    statusCallback = None

    def __init__(self):
        self.keyinprocess = None

    def lineReceived(self, data):
      self.dealWithSerial(data)

    def connectionLost(self, reason):
      if(reactor.running):
        print "Serial lost but reactor still running! reason: " + str(reason) + " at time " + time.asctime()
    [...etc...]



# Register the serialport into twisted
serialhandler = SerialEater()                   # <------------- instantiate
SerialPort(serialhandler, '/dev/ttyUSB0', reactor, baudrate=115200)
  

我如何/在何处接受来自用户的CLI输入,然后触发向调制解调器发送一组AT命令?

就像如何将串行处理程序注册到Twisted一样,您可以注册标准io的处理程序,例如:

# stuff to pull cbreak char input from stdin
class KeyEater(basic.LineReceiver):

    def __init__(self):
      self.setRawMode() # Switch from line mode to "however much I got" mode

    def connectionLost(self, reason):
      if(reactor.running):
        self.sendLine( "Keyboard lost but reactor still running! reason: " + str(reason) + " at time " + time.asctime())

    def rawDataReceived(self, data):
      key = str(data).lower()[0]
      try:
        if key == '?':
          key = "help"
     [...etc...]

# register the stdio handler into twisted
keyboardobj = KeyEater()
keyboardobj.serialobj = serialhandler
stdio.StandardIO(keyboardobj,sys.stdin.fileno())
  

将ttyUSB0&amp;命令端口上收到的信息关联起来。调制解调器1的ttyUSB1,调制解调器2的另一对类似吗?请注意,每个调制解调器都有自己的状态机(设备状态和连接状态)

在正常使用中,每个连接实例都将拥有自己的状态机(包含在您与连接一起注册到反应器中的类的实例中)。

您作为程序员选择如何连接类的状态,但通常是通过推送对合作伙伴类的引用。

下面,这个答案包含可运行的代码,用于说明如何在状态机/接口之间连接数据。这也在SO中说明:Persistent connection in twisted

  

twisted是否提供了应用程序管理多个状态机的任何机制?

如果“申请”是指“你的扭曲代码”,那么答案绝对是!

典型的Twisted应用程序是一组状态机,所有这些都具有一些非常明确定义的接口。我开始了我的Twisted冒险,打算用两个状态机(一个串口和键盘)编写一个应用程序,但是当我对使用twisted变得舒服时,我意识到添加额外的接口和状态机是很简单的(通过所有的奇迹tx库)。一天下午,我添加了一个粗略的Web界面,一个websocket界面,然后在两者上铺设SSL,甚至添加到SSH调试界面上。一旦你开始滚动,添加接口和状态机变得微不足道。

在许多(全部?)情况下,扭曲的模型是状态机将驻留在一个实例化的类中,该类与连接相关并且已经注册到(一次性)主事件中-loop。

使用生成新状态机的连接类型(想想http连接),你可以注册一个工厂级/状态机以及监听连接,它们共同使应用程序能够为每个新的类生成新的类/状态机连接。当按比例运行时,扭曲的应用程序通常会有10s甚至100s的数千个并发状态实例。

如果您尝试将不同的协议和状态粘合在一起(......所有这些都在您选择的事件循环中(选择/ epoll / kqueue / etc)),Twisted是惊人的。

以下是可运行的示例代码,应该说明其中的许多要点。阅读def main()之前的评论,了解有关代码的更多背景信息:

#!/usr/bin/python
#
# Frankenstein-esk amalgam of example code
#   Key of which comes from the Twisted "Chat" example
#   (such as: http://twistedmatrix.com/documents/12.0.0/core/examples/chatserver.py)

import sys # so I can get at stdin
import os # for isatty
import termios, tty # access to posix IO settings
from random import random
from twisted.internet import reactor
from twisted.internet import stdio # the stdio equiv of listenXXX
from twisted.protocols import basic # for lineReceiver for keyboard
from twisted.internet.protocol import Protocol, ServerFactory

class MyClientConnections(basic.LineReceiver):

    def __init__(self):
        self.storedState = "Idle"
        self.connectionpos = None

    def connectionMade(self):
        self.factory.clients.append(self) # <--- magic here :
            # protocol automagically has a link to its factory class, and
            # in this case that is being used to push each new connection
            # (which is in the form of this class) into a list that the
            # factory can then access to get at each of the connections
        self.connectionpos = str(self.factory.clients.index(self)) # figure out 
                                      # where I am in the connection array
        print "Got new client! (index:", self.connectionpos + ")"
        self.transport.write("---\nYour connection: " + self.connectionpos + "\n---\n")

    def connectionLost(self, reason):
        print "Lost a client!"
        self.factory.clients.remove(self)

    # used to pretend that something was typed on a telnet connection
    def fakeInput(self, message):
        self.transport.write("FAKING Input: '" + message + "'\n")
        self.lineReceived(message)

    #this is only in a def on its own so I can lump my demo callLater
    def stateUpdate(self, newState, delay):
        self.storedState = newState
        # the following is a hack to fake data coming in this interface
        reactor.callLater(delay, self.fakeInput, newState + " DONE")

    def processInput(self, newState):
        # all the logic in here is junk to make a demo, real code may or may-not look like
        # this.  This junk logic is an example statemachine though
        if self.storedState == "Idle":
            if newState == "start":
                self.stateUpdate("State A", 1)        
                # send a message to this connection
                self.transport.write("starting state machine\n")
                # send a message to the term in which the script it running
                print "Connection [" + self.connectionpos + "] starting state machine"
        elif self.storedState == "State A":
            if newState == "State A DONE":
                self.transport.write("Beginning state B\n")
                self.stateUpdate("State B", 2)
        elif self.storedState == "State B":
            if newState == "State B DONE":
                self.transport.write("Beginning state C\n")
                self.stateUpdate("State C", 2)
        elif self.storedState == "State C":
            if newState == "State C DONE":
                self.storedState = "Idle"
                # send a message to this connection
                self.transport.write("Returning to Idle state\n")
                # send a message to the term in which the script it running
                print "Connection [" + self.connectionpos + "] return to Idle state"

    def lineReceived(self, line):
        # print "received '" + line +"' from connection", self.factory.clients.index(self)
        self.processInput(line)

class MyServerFactory(ServerFactory):
    protocol = MyClientConnections

    def __init__(self):
        self.clients = [] # this gets filled from the class above

    def sendToAll(self, message):
      for c in self.clients:  # Read MyClientConnections class for background
        c.transport.write(message)

    def randStart(self, width):
      for c in self.clients:
        startDelay = random() * width
        print "Starting client " + str(c.connectionpos) + " in " +str(startDelay) + " secs" 
        reactor.callLater(startDelay, c.processInput, "start")

# to set keyboard into cbreak mode -- just because I like it that way...
class Cbreaktty(object):
    org_termio = None
    my_termio = None

    def __init__(self, ttyfd):
        if(os.isatty(ttyfd)):
            self.org_termio = (ttyfd, termios.tcgetattr(ttyfd))
            tty.setcbreak(ttyfd)
            print '  Set cbreak mode'
            self.my_termio = (ttyfd, termios.tcgetattr(ttyfd))
        else:
          raise IOError #Not something I can set cbreak on!

    def retToOrgState(self):
        (tty, org) = self.org_termio
        print '  Restoring terminal settings'
        termios.tcsetattr(tty, termios.TCSANOW, org)


class KeyEater(basic.LineReceiver):

    def __init__(self, factoryObj):
        self.setRawMode() # Switch from line mode to "however much I got" mode
        # the following is one of the key connecting ideas in twisted, the object
        # that contains another state machine (really all of the tcp statemachines)
        # has been passed into this class via its init.
        self.factoryObj = factoryObj

    def rawDataReceived(self, data):
        key = str(data).lower()[0]
        if key == 's':
            # The following line is going to call (from within the factory object)
            # the random start def
            self.factoryObj.randStart(5)
        elif key == 'd':
            print "State Dump of connections"
            print "-------------------------"
            for c in self.factoryObj.clients:
                print "#" + str(c.connectionpos) + "      " + c.storedState
        elif key == 'q':
            reactor.stop()
        else:
            print "--------------"
            print "  If you haven't already, connect to this script via a"
            print "  'telnet localhost 5000' at least one (multiple connections"
            print "  are better)"
            print "Press:"
            print "      s  - randomly start all clients"
            print "      d  - dump the state of all connected clients"
            print "      q  - to cleanly shutdown"
            print " Note: you can type commands in the connections, things"
            print "       most useful of which is 'start'"
            print "---------------"

# Custom tailored example for SO:30397425
# 
# This code is a mishmash of styles and techniques. Both to provide different examples of how
# something can be done and because I'm lazy.  Its been built and tested on OSX and linux,
# it should be portable (other then perhaps termal cbreak mode).  If you want to ask
# questions about this code contact me directly via mail to mike at partialmesh.com
#
# While it isn't directly using serial ports, the tcp connections that its using are a good
# parallel.
#
# It should be used by running the script and then opening up many windows telnet'ing into
# localhost 5000.
#
# Once running press any key in the window where the script was run and it will give
# instructions.  
# The normal use case would be to type "s" to queue statemachine
# start-ups, then repeatedly press 'd' to dump the status of all the state machines
#
# 'start' can be typed into any of the telnet connections to start them by hand too.


def main():
    client_connection_factory = MyServerFactory()

    try:
      termstate = Cbreaktty(sys.stdin.fileno())
    except IOError:
      sys.stderr.write("Error: " + sys.argv[0] + " only for use on interactive ttys\n")
      sys.exit(1)

    keyboardobj = KeyEater(client_connection_factory)

    stdio.StandardIO(keyboardobj,sys.stdin.fileno())
    reactor.listenTCP(5000, client_connection_factory)
    reactor.run()
    termstate.retToOrgState()
if __name__ == '__main__':
  main()
  

调制解调器的USB串行连接可能由于调制解调器被拔出而被破坏,并且在插入后重新建立。如何检测此类事件并将相应设备端口的监控添加到反应堆?目前,我正在主要应用程序中静态地执行此操作。

经过研究,我没有一个简单的答案。我仍然怀疑以下逻辑将接近解决方案但我没有运气找到今天实现此目的的代码。

我的猜测是有一种合理的方法可以确定是否发生了USB事件,并确定是否添加了串行设备。但我怀疑是否有一个很好的方法可以确定它是否是您的串行设备之一 - 更不用说它是您的命令或诊断接口(除非您的构建硬件并且可以控制设备的USB ID)

事件是针对串口错误引发的(至少根据我在linux上的经验),但我不确定USB拔出的注册方式/位置。

可能对您有用的其他链接