20 Weeks
Over the last few weeks Vicky's previously invisible pregnancy has finally popped out. Much frustration ensued as this meant most of her clothes didn't fit any more, but that was soon relieved.
Postr 0.12.1
I just made a quick Postr 0.12.1 release to fix authentication with non-trivial HTTP handler strings. If you can't login to Flickr with Postr, then this release should fix it for you.
The tarball is here, and packages for Debian are being built now.
In other news postr.dev has seen a lot of development and is looking pretty damn neat at the moment.
GUPnP Bindings Generation
I've now finished the first draft of the bindings generation tool for GUPnP, which is now part of libgupnp itself. I've added both blocking and non-blocking wrappers, so if you wanted to get the external IP there is the choice of this for blocking calls:
char *ip; GetExternalIPAddress (proxy, &ip, &error);
Or this for non-blocking calls:
static void
external_ip_cb (GUPnPServiceProxy *proxy, char * ip,
GError *error, gpointer userdata)
{
// ...
}
...
GetExternalIPAddress_async (proxy, external_ip_cb, NULL);
I've ported my test applications to use the bindings, which are available in this Bazaar repository. It appears to work quite well, I just need to test it against all of the official service descriptions and add a few small features.
GUPnP Autogeneration
The problem with GUPnP is that (like DBus) when programming from C you need to specify the types of each argument when making a method call:
gupnp_service_proxy_send_action (proxy,
"AddPortMapping", &error,
/* In arguments */
"NewRemoteHost", G_TYPE_STRING, "",
"NewExternalPort", G_TYPE_UINT, external_port,
"NewProtocol", G_TYPE_STRING, "TCP",
"NewInternalPort", G_TYPE_UINT, internal_port,
"NewInternalClient", G_TYPE_STRING, internal_host,
"NewEnabled", G_TYPE_BOOLEAN, TRUE,
"NewPortMappingDescription", G_TYPE_STRING, desc,
"NewLeaseDuration", G_TYPE_UINT, 0,
NULL,
/* Out arguments */
NULL);
Now, that really is quite tiresome. It basically means that you need to have the service reference to hand when coding, because you need to know the name and type of each argument. Luckily for DBus part of dbus-glib is a binding tool which can create type-safe wrappers so that making method calls is much easier. Wouldn't it be nice if there was something similar for GUPnP, which generated inline functions with prototypes like this:
static inline gboolean
AddPortMapping (GUPnPServiceProxy *proxy,
char * in_NewRemoteHost,
unsigned int in_NewExternalPort,
char * in_NewProtocol,
unsigned int in_NewInternalPort,
char * in_NewInternalClient,
gboolean in_NewEnabled,
char * in_NewPortMappingDescription,
unsigned int in_NewLeaseDuration,
GError **error);
Well, now there is. I've put the initial code here but will be moving this into GUPnP itself shortly. The next task is to add asynchronous wrappers just as in dbus-glib, but that shouldn't be too hard.
Anjuta+Poky Integration
Yesterday I tested and rolled a new release of the Poky integration plugin for Anjuta, created by our fearless Sir Bradford. This is a very special piece of magic which lets you use a Poky SDK in Anjuta to cross-compile binaries without any pain, and will even deploy, execute and debug the binaries in a QEMU for testing. As part of the release process I had to test it, so I'll step through what I did as a brief tutorial on how to use Anjuta with Poky.
The prerequisites are Anjuta, the Anjuta Poky SDK plugin, and QEMU. These are all available for installation from our Debian repository for Debian/Ubuntu users, everyone else will have to build from source, sorry! You'll also need a Poky ARM SDK and QEMU ARM images from the Poky web site. The SDK is a tarball which contains a cross compiler with base libraries (glibc, GTK+, and so on) and should be extracted onto your machine (it extracts the SDK into /usr/local/poky). The QEMU image consists of a kernel and a ext3 file system which will boot Poky inside QEMU.
To start I fetched a checkout of Tasks and loaded up Anjuta. I don't have an existing Anjuta project for Tasks, so I used File → New → Project From Existing Sources to create a project using the checkout. At this point I could do native development using Build → Run Configure and Build → Build Project to configure and compile the source, but we want to cross-compile.
To activate cross compiling go to Edit → Preferences → General → Installed Plugins and enable the Poky SDK plugin. This will add a new page Poky SDK to the preferences dialog. We're using an external toolchain so set the SDK root to /usr/local/poky/eabi-glibc/arm and the toolchain triplet to arm-poky-linux-gnueabi. We're also using QEMU instead of a real device so set the paths to the kernel and root filesystem (remembering to uncompress the filesystem). We're now done configuring, so the preferences dialog can be closed. However notice that if you switch from using a SDK to building with a full Poky tree you can use the cross-compiler it produces directly, and you can also use an external device instead of QEMU: the only requirement is that you can SSH into it.
Now to do the build. Use Build → Run Configure to configure Tasks, passing any extra options you want. Note that if you want to debug your build in the future you'll need to enter CFLAGS=-g here to disable optimisation (autoconf sets -O2 -g by default, which isn't useful for debugging). The configure script is then ran with the right environment and options for cross compiling, and with any luck will successfully configure. Then hit Build → Build Project and watch the cross-compiler do its thing. When that has worked, you can prove to yourself that the right thing has happened.
$ file tasks tasks: ELF 32-bit LSB executable, ARM, version 1 (SYSV), for GNU/Linux 2.6.14, dynamically linked (uses shared libs), not stripped
We have an ARM binary, ready for deployment. Start the virtual machine with Tools → Start QEMU (this may ask for your root password to configure networking) and once it has booted you can install the project into the VM with Tools → Deploy. This will run make install to a temporary directory and then rsync it to the VM. Now you can either interact with the VM directly (if the application installed a new desktop file, then it should appear on the desktop), or use Tools → Run Remote to execute a binary directly: entering tasks will execute the freshly installed Tasks. Neat, huh?
For the final trick there is even GDB integration. Tools → Debug Remote will let you specify a local binary (to extract debug symbols from, say src/gtk/tasks) and a remote binary to run, and then start a GDB on the VM and connect to it. The binary will be initially running but paused at the entrypoint, so you can add breakpoints and then continue execution.
Hopefully this post has been a good overview of the integration available between Poky and Anjuta. In the future I hope to see Nemiver integrated into Anjuta, and gdbserver support in Nemiver, which would be a killer combination for Poky integration.
NP: One On Twoism, Various
Gypsy and Geoclue in Fedora
Thanks to Peter Robinson, both Gypsy and Geoclue are scheduled for addition to Fedora 9 Updates. Thanks Peter!
Ridicule
The problem for mainstream pop since the 70s is that metal has siphoned off many of the best freaks and losers.
It's not often you read an article in the Guardian about Adam and The Ants, Finnish Battle Metal bands, and being "cool", but today I did.
Galaxy Dark
I got a free bar of the new Galaxy Dark with my shopping yesterday, which is basically a dark chocolate (50%) version of a Galaxy bar. Well, I say that, but...
The smooth Galaxy way to enjoy dark chocolate... deeply smooth, intensely delicious and not at all bitter.
This should be called Galaxy Fail. It looks like dark chocolate but is pumped with sugar so it has a weird sickly sweet taste, nothing like the creamy taste of the original Galaxy. I predict this product will be binned soon.
Today's Second Geohack
I managed to wangle a Fire Eagle invitation this morning, so over lunch I grabbed the Python API Kit and threw it at the sample Gypsy client.
$ ./gypsy-fireeagle.py 00:0B:0D:88:A4:A3 got 51.861145 0.156275 Updated FireEagle
The first line is me running my script (this one is 64 lines, but it is half whitespace), telling it where my GPS is. The second line is the current position that my rather cheap and nasty GPS determined. The third line tells me that Fire Eagle has been updated with those coordinates.
Suffice to say I'm very impressed with Yahoo's geocoding software. My GPS never settles to an accurate reading and will happily jitter around a 20 metre wide circle for hours, but the location Fire Eagle is reporting me at is two doors away. I'm not exaggerating: it says number 9 on my street when it should be number 5. That is some incredibly accurate mapping they have.
Today's Geohack
Following hot on the heels of Yahoo's announcement of their Internet Location Platform, I wrote a quick 20-line Python hack to convert from latitude and longitude to a place name. Because the ILP doesn't yet expose the ability to go from a position to a WOEID we have to ask the Flickr web services to do this first (as Flickr is owned by Yahoo this is using the same backend). Once we have the WOEID, it can be then be looked up on the ILP and useful information obtained. Example speak more than words:
$ python geohack.py Using position 51.872330 0.161950 Got WOEID 12775 Got town Bishop's Stortford
Now to write a GeoClue provider which will fill in the locality information from the position. Long-term grand plans involve integrating all of this geo magic into Postr, somehow.
NP: Third, Portishead
Fire Eagle Invitation?
Does anyone out there on the Intarwebs work for Yahoo, or have a friend who works at Yahoo? I'd really like to give this Fire Eagle thing a go, specifically integrating Gypsy and GeoClue with Fire Eagle, but it's invitation only at the moment...
Update: I now have an account!
GUPnP Basics, Part 1
For the last few days I've been learning more about UPnP and testing it with the few devices I have around the house. One of these is a cheap ADSL router, which apparently has the lamest UPnP stack on in existence. It does however support the WAN IP Connection interface, so you can use UPnP to get the external IP address and manipulate the port mapping. I'll skip over the horrific security violations this involves, because it's a useful demonstration that the majority of people will be able to test.
Today we'll start simple and get our external IP address using GUPnP. The first thing to be done is to create a Control Point, which in the UPnP model handles discovery of resources, be them devices or services (a device can have multiple services). When creating a control point you can specify the URN of the resource you want to target. In this case we want all services providing WANIPConnection so we'd use urn:schemas-upnp-org:service:WANIPConnection:1. If you want to browse for all services then use ssdp:all (SSDP being the Simple Service Discovery Protocol).
static GMainLoop *main_loop;
int
main (int argc, char **argv)
{
GError *error = NULL;
GUPnPContext *context;
GUPnPControlPoint *cp;
/* libsoup requires threading, so we have to initialise it */
g_thread_init (NULL);
g_type_init ();
/* Default GLib context, default host IP, default port */
context = gupnp_context_new (NULL, NULL, 0, &error);
if (error) g_error (error->message);
/* Create a control point targeting WAN IP Connection services */
cp = gupnp_control_point_new
(context, "urn:schemas-upnp-org:service:WANIPConnection:1");
/* The service-proxy-available signal is emitted when any services which match
our target are found */
g_signal_connect (cp,
"service-proxy-available",
G_CALLBACK (service_proxy_available_cb),
NULL);
/* Tell the control point to start searching */
gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
/* Enter the main loop */
main_loop = g_main_loop_new (NULL, FALSE);
g_main_loop_run (main_loop);
/* Clean up */
g_main_loop_unref (main_loop);
g_object_unref (cp);
g_object_unref (context);
return 0;
}
static void
service_proxy_available_cb (GUPnPControlPoint *cp,
GUPnPServiceProxy *proxy)
{
/* ... */
}
Now we have an application which searches for the service we specified and calls service_proxy_available_cb for each one it found. Now, to get the external IP address we need to invoke the GetExternalIPAddress action. This action takes no in arguments, and has a single out argument called "NewExternalIPAddress". Yes, the naming scheme is stupid. GUPnP has a set of methods to invoke actions -- which will be very familiar to anyone who has used dbus-glib -- where you pass a NULL-terminated varargs list of (name, type, value) tuples for the in arguments, then a NULL-terminated varargs list of (name, value, return location) tuples for the out arguments. A simple implementation would be as follows.
static void
service_proxy_available_cb (GUPnPControlPoint *cp,
GUPnPServiceProxy *proxy)
{
GError *error = NULL;
char *ip = NULL;
gupnp_service_proxy_send_action (proxy,
/* Action name and error location */
"GetExternalIPAddress", &error,
/* IN args */
NULL,
/* OUT args */
"NewExternalIPAddress",
G_TYPE_STRING, &ip,
NULL);
if (error == NULL) {
g_print ("External IP address is %s\n", ip);
g_free (ip);
} else {
g_printerr ("Error: %s\n", error->message);
g_error_free (error);
}
g_main_loop_quit (main_loop);
}
Note that _send_action blocks until the service has replied. If you need to make non-blocking calls then use gupnp_service_proxy_begin_action which takes a callback.
So, that is searching for services and invoking actions in GUPnP. Next time I'll cover subscribing to state variables, and routers which can't count.
NP: Folk But Not Folk, Various