[Twisted-Python] how to get an idle callback while running a reactor?
I'm trying to make a simple AIM bot that, in addition to responding when I talk to it, can also send me a message on its own initiative. My code is based on SkippyTalkBot [1], but I confess that I don't understand it very well, and though I've been crawling the Twisted documentation for a few days now, it's still rather mysterious to me. So I'm at a loss as to how to add an "idle" function that will allow my bot to periodically see whether it has something new to say to the user. My code looks like this: class B(oscar.BOSConnection): capabilities = [oscar.CAP_CHAT] ... def receiveMessage(self, user, multiparts, flags): "respond to an incoming message from the user" # (by calling self.sendMessage) ... class OA(oscar.OscarAuthenticator): BOSClass = B protocol.ClientCreator(reactor, OA, SN, PASS, icq=icqMode).connectTCP(*hostport) reactor.run() Now, from trawling the docs, I guess that I want to call reactor.callWhenRunning... but then I'd like to pass in a bound method of my B class, so it can call sendMessage on myself. But I'm stumped as to how my B class is even being instantiated, let alone how to get a reference to that instance. And the callWhenRunning idea is only a wild guess and probably wrong. I'm sure the answer is in the Twisted docs or examples somewhere, but I haven't been able to find it. Will someone have pity and share a clue? Thanks, - Joe [1] http://www.gilesgoatboy.org/python/skippy.html
On Sat, 20 Dec 2008 07:10:53 -0700, Joe Strout <joe@strout.net> wrote:
I'm trying to make a simple AIM bot that, in addition to responding when I talk to it, can also send me a message on its own initiative. My code is based on SkippyTalkBot [1], but I confess that I don't understand it very well, and though I've been crawling the Twisted documentation for a few days now, it's still rather mysterious to me.
So I'm at a loss as to how to add an "idle" function that will allow my bot to periodically see whether it has something new to say to the user.
While this sometimes makes sense, it's usually *not* the approach you want to take. You're describing a solution which is essentially polling. And polling is not as good as responding to events. You *could* run a function ten times a second that looks around for a message to send and sends it if it finds one. Or, whatever event occurs which creates those messages could just send the message instead of putting it in a pile and waiting for your poller to find it. I can't tell you how to do this in detail, though, since I don't know what causes you to have messages to send.
[snip]
Now, from trawling the docs, I guess that I want to call reactor.callWhenRunning...
Probably not. If you want to run a function later (and that's how polling is generally implemented), you want reactor.callLater. If you want to run a function repeatedly at a fixed interval, twisted.internet.task.LoopingCall will help.
but then I'd like to pass in a bound method of my B class, so it can call sendMessage on myself. But I'm stumped as to how my B class is even being instantiated, let alone how to get a reference to that instance. And the callWhenRunning idea is only a wild guess and probably wrong.
You'll probably encounter the same problem with reactor.callLater as you were encountering with reactor.callWhenRunning, though. How do you get a reference to the bound method to pass in? That's simple - don't call the reactor method until you *have* an instance. You can call almost any reactor method even *after* the reactor is started; you do not have to set everything up before you call reactor.run. You can read more about reactor.callLater in the scheduling howto: http://twistedmatrix.com/projects/core/documentation/howto/time.html Jean-Paul
Jean-Paul Calderone wrote:
So I'm at a loss as to how to add an "idle" function that will allow my bot to periodically see whether it has something new to say to the user.
While this sometimes makes sense, it's usually *not* the approach you want to take. You're describing a solution which is essentially polling. And polling is not as good as responding to events. You *could* run a function ten times a second that looks around for a message to send and sends it if it finds one. Or, whatever event occurs which creates those messages could just send the message instead of putting it in a pile and waiting for your poller to find it.
Well, the thing is that there will actually be a great many things that could cause it to need to send a message, such as the time, a change in the outside temperature, a certain change in the value of a stock fund, etc. And the AIM interface isn't the only interface to this bot. So I really do think I need a way to call a function once every few seconds. Now, that does suggest an alternative approach, which is to ignore twisted's event/idle mechanisms and use something external to it. But I'm also fairly newish to Python, so I'm not sure exactly how to do that either -- reactor.run() is a blocking call, so I supposed I'd have to use threads. Then my "idler thread" could call the idle function, sleep for a few seconds, and repeat. But if there's a simple way to do this within Twisted, I'd like to learn what that is .
Now, from trawling the docs, I guess that I want to call reactor.callWhenRunning...
Probably not. If you want to run a function later (and that's how polling is generally implemented), you want reactor.callLater. If you want to run a function repeatedly at a fixed interval, twisted.internet.task.LoopingCall will help.
Thanks, I'll look at these.
You'll probably encounter the same problem with reactor.callLater as you were encountering with reactor.callWhenRunning, though. How do you get a reference to the bound method to pass in? That's simple - don't call the reactor method until you *have* an instance. You can call almost any reactor method even *after* the reactor is started; you do not have to set everything up before you call reactor.run.
OK, so somewhere within my B class, maybe in initDone, set it up there? That makes sense. Thanks, - Joe
On Sat, Dec 20, 2008 at 10:28 AM, Joe Strout <joe@strout.net> wrote:
Jean-Paul Calderone wrote:
So I'm at a loss as to how to add an "idle" function that will allow my bot to periodically see whether it has something new to say to the user.
While this sometimes makes sense, it's usually *not* the approach you want to take. You're describing a solution which is essentially polling. And polling is not as good as responding to events. You *could* run a function ten times a second that looks around for a message to send and sends it if it finds one. Or, whatever event occurs which creates those messages could just send the message instead of putting it in a pile and waiting for your poller to find it.
Well, the thing is that there will actually be a great many things that could cause it to need to send a message, such as the time, a change in the outside temperature, a certain change in the value of a stock fund, etc. And the AIM interface isn't the only interface to this bot. So I really do think I need a way to call a function once every few seconds.
Just because you have a number of sources doesn't mean you intrinsically can't use them in an efficient, event-oriented manner. And you don't need a time-based call *just* because you have more than one interface to the bot. A timed poll may end up the easiest solution for you, but I want to make sure you understand the way events work, because there may be a much better solution. Let's go over your example event sources: Time - Easy: your event source there is reactor.callLater itself. Change in the temperature - How does your thermostat communicate this information? A serial connection? a TCP socket? in any case, you can probably hook up to that event source with Twisted directly. Stock price - same question. The point is that you can hook into these event sources with some code that will immediately send the message out over your AIM (or IRC, or whatever other) interface to the bot. That means communication will be instantaneous *and* efficient. -- Christopher Armstrong http://radix.twistedmatrix.com/ http://planet-if.com/ http://canonical.com/
Christopher Armstrong wrote:
Just because you have a number of sources doesn't mean you intrinsically can't use them in an efficient, event-oriented manner.
No, but when these "sources" as you call them are external data sources that I have to periodically check (have to -- there is no way to make these external contact my app when something changes), that is inherently a polling situation.
And you don't need a time-based call *just* because you have more than one interface to the bot.
True; that just makes it easier.
Change in the temperature - How does your thermostat communicate this information?
I'm accessing a web service.
Stock price - same question.
Same answer. These services give you an answer when you ask for them; they don't come to you when a value changes. Anyway, I think I have the answer I need (twisted.internet.task.LoopingCall), and I thank you all for it. Best, - Joe
participants (3)
-
Christopher Armstrong -
Jean-Paul Calderone -
Joe Strout