I am writing a server that serves an EventSource data-stream using Python's Twisted. Although this is my first time using Twisted, I hope this part of the code is acceptable.
To get the events, the code queries another server. The other server is a PLC that has its own special ASCII-based protocol. It must be continually polled in order to extract the changes in variables that I would like to turn into events. It is here that I am a little uncertain of the best technique, and I have not been able to find any examples of anything similar.
Specifically, my question is: should the ClientFactory keep a reference to the instance of the Protocol that it creates? I am using this instance in my code in order to have access to the protocol, which allows me to query the PLC.
EDIT: Just to clarify, the PLC can accept only one connection at a time. Thus the code needs to be able to send all queries to the PLC through just the one connection.
Although this approach works, it feels like a bit of a hack to me as the "factory" is really being coerced into something more like a Singleton. Is this how it should be done? Is there a better approach?
To make this more concrete, here is a section of the EventSource
code that uses the instance of the protocol:
class EventSource(Resource):
isLeaf = True
def __init__(self):
# Create connection to PLC
self.factory = ASCIIClientFactory()
reactor.connectTCP("192.168.0.91", 10001, self.factory)
def writeEvent(self, request):
if self.factory._instance is None:
return # nothing we can do yet
# Obtain variables from query string
response = {}
if "x" in request.args:
address = request.args["x"][0]
d = self.factory._instance.getRegister( address ) # <--- What do you think?
def onResult(data):
response["x"] = data
request.write("\nevent:\n")
request.write("data: "+json.dumps(response)+"\n")
d.addCallback(onResult)
And for reference, here is the protocol and its ClientFactory
:
class ASCIIClientProtocol(LineReceiver):
class Command:
def __init__(self, string):
self.string = string
self.deferred = Deferred()
def __init__(self):
self._busy = False
self._commands = deque()
self._current = None
def processCommand(self):
if not self._busy:
self._busy = True
self._current = self._commands.popleft()
self.transport.write( self._current.string )
return self._current.deferred
def addCommand(self, commandString):
command = ASCIIClientProtocol.Command( commandString )
self._commands.append( command )
self.processCommand()
return command.deferred
def getRegister(self, address):
return self.addCommand("SR"+str(address)+"*\n")
def getRaw(self, address):
return self.addCommand("SU"+str(address)+"*\n")
def setRegister(self, address, value):
return self.addCommand("SW"+str(address)+" "+str(value)+"*\n")
def dataReceived(self, data):
assert self._current != None
self._current.deferred.callback(data[0:-2]) # Remove the trailing \r\n
self._busy = False
self._current = None
if len(self._commands) != 0:
self.processCommand()
class ASCIIClientFactory(protocol.ClientFactory):
def __init__(self):
self._instance = None
def buildProtocol(self, addr):
self._instance = ASCIIClientProtocol()
return self._instance
def clientConnectionFailed(self, connector, reason):
print "Failed to connect to PLC"
print "reason:", reason
print "Trying again to connect..."
connector.connect()
def clientConnectionLost(self, connector, reason):
print "Connection to PLC has been lost"
print "reason:", reason
print "Trying to reconnect..."
connector.connect()
def startedConnecting(self, connector):
print "Connecting to PLC..."