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

2024-09-30 12:14:24 发布

您现在位置:Python中文网/ 问答频道 /正文

通过twisted finger教程,看到了SO问题:

但是,我还不能编写一个可以从多个串行端口读写的twisted程序,尤其是当协议涉及到读取一条或多条线并相应地向设备写回数据时。在

我要做的是为2个调制解调器打开2对(即总共4个)串行端口。与调制解调器的通信使用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命令端口和ttyUSB1命令端口接收到的信息关联起来,对于modem1,是否将另一对命令端口上接收到的信息关联起来?请注意,每个调制解调器都有自己的状态机(设备状态和连接状态)
  • twisted是否提供了按应用程序管理多个状态机的机制?在
  • 调制解调器的USB串行连接可能会由于调制解调器被拔下而被破坏,并在重新插入时重新建立。我如何检测这些事件并将相应设备端口的监视添加到反应堆?目前,我在主应用程序中静态地执行它。在

Tags: 端口dev命令self信息调制解调器状态def
1条回答
网友
1楼 · 发布于 2024-09-30 12:14:24

请注意示例代码

我看不到你在将类注册到reactor之前实例化它们。我想那会失败得很惨。下面是我运行代码的一个类似片段:

# 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)

How/where do I accept CLI input from user, that then triggers sending a set of AT command to the modems ?

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

^{pr2}$

Correlate the information received on command port for ttyUSB0 & ttyUSB1 for modem1, and similarly for the other pair for modem2 ? Note that each modem has it's own state-machine (device-state and connection-state)

在正常使用中,每个连接实例都将有自己的状态机(封装在与连接一起注册到reactor中的类的实例中)。在

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

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


Does twisted provide any mechanism for management of multiple state-machines by application ?

如果你所说的“应用程序”是指“你的扭曲代码”,那么答案绝对是肯定的!在

典型的Twisted应用程序是一系列状态机,都有一些非常好定义的接口。我开始Twisted的冒险之旅,打算用两个状态机(串行机和键盘)编写一个应用程序,但当我适应Twisted的工作时,我意识到增加额外的接口和状态机是很简单的(通过tx库的所有奇妙之处)。在一个下午的时间里,我添加了一个粗略的web接口,一个websocket接口,然后在两者之上都添加了SSL,甚至还添加了SSH调试接口。一旦开始运行,添加接口和状态机就变得很简单了。在

在许多(全部?)在这种情况下,twisted模型是一个状态机将驻留在一个实例类中,该类绑定到一个连接上,并且已经注册到(一个且只有一个)主事件循环中。在

对于派生出新状态机的连接类型(比如http连接),您可以注册一个工厂类/状态机以及侦听连接,这两个连接一起为每个新连接启用派生新类/状态机的应用程序。Twisted应用程序在大规模运行时,通常会有10个甚至100个数千个并发状态实例。在

如果你想把不同的协议和状态粘在一起(。。。所有这些都在您选择的事件循环中(select/epoll/kqueue/etc))

下面是可运行的示例代码,它应该说明其中的许多要点。请阅读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()

It is possible that the USB-serial connection to the modem is destroyed due to the modem being unplugged, and re-established on being plugged back-in. How can I detect such events and add the monitoring of the corresponding device-ports to the reactor ? Currently, I'm doing it statically in the main application.

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

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

事件会在串行端口出错时触发(至少从我在linux上的经验来看),但我不确定USB插头将如何/在何处注册。在


其他可能对您有用的链接

相关问题 更多 >

    热门问题