sugarbot's aim is to provide testing and automation facilities for the OLPC Project's Sugar GUI. The project must first identify and evaluate possible implementation options, and then implement the best choice. Although it has a Sugar focus, sugarbot should be easily extensible to other Python-based GUI's.

Sunday, June 29, 2008

Issues Testing XML Server

Running into some issues getting the XML Server issues to work properly.  The problems stem from the connections not wanting to persist, or not wanting to close/open fast enough, in addition to some other oddities.

Committed several new files to SVN, lots of tests for most of the other functionality.

Friday, June 27, 2008

So wait...

You mean I can make changes now, and not have to worry that much that 13,000 things just broke?

...

AND I can find bugs in the process?

zomg

I founded a bug.  While writing nose tests.

(I tried to find a fitting lolcat picture, but couldn't.)

sugar-emulator

I'm fed up with sugar-jhbuild (building xulrunner causes the compiler to *segfault*) for the time being.  I'm just going to use sugar-emulator, even though it's an older version of Sugar.  I would imagine most of the internals will still work the same, and I'll be able to get my tests written and... tested.

Thursday, June 26, 2008

Not cooperating...

Ubuntu, sugar-jhbuild, and Vmware Fusion are not cooperating.  And it's pissing me off.

First, I tried to use the old sugar-jhbuild from before the drive corruption, because I had the actual files stored on the Mac filesystem (accessed in Ubuntu via the Shared Folders featured in VMWare).  For whatever reason, that didn't work.  Okay, I can accept that.

Next, I tried to simply './sugar-jhbuild update' and './sugar-jhbuild build' on the existing installation.  I let that run overnight.  No dice.

So I started out with a fresh copy, via git.  After 'update' passed, 'build' decided that it would freeze the VM, resulting in me having to hard-reboot (because the VMWare process would not quit via Force Quit or sudo kill).

Now I am rebuilding sugar-jhbuild *again*, and it's deciding that it's going to take its dandy time.  Literally.  I've given the VM 512MB of RAM and access to both processors, and it's sitting at about 20% utilization.  In addition to that, the VM is completely unresponsive -- which is intereseting, because it is supposedly (according to Activity Monitor in OSX) -- not really doing anything.

This whole incident is becoming very frustrating.  I can't test any of the 'nose' code that I've written, because none of the Sugar libraries are available/working under Leopard, which means that anything that's Sugar-dependent (read: everything) refuses to run on Python under OSX.

In order to see if I could expedite things, I went the 'apt' route with precreated Sugar packages and emulator.  I figured I'd just run the sugar-jhbuild process in the background, and work around it until it was done building.  As I mentioend before, the VM is completely unresponsive... to the point that I'm considering just creating a new VM (yet again...).  Of course, I know that won't really solve anything, because Ubuntu runs just fine until it gets a few minutes into the sugar-jhbuild process.

I should very shortly have access to a shiny new Dell workstation (as part of my UH project) that will have Ubuntu on it.  Hopefully that will yield better results.

Monday, June 23, 2008

Documentation Effort

Documentation of the source code has been going fairly well.  I've also knocked off a few items on my todo.txt.

Tomorrow night will kick off the nose'ing of sugarbot.

Ubuntu is running

The Ubuntu VMware machine is up and running again, and sugar-jhbuild finished last night (Update: Haha, just kidding!). I've got to do some updates for 10.5.3, and the obligatory Ubuntu updates, but everything should be working now (of course, Safari is exhibiting some weird behavior but I think 10.5.3 will fix that).

Sunday, June 22, 2008

Drive Genius

Do not use it! It may decide to hose your entire filesystem, resulting in you needing do a completely fresh installation of OS X. Thank Steve Jobs for Time Machine (and WD for making 250GB of storage available in a pocket-size form factor for $120).

After my Time Machine backup finishes restoring (looks like about 45 minutes left on that), I have to re-install my Ubuntu VM, as my VM's are the only thing I didn't have Time Machine back up (because they would chew up disk space). I'm hoping to be back up and running before the night is over.

Saturday, June 21, 2008

New Wiki Pages

There are two new Wiki pages. This needed to be done eventually, so I thought I'd take a break from coding to write up some documentation. It's pretty basic, but better than no documentation at all :-).

Sugarbot Script Syntax
Running Sugarbot

Refactoring

By the way, I am also going to do some refactoring on the sbGUI class.  It has had some functionality embedded in it that it should not be responsible for, and some of the methods are kludges as a result of the recent restructuring of the "command pipeline" (dunno what else to call it).

Not going well today...

Today has been an interesting day so far (about 6 work-hours left to go today) in that there have been multiple small bugs all conspiring against me.  (Yes, I think the Python interpreter is sentient.)

I think/hope I have FINALLY gotten things sorted out.  Maybe.

Friday, June 20, 2008

Now Launching

I have re-restructured most of the code, and I can now send commands over XML-RPC as text, and everything works fine (so far).  I have to change some of the execution code, but the issues should be minor.

Thursday, June 19, 2008

Parsing Server-side

Parsing commands server-side may run into a few issues, the first of which is that returning an object over XML-RPC does not work the way that I imagined it would.  While it provides the objects' members as a dictionary, it does not provide any information about the instance of the object -- for example, whether it is an sbInitCmd object or a sbClickCmd object.

I can make a work-around for this (for example, by making the class name a member, and then passing that to another function that re-instantiates the object) but it seems like a giant kludge.

Bug Hunting

'self.currentScript' is not equivalent to 'currentScript'.

The fact that the error was being reported via the xmlrpc library only made it that much harder to realize my mistake.

Wasted time, bah.

Wednesday, June 18, 2008

Moving

I've had to do some restructuring to get the Parser to play nice with the RPC Server, and for the Commands to still work the way they should.  

Played around with the logger functionality tonight.  Very nifty.

Tuesday, June 17, 2008

That's it, really?

I took the path of least resistance, and provide the path to the script file via XML-RPC.

After an hour of struggling with it, only to finally notice that I have to restart the server script (in addition to the Sugar Activity) if I actually want my changes to propagate, I have to say... that was pretty freaking easy.

By the way, why did nobody tell me about XML-RPC and its awesomeness sooner?

Sunday, June 15, 2008

Timout problem

I tried the threading route... that didn't work so well due to Python's GIL.  What a pain.

Good news is that the problem was solved with -- drumroll -- changing an 'if' into a 'while' and adding a little bit more hackery.

XML-RPC

Ha! I may just go with my networking idea for telling Sugarbot what to do. Usage of XML-RPC had been discussed before, and it solves at least one problem, of how to get Sugarbot to execute different scripts without swapping out script files.

A separate XML-RPC server can feed it the commands.  Bingo!

Saturday, June 14, 2008

Temporary Diversion

I just realized how convoluted the current logging mechanism was.

Fixing that pronto.

Brick Wall

Just hit a brick wall today.

Turns out that I might need to create a separate thread in order to launch the commands, as the method that I am currently using is only good until things are done initializing. After that, no new gdk.Event's are issued, which means that the place that I use as a callback to launch sbCommand objects never gets called.

For an example of what I mean, check out r41 (just committed it) and run the sugarbot activity. No modification should be necessary, and it should launch the Terminal activity (which you need to have installed for this to work properly, obviously). After/during the "sleep '2'" statement, everything finishes initializing, which means there are no more Events flying around. That means that all of the 'type' commands will not execute until some event is created. To see this, launch the activity, and do not touch your mouse or keyboard for a few seconds. Then move the mouse, and the 'type' commands should execute shortly thereafter.

Ideas:
  1. Multithreading. Just have a thread that sits and does what the hooked code would otherwise do. This would allow me to separate the sbgui functionality from the execution of commands, which is a Good ThingTM.
  2. Find a way to generate some kind of empty gdk.Event after executing a sbCommand, if there are no other gdk.Event's waiting for processing.

Friday, June 13, 2008

Reorganizing, issues with a few Widgets

Well, it's been a few days since I've had a serious blog post. I was in Maryland for Tuesday afternoon through Thursday night (got back around midnight) for interviews with the [government]. I think things went well, and hope to hear back from them (although it's a "hurry up and wait" mentality, that may be up to a month).

As far as sugarbot goes, I got a bit done on Tuesday evening at the airport, but didn't have any time to work Wednesday or Thursday. I've made a few additions so that sugarbot now reads from an actual configuration file (located in the sugarbot.activity directory for now), and can load any arbitrary activity. From the activities that I've tested (Write, Terminal, and Browse), there may be some issues getting some of the more specialized Widgets to respond properly. I need to do some research into the AbiWord widget, for example. Although they are unique to their Activity, I would like to have support for the more common ones. Unfortunately as far as a simple demo goes, Calculate.activity is a poor candidate since it [1] requires modification to be able to check the input field and [2] stores the answers along with the output. So instead of:

click '1'
click '+'
click '1'
click 'enter'
text 'calculateAnswerField'=='2'

the required script might instead read:

text 'calculateAnswerField=='1+1\n\t2' [or however a gtk.TextBuffer stores the text internally]

I have also moved some functionality around, but it is mostly just a matter of what is initialized where. For example, the sugarbot Activity instance class needed access to the parser, which means that the parsed commands had to be passed down to the sbgui object, etc. I also spent a little bit of time on the IRC channel (#pygtk) and found out a more convenient/likely to work with other kinds of widgets method for simulating clicks and typing (via Signals). Nifty.

Currently, I do not have any ideas on how to tell sugarbot which file to read from, aside from some hard-coded value. I would greatly prefer to not use the GUI to do this, so that it would be easier to automate. A few things off the top of my head that might work...
  • A script file that actually contains multiple scripts. With each subsequent execution, the next 'script' is run. sugarbot keeps track of where it needs to read from next with some other file, and resets that data once it reaches the end.
  • Use the network to connect to some separate 'server' application that tells sugarbot what to do. This other application would have unlimited flexibility, since it is not bounded by the Sugar environment.
As far as automating the process of starting sugarbot in the first place, that's another problem to tackle. I need to find some way to hook into the PyGTK calls of the Sugar process, in a similar manner that I've used to automate activities. Again, I'd prefer to find a way to do this without modifying any code in the Sugarbot base.

Wednesday, June 11, 2008

In Maryland

In Maryland right now. Got done with my polygraph and first operational interview today. Psych battery, psych eval, second op. interview, and HR interview tomorrow.

Added some parser functionality, and moved some functionality around in regards to the commands and parser while I was at the airport/on the plane. More updates on Friday.

Sunday, June 8, 2008

Multiple-Initialization

A commenter (Michael Stone) asked for a bit more information on the multiple-initialization issues that I ran into. The issue first appeared when I first started the project, and I resolved the issue with a runtime inheritance hack. The general idea is:

The sugarbot object makes itself inherit from the other class, then calls __init__(self, self.handle) so that the Activity gets initialized the way that it normally would. This was not how it was originally set up (because it did not work the way it was originally set up). At first, I just assumed that I could do the following:
self.parentClass = someOtherActivityClass
sugarbot.bases = (self.parentClass,)
self.parentClass.init(self,self.handle)
…and have everything work. Unfortunately, certain calls (calling activity.Activity.__init__ explicitly, to initialize the activity being one of those) only like to be called by the Activity instance that it expects the call to come from. Otherwise, the excrement comes into contact with the rotary cooling device.
launchedActivity = someOtherActivity(self,handle)
An issue that I’m facing is that, with the current way of instantiating Activities, it is not possible (to my knowledge) to terminate the spawned activity without causing the whole process to terminate. I think the way to resolve this is going to (ultimately) be to automate the Sugar GUI itself, so that I can re-launch the sugarbot Activity after it terminates. It will somehow know (or perhaps have this information provided to it) to run the next automation script or the like.

Example Activity:
from sugar.activity import activity
import sys, os
import gtk
class exampleActivity(activity.Activity):
def init(self, handle):
# With or without the following call, everything freaks out.
activity.Activity.init(self,handle)
# Add the path to the activity
sys.path.append(pathToCalculateActivity)
# Import and instantiate the activity
from calculate import Calculate
launchedActivity = Calculate(self,handle)

Saturday, June 7, 2008

Re-factoring mood?

Moved a bunch of classes to their own individual files.  Nothing too exciting.

Debating what my next-step is:
1.) Go through each object type and make sure the various commands work (e.g. "click algebra-ln" when the "algebra-ln" button is not exposed).
2.) Finish the parser so that it can read from command.
3.) Build a GUI so that you can select various activities (this is probably a no-no, as it messes with the GUI state, and Sugar seems pretty finicky about certain things if they get set-up twice -- which screws up spawning other activities).   Also have considered implementing this functionality in the parser (e.g.  a command with the syntax "activity Calculate").  However, that has implications of its own, since actions are only called after some Gtk events are thrown around... which only happens after the Activity has been started.  You get the idea (chicken-egg).

Widget Identifier Refactor

The widget identification code was getting to be a bit messy... and was in a place that it really didn't fit.  All of that code has been refactored into a seperate class (and subclasses).  The code is much cleaner now.  Ideally, widget identification would be simple if the set_name() method was called on each and every widget --- but that's a bit much to expect.  

That aside, sugarbot can now identify every individual widget in Calculate.activity.  Unfortunately, there are multiple ways of naming some of the widgets.  For example, Calculate.activity's toolbars.py is responsible for drawing all of the toolbar widgets.  However, some of these have (potentially) multiple names.  In the below example, there are five identifiers that could potentially be used (the five strings used).  Currently, the first one is used, but only because I have not found a way to get at the second one.  It is probably better that the second one is not used, as internationalized text could cause issues with widget identification (the "_()" operator is used in lieu of gettext()).

toolbars.py excerpt:
        self.insert(IconToolButton('algebra-square', _('Square'),
            lambda x: calc.button_pressed(calc.TYPE_OP_POST, '^2'),
            lambda x: calc.button_pressed(calc.TYPE_TEXT, 'help(square)'),
            alt_html='x2'), -1)

Log excerpt showing identified widgets:
Tracking widget id 146431428 by identifier sugarbot Activity
Tracking widget id 146431748 by identifier sugar+graphics+toolcombobox+ToolComboBox
Tracking widget id 146431908 by identifier Keep
Tracking widget id 146474524 by identifier Stop
Tracking widget id 146475644 by identifier edit-copy
Tracking widget id 146476964 by identifier edit-paste
Tracking widget id 146482316 by identifier edit-cut
Tracking widget id 146483516 by identifier x2
Tracking widget id 146483636 by identifier √x
Tracking widget id 146484876 by identifier x-1
Tracking widget id 146484916 by identifier ex
Tracking widget id 147261556 by identifier xy
Tracking widget id 147262876 by identifier algebra-ln
Tracking widget id 147262916 by identifier algebra-fac
Tracking widget id 147272364 by identifier trigonometry-sin
Tracking widget id 147273604 by identifier trigonometry-cos
Tracking widget id 147274964 by identifier trigonometry-tan
Tracking widget id 147285612 by identifier trigonometry-asin
Tracking widget id 147286892 by identifier trigonometry-acos
Tracking widget id 147286852 by identifier trigonometry-atan
Tracking widget id 147297620 by identifier trigonometry-sinh
Tracking widget id 147298900 by identifier trigonometry-cosh
Tracking widget id 147300220 by identifier trigonometry-tanh
Tracking widget id 147300180 by identifier boolean-and
Tracking widget id 147310948 by identifier boolean-or
Tracking widget id 147312228 by identifier boolean-eq
Tracking widget id 147312268 by identifier boolean-neq
Tracking widget id 147322876 by identifier π
Tracking widget id 147324116 by identifier constants-e
Tracking widget id 147330788 by identifier Deg
Tracking widget id 147337740 by identifier enter
Tracking widget id 147337660 by identifier )
Tracking widget id 147337580 by identifier /
Tracking widget id 147337500 by identifier *
Tracking widget id 147337420 by identifier (
Tracking widget id 147332148 by identifier -
Tracking widget id 147332228 by identifier +
Tracking widget id 147333028 by identifier clear
Tracking widget id 147332948 by identifier .
Tracking widget id 147332868 by identifier 0
Tracking widget id 147332788 by identifier 3
Tracking widget id 147332708 by identifier 2
Tracking widget id 147332628 by identifier 1
Tracking widget id 147332548 by identifier 6
Tracking widget id 147332468 by identifier 5
Tracking widget id 147332388 by identifier 4
Tracking widget id 147332308 by identifier 9
Tracking widget id 147331988 by identifier 8
Tracking widget id 147332188 by identifier 7
Tracking widget id 147331828 by identifier calcMainEntry
Tracking widget id 147332028 by identifier Label
Tracking widget id 147337820 by identifier All equations
Tracking widget id 147337900 by identifier Show history
Tracking widget id 139099748 by identifier SugarActivity

Thursday, June 5, 2008

Getting pretty sugar-specific

I'm getting into some of the nitty-gritty with Sugar here, trying to handle some of the Widget types that it defines itself.  Kind of a pain, but alas...

Wednesday, June 4, 2008

Bandwidth

Bahahahaha.



Unfortunately, this is only in the computer lab in one of the buildings. In the dorm rooms, they regulate it to 384/128 kbps.

Tuesday, June 3, 2008

Damnit, Python

Just spent two hours trying to figure out why some code wouldn't do what I wanted to.

class sbTypeCmd(sbCommand):
"""
Types text into an Entry widget or similar widget with a set_text method.
"""
def __init__(self, sbgui, widgetName, params=None):
sbCommand.__init__(self, sbgui, widgetName, params)

def run(self):
print "sbTypeCmd"
widget = self.getWidget()
methodName = "set_text"
if hasattr(widget, methodName):
print "Hasattr"
text = (" ".join(self._params)).split("'")
print "Text: " + text
if len(text) <>
return False
textToType = "'".join(text[1:-2])
getattr(widget, methodName)(textToType)
return True
return False

Flipping indentation.

Monday, June 2, 2008

Evaluating Entries, basic parsing

Basic strings such as:

click '1'
click '+'
click '1'
click 'enter'

and complex strings such as

entryEval 'Entry'.split()[0] == '1'

Are both parse-able AND should work the way that they're expected.  Pretty cool.  Source is on SVN.  [edit] I have also quickly implemented a feature that will allow the serialization of sbCommand objects -- so that not only can lines like the above be parsed, they can be un-parsed.

Note: In order for Entry hooking to work properly, the gtk.Entry has to [editbe named properly.  For testing purposes, I named Calculate.activity's main entry "Entry".  I will include a patch to automate this process at a later point in time. [edit] Manual patch below, will upload a Diff to the sugarbot downloads area.

Underneath the line in Calculate.activity/layout.py:
  self.text_entry = gtk.Entry()
Enter the following (my personal tests use:
  self.text_entry.set_name('calcMainEntry')

Houston

In Houston right now, everything is going well so far.  I got a bit of sugarbot coding done last night on the plane, and in the Airport lobby.  Trying to come up with a framework that will allow flexibility in the commands, and I think I've got it.

Will have updates later.