Control Second Life with GPS, accelerometer & magnetometer

As part of my PhD work I have produced a modified version of the Second Life viewer, which I have dubbed Pangolin after the Open Virtual Worlds research group’s previous Mongoose, Armadillo & Chimera projects, that allows;

  • connecting a serial device to the viewer
  • controlling movement of the avatar according to GPS readings
  • controlling the camera according to accelerometer & magnetometer readings

The combination of these features allows you to do things like connect an accelerometer, magnetometer & GPS receiver to an Arduino, have it dump readings into the viewer & have the viewer use them to control the avatar & camera. The motivation behind this was to address the ‘vacancy problem’ by creating a mobile cross reality interface; allowing a user to experience simultaneous presence in a real environment & an equivalent synthetic environment, using their physical position & orientation as an implicit method of control for their synthetic representation. I’m presenting a paper about this at the iED 2013 Boston summit in June – if I can secure funding to actually get me there!

Getting Code & Building

The Pangolin source code is available on Bitbucket, with my additions & modifications licensed under the GNU General Public License. The serial IO functionality uses Terraneo Federico’s AsyncSerial class which is licensed under the Boost Software License. The viewer codebase was forked from Linden Lab’s viewer-release before the removal of the --loginuri flag so Pangolin is compatible with OpenSim grids/servers.

Instructions for building the viewer are available on the Second Life wiki. I build using 32-bit Debian GNU/Linux (specifically by chrooting into a 32-bit debootstrap install from a 64-bit Arch Linux host, see my instructions) which produces a binary that runs on 32-bit Linux & on 64-bit Linux with 32-bit compatibility libraries installed.

The serial connectivity makes use of Boost.Asio. The Linden-provided Boost pre-built library is missing some of the features that my modifications make use of, so I build with LightDrake’s alternative; his public libraries are available here. To use these libraries, edit the corresponding entry in the autobuild.xml file in the root of the codebase. You’re looking for this section, here I’ve commented out the original library & hash for the Linux version & replaced it with LightDrake’s;

<key>boost</key>
  <map>
    <key>license</key>
    <string>boost</string>
    <key>license_file</key>
    <string>LICENSES/boost.txt</string>
    <key>name</key>
    <string>boost</string>
    <key>platforms</key>
    <map>
      <key>darwin</key>
      <map>
        <key>archive</key>
        <map>
          <key>hash</key>
          <string>d98078791ce345bf6168ce9ba53ca2d7</string>
          <key>url</key>
          <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/3p-boost/rev/222752/arch/Darwin/installer/boost-1.45.0-darwin-20110304.tar.bz2</string>
        </map>
        <key>name</key>
        <string>darwin</string>
      </map>
      <key>linux</key>
      <map>
        <key>archive</key>
        <map>
          <key>hash</key>
          <!--<string>a34e7fffdb94a6a4d8a2966b1f216da3</string>-->
          <string>2523af5082f44628e553635de6bbea70</string>
          <key>url</key>
          <!--<string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.45.0-linux-20110310.tar.bz2</string>-->
          <string>https://bitbucket.org/LightDrake/public-libs/downloads/boost-1.45.0-linux-20120213.tar.bz2</string>
        </map>
        <key>name</key>
        <string>linux</string>
      </map>
      <key>windows</key>
      <map>
        <key>archive</key>
        <map>
          <key>hash</key>
          <string>98be22c8833aa2bca184b9fa09fbb82b</string>
          <key>url</key>
          <string>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/boost-1.45.0-windows-20110124.tar.bz2</string>
        </map>
        <key>name</key>
        <string>windows</string>
      </map>
    </map>
  </map>

I’ve been building with the Linux 1.45.0 version; if you try the more recent Linux version or the Windows/Darwin versions let me know how it goes! Using this new library leads to a rather nasty namespace collision (at least with the Linux 1.45.0 version) for which I have uploaded a fix.

I’ve also put an example Arduino sketch on Bitbucket, which uses an HMC6343 accelerometer/magnetometer & a u-blox MAX-6 GPS receiver, which I wrote about previously.

Binary

I’ve uploaded a 32-bit Linux binary to Bitbucket if you just want to try it out without the rigmarole of successfully setting up the (rather particular) build environment.

Usage

Start the viewer & login as normal, then take a look at the Serial menu which contains a single entry Serial Monitor. Click this & you will see something like this;

2013-04-17-145652_956x1041_scrot

Put the path to the serial device & the baudrate into the fields in the Device settings section at the top & click [Connect]. For me using an Arduino on Linux boxes the serial device normally appears at /dev/ttyACM0 or /dev/ttyS0 if it’s the first serial device, /dev/ttyACM1 or /dev/ttyS1 if it’s the second serial device, etc. If you’re using an Arduino & are having trouble finding it, just start the Arduino IDE & look at the Serial Port entry in the Tools menu.

Pangolin expects messages, separated by newline characters, in the following format;

<bearing> <pitch> <roll> <latitude> <longitude>

For example;

183.90 75.80 -59.30 56.339991 -2.7875334

So make sure that your serial device adheres to this message format; the example Arduino sketch linked above does this & might be a useful starting point for you.

The fields in the Anchor settings section are for entering the location of a single point for which you know both the real world latitude/longitude & the corresponding virtual world coordinates along with the scale of the virtual world to the real world – eg if 1m in the real world is represented by 1.2m in the virtual world then enter 1.2 into the Scale field.

Sim X & Sim Y of the anchor point are global; this is necessary to allow Pangolin to work across multiple regions (such as mega regions). Because regions are 256x256m you can easily calculate the global coordinate of a point by doing (256 * region position) + local coordinate. For example, if my anchor point is at 127,203,23 in a region at 1020,1043, then the global X coordinate of the anchor point is (1020 * 256) + 127 = 261247 & the global Y coordinate of the anchor point is (1043 * 256) + 203 = 267211. Height isn’t implemented (vertical accuracy of GPS is substantially worse than horizontal) so just pick a Sim Z of around your sim’s ground level. The latitude & longitude fields have 6 decimal places of accuracy.

Once you have input all of the anchor settings, click [Set] & if everything is okay you should see the data from the serial device in the Received data section & the processed position values in the Calculated data section. The Pause checkbox will stop the fields from updating so you can copy the values, etc. If you get garbled received data then you have probably set the baudrate incorrectly – Pangolin will ignore this data instead of trying to process it & sending your avatar’s movement haywire.

To enable/disable control of the camera & your avatar’s movement from the received/calculated data use the Orientation control & Position control checkboxes in the Controls section. The various spinners in this section allow you to alter the smoothing, high pass filters & frequency of updates. Good values for these will depend upon what hardware/sensors you are using, the scale of your sim, etc.

Does it work?

The code itself works nicely. I tested it walking around the ruins of the cathedral at St Andrews, for which the Open Virtual Worlds group has produced an OpenSim reconstruction, using a MSI WindPad 110W tablet computer. This sim is accessible on our own grid & on OSgrid (search for regions called ‘StAndrewsOVW’) though the OSgrid version is usually older than our local grid.

tablet

Camera control from orientation was a very rewarding experience, but will benefit from a faster update rate than the 10Hz of the HMC6343 that I used.

test

The accuracy attainable from a GPS receiver, even a fairly high-spec one like the u-blox MAX-6 that I used, isn’t enough to have the avatar ‘follow in your footsepts’ as I’d originally envisaged, but is good enough to move/teleport the avatar between different points of interest when you move within a certain distance/threshold of them.

Extending

Hopefully my additions & modifications to the viewer will provide a convenient starting point or reference for other people wishing to interface serial devices with the viewer &/or to leverage real world sensor data for controlling avatar/camera.

The code is well commented throughout (more so than the rest of viewer codebase!) & should be fairly self-explanatory. The commit logs on Bitbucket will reveal where the additions/modifications reside, however as a brief overview to get you started;

  • Terraneo Federico’s AsyncSerial class is at indra/newview/AsyncSerial
  • most of my added functionality resides in indra/newview/llviewerserialmovement
  • see LLAppViewer::mainLoop() in indra/newview/llappviewer.cpp for how the serial functionality is actually started
  • the floater is in indra/newview/skins/default/xui/en/floater_serial_monitor.xml

Credits

As well as the people I’ve mentioned whose code I have used, I received huge amounts of help from various people on the Second Life opensource development IRC channel (#opensl on Freenode) & the opensource-dev mailing list – thanks guys!

Building 32-bit Second Life on 64-bit Arch Linux w/ debootstrap

One of the first things I learned about working with the Second Life viewer’s codebase is that the build environment is very particular. Once I found out that Linden themselves use Debian to build their Linux binaries, I figured that was probably the best approach for me to take. Instead of firing up VirtualBox & deploying a new Debian VM, I discovered a wonderful tool call debootstrap which “which will install a Debian base system into a subdirectory of another, already installed system” & allows me to build a 32-bit viewer using 32-bit Debian on my 64-bit Arch Linux work machine.

I recently set up the same arrangement on my home machine & thought I would document it, both as a reference for myself & to others who may be in a similar situation.

Debootstrap

First install debootstrap from the AUR using your preferred AUR helper.

[cj@yuzuki ~]$ yaourt -S debootstrap

Then create a directory for the Debian system to be installed into.

[root@yuzuki ~]# mkdir /opt/debootstrap32

Then debootstrap like this.

[root@yuzuki ~]# debootstrap --arch i386 squeeze ./debootstrap32 http://ftp.uk.debian.org/debian/

Obviously you can substitute i386 for other architectures, squeeze for other Debian releases & the mirror to one geographically closer to you.

To use the Debian install we chroot into it, using linux32 to report the correct architecture.

[root@yuzuki ~]# linux32 chroot /opt/debootstrap32

I wanted the Debian chroot to be able to access files on the host system (namely the viewer code itself) so what I did next was create a user in the chroot with the same name as my user on the host machine. Note that the prompt has changed now that we’re chroot’ed into Debian.

root@yuzuki:/# adduser cj

And made a mountpoint in the Debian directory tree for the host machines RAID.

root@yuzuki:/# mkdir /array_x

Then the following bash script added to /etc/rc.d/ provides a way to conveniently mount/unmount /dev, /dev/pts, /dev/shm, /tmp, /home & the RAID from the host machine into the debian chroot.

Update – should you ever decide to delete the debootstrap, don’t forget if you have mounted parts of the host filesystem within it (as I very nearly discovered the hard way!).

#!/bin/bash

. /etc/rc.conf
. /etc/rc.d/functions

dirs=(/dev /dev/pts /dev/shm /tmp /home)
case $1 in
    start)
        stat_busy "Starting debootstrap32 chroot"
        for d in "${dirs[@]}"; do
         mount -o bind $d /opt/debootstrap32$d
        done
        mount -o bind /array_x /opt/debootstrap32/array_x
        mount -t proc none /opt/debootstrap32/proc
        mount -t sysfs none /opt/debootstrap32/sys
        add_daemon debootstrap32
        stat_done
        ;;
    stop)
        stat_busy "Stopping debootstrap32 chroot"
        for (( i = ${#dirs[@]} - 1; i >= 0; i-- )); do
         umount "/opt/debootstrap32${dirs[i]}"
        done
        umount /opt/debootstrap32/array_x
        umount /opt/debootstrap32/{proc,sys}
        rm_daemon debootstrap32
        stat_done
        ;;
    restart)
        $0 stop
        sleep 1
        $0 start
        ;;
    *)
        echo "usage: $0 {start|stop|restart}"
esac
exit 0

This can be added to the daemons array in /etc/rc.conf as usual & manually invoked in the usual manner.

[root@yuzuki rc.d]# /etc/rc.d/debootstrap32 foo
usage: /etc/rc.d/debootstrap32 {start|stop|restart}

Second Life dependencies & using ccache

Install the dependencies (including ccache to speed up subsequent compilations of the viewer) listed by the Second Life wiki for building the viewer on Linux.

apt-get install cmake bison flex python g++ make bzip2 ccache libc6-dev libstdc++6 libx11-dev libgl1-mesa-dev libxrender-dev libglu1-mesa-dev zlib1g-dev libssl-dev libogg-dev libpng12-dev libdbus-glib-1-dev libgtk2.0-dev

Update – you may also need to install libxml2 which isn’t listed by the wiki, if the build fails saying it can’t find lxml2.

The easiest way to use ccache is to allow it to ‘masquerade’ as the other compiler commands, so that when you call (for example) gcc it actually calls the ccache version. To do this, you just need to export the ccache bin directory to the beginning of your path. The path is different for Arch & Debian so be careful if you add the export to your .bashrc so that it works every time you login.

// Arch version
export PATH="/usr/lib/ccache/bin:$PATH"

// Debian version
export PATH="/usr/lib/ccache:$PATH"

Now when you do which on gcc or g++ it should return the ccache versions.

[cj@yuzuki ~]$ which g++
/usr/lib/ccache/bin/g++
[cj@yuzuki ~]$ which gcc
/usr/lib/ccache/bin/gcc

Note that the default location that ccache uses to store the actual cache is ~/.ccache which you might want to change (eg to a SSD to increase speed, or away from a SSD to reduce wear). IIRC the default maximum size for the cache is 1GB. You can check the status of the cache via the -s flag.

[cj@yuzuki ~]$ ccache -s
cache directory                     /home/cj/.ccache
cache hit (direct)                     0
cache hit (preprocessed)               0
cache miss                             0
files in cache                         0
cache size                             0 Kbytes
max cache size                       1.0 Gbytes

The viewer is built with autobuild so grab that as per the wiki’s instructions.

hg clone http://hg.secondlife.com/autobuild

And add it to your path, which I’ve done in my .bashrc.

export PATH="/array_x/builds/autobuild/bin:$PATH"

With any luck we can now use our Debian chroot to actually build the viewer using autobuild. I made myself a little bash script to automate the process & to cancel the build if it detects that it isn’t being run in the Debian chroot.

#!/bin/bash

if [ -e /CHROOTdebootstrap32 ] ; then
	echo "Running autobuild configure..."
	autobuild configure -c RelWithDebInfoOS --debug -- -DLL_TESTS:BOOL=OFF -DGCC_DISABLE_FATAL_WARNINGS:BOOL=TRUE
	echo "Done configuring."

	echo "Running autobuild build..."
	autobuild build -c RelWithDebInfoOS --debug
	echo "Done building, have a nice day."
else
	echo "Not running in 32-bit debootstrap, exiting."
fi

This is the chroot… right? Oshi-

I like to make it really obvious when I’m in a chroot so I don’t inadvertently perform some sort of destructive maintenance on what I think is a chroot, only to discover that it is actually the host system & I have to go scrambling for the backups. A simple solution I thought of is simply to create a file in the root of the chroot & check for its existence at / in .bashrc. This check also allows me to use the right path for the export for ccache.

if [ -e /CHROOTdebootstrap32 ] ; then
        PS1="[\u@\h \[\e[1;31m\]CHROOTdebootstrap32$\[\e[0m\] \W]$ "
        export PATH="/usr/lib/ccache/bin:$PATH"
fi

This goes in the .bashrc of my regular user on the host system (because that is mounted to the chroot’s /home) but in the .bashrc of the chroot’s /root user (as the host system’s /root isn’t mounted into the chroot). The result is that the text ‘CHROOTdebootstrap32’ is appended in bright red to the prompt when in the chroot.

Adding a menu & dialog in Second Life

I thought that I would quickly add an option to the menu bar in my Second Life client to pop up a dialog (a window) to display the data streaming in over the serial port & maybe some options to enable/disable controls & whatnot. Unfortunately the page on the wiki for how to add dialogs hasn’t been updated since 2008 & the code/instructions no longer work, so I had to explore the viewer source to see how dialogs are currently implemented & along with help from the mailing list I succeeded.

(Note on terminology – “Windows and dialogs in Second Life are implemented as LLFloater objects” which is why the word ‘floater’ appears a lot).

Dialogs

The content & layout of the floater is defined in an xml file that goes in newview/skins/default/xui/en/floater_serial_monitor.xml (mine is called floater_serial_monitor, you obviously call yours what you want). This very simple example just creates an empty floater & the only point of interest is the layout="topleft" line which means that 0,0 will be in the top left of the floater rather than the default bottom left (OpenGL style).

<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<floater
 border="true"
 can_close="true"
 can_minimize="true"
 can_resize="false"
 save_rect="true"
 height="320"
 width="480"
 name="serial_monitor_floater"
 title="Serial Monitor"
 layout="topleft">

The implementation of the stuff in the floater goes in newview/llfloaterserialmonitor.h & newview/llfloaterserialmonitor.cpp. Of course you need to make the build system aware of these new files, on Linux by references to the relevant sections of newview/CMakeLists.txt.

You then have to register your new floater in newview/llviewerfloaterreg.cpp by adding something like this, along with an include statement at the top of the file like #include 'llfloaterserialmonitor.h".

LLFloaterReg::add("serial_monitor", "floater_serial_monitor.xml", LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSerialMonitor>);

Once you’ve done this, you’ll be able to show the dialog using showInstance.

LLFloaterReg::showInstance("serial_monitor");

For examples from the viewer source, particularly to give you an idea of what to put in the .h/.cpp files, take a look at;

Menus

The wiki page for adding menu items hasn’t been updated since 2009 however is still mostly relevant. So if you want to do display your new dialog from the main menu bar that runs along the top of the viewer window, first add a menu item to an existing menu on the menu bar, or add your own menu to the menu bar & then add a menu item to that, by editing newview/skins/default/xui/en/menu_viewer.xml. This example adds a menu called ‘Serial’ that contains a single item called ‘Serial Monitor’.

<menu
      create_jump_keys="true"
      label="Serial"
      name="Serial"
      tear_off="true">

      <menu_item_call
        label="Serial Monitor"
        name="Serial Monitor">
        <menu_item_call.on_click
          function="ShowSerialMonitor"
          parameter="agent" />
        </menu_item_call>
    </menu>

The function line refers to a method you add to newview/llviewermenu.cpp, first by adding a listener.

view_listener_t::addMenu(new LLShowSerialMonitor(), "ShowSerialMonitor");

Then by adding an event handler class that uses the showInstance method mentioned above.

class LLShowSerialMonitor : public view_listener_t
{
	bool handleEvent(const LLSD& userdata)
	{
		LLFloaterReg::showInstance("serial_monitor",userdata);
		return true;
	}
};

So now you know how to add a menu entry that displays a blank dialog, but you still need to put something in it…

Arduino + HMC6343 + u-blox MAX-6

Part of my investigation into simultaneous presence in real & virtual environments involves streaming real world position, heading & orientation data into a modified Second Life viewer. After much faffing about evaluation of different accelerometers, magnetometers & GPS receivers, as well as learning about GNSS & exciting concepts like magnetic declination, hard/soft iron offsets & tilt compensation, the result is an Arduino with an HMC6343 tilt-compensated magnetometer & a u-blox MAX-6 GPS receiver.

For the interaction style that I want to investigate with the VTW project I need to know 3 things about the user;

  • position – where they are
  • heading – the direction they are looking
  • pitch – the angle they are facing (up, down, level)

Pitch is the easiest of the three – you simply use an accelerometer. I tried both the ADXL335 with signal conditioned voltage outputs & the MMA8452 which uses I2C. These are both 3-axis accelerometers so can tell you roll, pitch & yaw all at the same time. They both did what I wanted, but the MMA8452 was easier to use as it didn’t involve experimenting to find out what values represented the rest/peak positions of each axis like the ADXL335 did.

Heading (by which I mean a compass heading) also seems easy to begin with – you just use a 3-axis magnetometer (aka ‘digital compass’ or ‘e-compass’) & perform a calculation that uses the readings of the 2 axes that represent pitch & roll. I tried the HMC5883L but soon discovered that things get much more complicated if you aren’t holding the magnetometer flat – because the calculation doesn’t take the third axis into consideration, the heading becomes more useless the more you tilt it! To calculate a heading even when not held level (which is important for many applications) you need to integrate the reading from the third axis, which can be thought of as recording the magnetic field lost by the other two when tilted out of alignment, into the heading calculation. But to do this you need to know how the magnetometer is tilted – good thing we have a 3-axis accelerometer!

There are a number of good tutorials/guides for this, including these two that I followed, however if you are lazy &/or scared by maths you can also buy ICs such as the HMC6343 that combine a 3-axis accelerometer with a 3-axis magnetometer & perform the tilt compensation algorithms onboard.

Of course there must be a downside to this convenience & most obvious is the price. If you are a SparkFun sort of person the HMC6343 will set you back $149.95. To put that into perspective the ADXL335 is $24.95, the MMA8452 a paltry $9.95 & the HMC5883L just $14.95.

The HMC6343

The HMC6343 ‘3-Axis Compass with Algorithms’ is an I2C device so is easy to hook up (SDA to analog 4, SCL to analog 5 on an Arduino Uno) & interact with. The datasheet contains the protocol definition & communicating with it looks something like this (the example here changes the rate of measurements from the default of 5Hz to the fastest of 10Hz). HMC6343_ADDRESS is 0x19 & obviously you will also need to include Wire.h at the top of your sketch.

Wire.beginTransmission(HMC6343_ADDRESS);
Wire.write(0xF1);
Wire.write(0x05);
Wire.write(0x02);
Wire.endTransmission();

The first Wire.write tells the device what command we want to run, in this example 0xF1 is the ‘Write to EEPROM’ command that expects 2 binary byte arguments. The first argument is which register we want to write to, in this example 0x05 is the address of ‘Operational Mode Register 2’ which stores the measurement speed. The second argument is the value that we actually want to write to this register & in this case 0x02 means that we want measurements at 10Hz. Simple enough, no?

If you look through the sketch you’ll see this pattern repeated for setting the variation angle correcton, the Infinite Impulse Response filter & temporarily changing the orientation (so that you can mount the IC in different orientations without having to manually swap the axes, which is very handy).

The HMC6343 also allows you to access just the accelerometer readings, separately from the tilt-compensated magnetometer readings, so this single IC gives me both the (tilt compensated) heading & the pitch.

One more handy feature of the HMC6343 is the hard-iron offset calibration. Metallic stuff around the chip (wires, the GPS module, the Arduino, etc.) can affect the magnetic field detected by the magneto resistors & throw off the readings, so we can compensate for this using the built-in calibration mode. Read the datasheet to see how this works, but essentially all you have to do is send command 0x71, rotate the device in a certain manner, then send command 0x7E.

In case you’re wondering what the difference between hard-iron offset & soft-iron offset is, the former is for things that won’t change during operation (eg the wires connecting the IC to the Arduino are always going to be there) whereas the latter refers to things that will change during operation (such as moving the IC around your lodestone collection). Hard-iron only need be calculated once, whereas soft-iron requires dynamic attention.

u-blox MAX-6

My research into GNSS led me to the u-blox MAX-6 as my best option. Impressive specification, solid performance & compatibility with SBAS including EGNOS in Europe (Wikipedia page on GNSS augmentation/SBAS here). It turns out that it’s a popular GPS receiver amongst high-altitude ballooners (the kind of people who attach Arduinos & cameras to weather balloons & let them go) as it operates correctly at high altitudes which some receivers evidently don’t. So after some discussion on the #highaltitude channel on freenode I ended up ordering a MAX-6 with the appropriate level conversion to work with an Arduino (the MAX-6 uses 3v logic, whereas the Arduino uses 5v, so conversion is required so as not to fry the module). I ordered it from here & based my sketch on the example code here.

The MAX-6 is a serial device (no convenient I2C here) so interfacing with an Arduino is slightly different. The wiki page linked above recommends not using Software Serial, however as I also want to run the HMC6343 on the same Arduino I have no choice – so far I haven’t experienced any trouble with it.

The protocol specification for the MAX-6 is substantially longer & the messages much more complex than the HMC6343. If you want to have a go at assembling the messages by hand you can do so by reading the 220+ page document & I actually had some success with this, however then discovered that the easier way is to use the u-center software which allows you to configure the receiver from Windows using tickboxes & menus then see/copy the messages from the relevant console window to paste into your sketch. The u-center software worked fine for me in a Windows 7 VM running on a Linux host, using pins 0 & 1 on an Arduino to connect the receiver directly to the USB connection via the Arduino’s UART.

Once you have your message, sending it to the receiver looks something like this (the example here sets the dynamic platform model to ‘pedestrian’);

uint8_t CFG_NAV5[] = {0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF,
                    0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27,
                    0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00,
                    0x64, 0x00, 0x2C, 0x01, 0x32, 0x3C, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                    0x00, 0x00, 0x00, 0x00};
calculateUBXChecksum(CFG_NAV5, (sizeof(CFG_NAV5)/sizeof(uint8_t)));

while (!success)
{
  sendUBX(CFG_NAV5, (sizeof(CFG_NAV5)/sizeof(uint8_t)));
  success = getUBX_ACK(CFG_NAV5);
}

The first thing we do is create an array of type unit8_t that contains the message itself. The final 2 hex values are the checksum & the call to calculateUBXChecksum on line 2 calculates these values & substitutes them into the array. To send the message we use the sendUBX method & keep on trying until the receiver sends us a confirmation.

Again you will see this pattern repeated a number of times in the sketch, to set the dynamic platform model, enable SBAS using EGNOS, disable all the messages that I don’t want & enable the ones that I do want.

Getting readings

Getting data from the HMC6343 is a simple case of sending the correct command (0x50), requesting the correct number of bytes & then reading them into sensible variables (heading, pitch, etc.).

Getting data from the MAX-6 isn’t much harder, using SoftwareSerial.available() & SoftwareSerial.Read(), however as with most GPS receivers the data that it returns is in the form of NMEA 0183 messages which look a bit like this;

$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68

It’s not hard to parse it yourself, but why go to the effort when there are libraries like TinyGPS that can do it for you?

…using readings?

So what was the point of all this again? Well, my modified Second Life client listens on the serial port using Boost.Asio, receives these messages & then uses then to control avatar & camera – more on this soon!

OpenSim Region Modules & GPS

Something else I did late last year/early this year, investigating how one might combine OpenSim Region Modules with GPS readings to control movement of an avatar & other exciting things, which weren’t developed to fruition but may be of interest to somebody.


From the OpenSim wiki

Region modules are .net/mono DLLs. During initialization of the simulator, the OpenSimulator bin directory (bin/) and the scriptengines (bin/ScriptEngines) directory are scanned for DLLs, in an attempt to load region modules stored there.

Region modules execute within the heart of the simulator and have access to all its facilities. Typically, region modules register for a number of events, e.g. chat messages, user logins, texture transfers, and take what ever steps are appropriate for the purposes of the module.

Essentially they allow you to develop much more complex extensions to the OpenSim platform than in-world LSL does, but are easier & more accessible than directly modifying the client &/or server source code.

Instructions for making your own region modules can be found on the OpenSim wiki (follow the link above) but I also wrote a heavily commented version of the boilerplate code for a shared region module that might make it easier to understand which parts you actually need to implement/change to get a basic region module doing something. This was the first time I had worked with C# so apologies if some of the comments seem superfluous ;)

Instead of having hundreds of lines of code in this post, I’ve put all of the examples in public Bitbucket repositories – here is the first one for the boilerplate region module code. All you really need to do is change names in a few places & then add some functionality starting in PostInitialise() to get a basic region module that can result in some visible effect in world.

One of the most basic visible effects is making something move & this example does just that. Despite its name it doesn’t actually have anything to do with GPS yet, it simply creates a spherical prim in each region & moves it a short distance on each tick of a timer. This shows the most basic usage of the SceneObjectGroup class to get a reference to a primitive in a scene & then do something with it (move it).

Moving onto something that actually begins to involve GPS, or at least begins to make some connection between real world latitude & longitude values & ‘equivalent’ positions in the virtual world, this next example waits for a latitude & longitude to be reported via a TCP connection & then moves the avatar to the equivalent position in the region. This approach assumes that there is one position within the OpenSim region for which the equivalent real world latitude & longitude is known (referred to as the ‘anchor’ point) & that the scale of the OpenSim region compared to the real world is also known (eg that every meter in the real world is represented by 1.2m in the OpenSim region).

When a latitude & longitude is received via TCP the haversine formula is used to calculate the real world ‘great circle’ distance between the anchor point & this new point. This distance is then scaled according to the scale of the real world to the OpenSim region & thus the equivalent OpenSim position is calculated as a displacement from the anchor point – to which the avatar is then moved.

This is a fairly ‘rough & ready’ proof-of-concept – the avatar’s name & the position of the anchor point are currently hard-coded & movement across region boundaries isn’t supported. The implementation of the haversine formula & the GPSSanitizer method (which checks for both dms & signed decimal latitude/longitude representations using regular expressions) may be useful to other applications. It has also been tested by manually piping in latitude/longitude values via a simple TCP client – a rudimentary example included below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace TCPClient {

    /*
     * A very simple TCP client test program. When run (with no command line arguments) it will send a message from the CLI to
     * localhost port 13000 via a TCP connection. The message, address & port can be changed by changing the hardcoded values.
     */
    class SimpleTcpClient {

        static void Main(string[] args) {
            Console.WriteLine("Enter GPS value to move to (eg '56.340626, -2.808015' or '56 20 26 N, 2 48 28 W'");
            string s = Console.In.ReadLine();
            Connect("127.0.0.1", s);
        }

        static void Connect(String server, String message) {
            try {
                // Create a TcpClient.
                // Note, for this client to work you need to have a TcpServer 
                // connected to the same address as specified by the server, port
                // combination.
                Int32 port = 13000;
                TcpClient client = new TcpClient(server, port);

                // Translate the passed message into ASCII and store it as a Byte array.
                Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);

                // Get a client stream for reading and writing.
                NetworkStream stream = client.GetStream();

                // Send the message to the connected TcpServer. 
                stream.Write(data, 0, data.Length);

                Console.WriteLine("Sent: {0}", message);

                //send & forget, don't bother waiting for a response

                // Close everything.
                client.Close();
            }
            catch (ArgumentNullException e) {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e) {
                Console.WriteLine("SocketException: {0}", e);
            }

            Console.WriteLine("\n Press Enter to continue...");
            Console.Read();
        }
    }
}

The final example is something a bit different, but still GPS related. This one takes a latitude & longitude via TCP in the same manner as the previous example, but instead of moving something it instead queries the Google Maps API for a satellite image centered about this position & applies this image as a texture to a prim that covers the entire region.

This was written to make testing of the previous example easier – it’s easier to visualise whether the movements of the avatar are correct if s/he is walking over imagery of the real world than blank terrain. This was written a long time ago but I think I started extending it such that when an avatar moved into a neighbouring region it would automatically be textured with another satellite image – at any rate the code is in a very unfinished state, but please feel free to harvest any bits that might be useful to you.


It became apparent during these experiments that it would make more sense to handle GPS/accelerometer/magnetometer avatar movement on the client side rather than the server side, thus these experiments were abandoned – however they still serve as an interesting demonstration of what can be achieved with region modules & a bit of imagination :)

In addition, much of the GPSAvatar code will be transferred into my modified Second Life viewer & will help to speed up development there.