I am writing a plugin for existing software that will use twisted to communicate with a Denon AV receiver. I've created a method, powerDenonOff, in my protocol for turning off the device, and have the reactor start in a thread when the application launches via runConcurrentThread. I have another method, powerOff, which will be called when the user selects to turn the device off in the software. I'm having trouble figuring out how to call my powerDenonOff method in the protocol from the Plugin class.
from twisted.internet.protocol import Factory, Protocol
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor
class DenonProtocol(LineReceiver):
delimiter='\r'
def lineReceived(self, line):
pass
def connectionMade(self):
pass
def powerDenonOff(self):
self.transport.write("PWSTANDBY")
def __call__(self):
pass
class DenonFactory(Factory):
def __init__(self):
pass
def startedConnecting(self, connector):
pass
def clientConnectionFailed(self, one, two):
pass
def connectionMade(self):
pass
def buildProtocol(self, addr):
return DenonProtocol()
class Plugin(software.PluginClass):
def powerOff(self):
reactor.callInThread(powerDenonOff) #I think this may need to be callFromThread but
#but I left it since that was where the poorly
#worded version of my question left off.
def runConcurrentThread(self):
try:
while True:
#hardcoded for testing
port = 23
host = "111.11.11.11"
denon=DenonFactory()
reactor.connectTCP(host, port, denon)
reactor.run()
except self.StopThread:
pass
def stopConcurrentThread(self):
reactor.callFromThread(reactor.stop)
How do I reach the method in my protocol class? Is there a better way to accomplish this?
Thanks
You can't call reactor.run
more than once, so the loop in runConcurrentThread
won't work. You need to start the reactor exactly once per process. You can call connectTCP
any number of times and they'll work as long as the reactor gets started at some point.
If runConcurrentThread
is called in a non-main thread, then you also need to start the reactor without child process support (child processes are only supported when the reactor is run in the main thread).
If stopConcurrentThread
is called in the same thread as runConcurrentThread
then it doesn't need to use reactor.callFromThread
. However, if runConcurrentThread
is supposed to block until the plugin is done, then there's no way stopConcurrentThread
could be called in the same thread, so the use of reactor.callFromThread
must be correct.
reactor.run
will never raise StopThread
. It will just return.
As far as how to call methods on the protocol instance "from" the plugin, this is just a question about managing references in Python. Recall that protocol instances are given a reference to their factory (your factory doesn't do this, but it could) and your plugin class is the code that creates the factory. This means you should be able to write plugin code that refers to the protocol via the factory.
However, you could also use one of the more convenient APIs - for example, endpoints:
endpoint = TCP4ClientEndpoint(reactor, host, port)
factory = ...
d = endpoint.connect(factory)
def connected(protocol):
protocol.powerDenonOff()
protocol.transport.loseConnection()
d.addCallback(connected)
This is just one possible approach - I don't know what kind of connection management makes sense to your application. If you want to maintain a single long-lived connection, then you might save the protocol
reference for later use rather than calling powerDenonOff
and then disconnecting it. I suspect that since the method here is for turning power off, the connection might not live much longer anyway though...
Crochet will help you do most of the more boring and finnicky parts of this. I suggest taking a look at it sooner rather than later.
For the rest, you just need to be very clear about what code runs in what thread. The only Twisted API you are allowed to use in a different thread than reactor.run
was called in is reactor.callFromThread
. You can use this to call other Twisted APIs in the reactor thread. Also keep in mind that you can only run one reactor per process, and you can only run it once. The rest is just keeping track of references to the objects you need to use.