Introduction

Using pbLua for controlling robots made with a LEGO MINDSTORMS NXT is fun and easy, but what if you wanted to use the NXT for something really outlandish. Something that needs access to GPS data. Something like a UAV.

The standard LEGO firmware supports Bluetooth communication between NXTs and between a host PC and the NXT, but it does not handle arbitrary data from a Bluetooth device. Now that pbLua has support for streaming data from Bluetooth devices, it's a fairly simple matter to parse GPS data from a portable receiver.

This article shows you how to use the Bluetooth API in pbLua and the example will be to read GPS data.

To get started, download the most recent pbLua distribution and unpack it somewhere on your hard drive. It does not really matter where it goes, as long as you can remember the path later.

Contents

The pbLua Bluetooth API

Like the other functions built into pbLua to support the NXT hardware, the Bluetooth API has its own set of functions. They are fully described in the Bluetooth section of the pbLua API document.

What we're going to do here is go over some basic concepts that you'll use over and over again. Most of the samples of code described here are avaialble in the sample code directory of the pbLua distribution.

The Bluetooth radio can be a significant power drain on the NXT brick, so pbLua gives you the capability to turn it off and on. The nice thing about the Bluetooth system is that it remembers the settings and devices you have connected to it even when it's powered off.

  -- Turn the Bluetooth radio off:
  
  nxt.BtPower(0)
  
  -- Turn the Bluetooth radio on:

  nxt.BtPower()
  
  -- or
    
  nxt.BtPower(1)

Many of the Bluetooth API calls have default values for their main parameter, so calls like nxt.BtPower() are valid, and turn the radio on. If you want to be explicit, you can always write nxt.BtPower(1).

The other Bluetooth API calls that you might use from time to time include:

  -- Reset the Bluetooth subsystem to factory defaults. Note theat this does
  -- not reset the firmware, just the device tables. You'll need to do a fresh
  -- search of the Bluetooth devices
  
  nxt.BtFactoryReset()
  
  -- Turn the visibility of the NXT on:
  
  nxt.BtVisible() 
  
  -- Turn the visibility of the NXT off:
  
  nxt.BtVisible(0)
  
  -- Start searching for other visible Bluetooth devices:
  
  nxt.BtSearch()
  
  -- Abort the search:
  
  nxt.BtSearch(0)

When programming the Blutooth operations, you'll want to be able to discover the state of the Bluetooth subsystem that's running in the NXT. The new state is evaluated once every millisecond. For a detailed description refer to the nxt.BtGetStatus() call description in the Bluetooth section of the pbLua API document.

The most common thing to do is to wait until the Bluetooth system is back in the idle state, like this:

  -- Turn on the Bluetoorh radio, make the NXT visible and search for
  -- other devices
  
  function btIdleWait()
    repeat
      state, active = nxtBtGetStatus()
    until 17 == active
  end
       
  nxt.BtPower()
  btIdleWait()
  
  nxt.BtVisible()
  btIdleWait()

  nxt.BtSearch() -- This takes about 20 seconds!
  btIdleWait()
  
  -- Now you're ready to use the Bluetooth system!

Note that it's not always required to do a search for Bluetooth devices every time you fire up the NXT. The Bluetooth system remembers devices it has found between power cycles. If you add a new device and want to use it, you will have to do a search though.

Bluetooth Names and PINs

There a couple of additional calls that will help you to customize your pbLua powered NXT to distinguish it from other NXTs. The name is used to provide your NXT with a human readable identifier. The Bluetooth subsystem does not care what you call your NXT, as long as the name has 15 or fewer characters.

Similarly, the PIN is used to establish a "secure" connection with another Bluetooth equipped device. The PIN is up to 16 characters long.

Let's start by making sure we can see the NXT from the PC. The Bluetooth system is normally on by default, and it remembers the visibility state, but just in case, type the following text to the NXT console.

  nxt.BtPower()
  nxt.BtVisible(0)

Using the Bluetooth system on your computer, search for new devices. If you typed in the text above, the NXT will not be found. That's because we told it to be invisible, so let's fix that by typing this:

  nxt.BtVisible()

This time, your computer should have found your NXT and told you its name and possibly its Bluetooth MAC address. If you have been following along and done a nxt.BtFactoryReset() the name will now be "NXT" by default. If you want to change the name of your brick, you can do this:

  nxt.BtSetName("Frodo")

You don't have to set the name to a Lord Of The Rings character. You can use any name you want as long as it has 15 or fewer characters.

Once you've found your NXT in the list of new Bluetooth devices you can continue with the process of connecting to it. In order to make it a bit easier to understand what's going on, we'll use a handy little utility the monitors the state of the Bluetooth system on the NXT and tells us when something changes.

-- btMonitor

function btMonitor( n )
  local t = nxt.TimerRead()
  local oldState, oldActive, oldUpdate
  
  repeat
    state, active, update = nxt.BtGetStatus()
    if (state ~= oldState) or (active ~= oldActive) or (update ~= oldUpdate) then
      print( nxt.TimerRead() - t, state, active, update )
      oldState  = state
      oldActive = active
      oldUpdate = update
    end
  until t+n < nxt.TimerRead()
end
    

Once you've loaded the btMonitor into the NXT, let's run through the connection process from the point of view of the computer. Before your start up your Bluetooth detection utility type this to your NXT console:

  btMonitor(30000)

This will monitor your Bluetooth connection for 30 seconds. Now start scanning for new Bluetooth devices on your PC. You'll find your NXT, so try to connect to it and watch what happens to the state of the NXT on the console. You should see something like this:

0       1       17      0
23112   193     6       0

If you've been following along, you'll note that the transition from 1 to 193 in the second value (state) represents the fact that besides being visible (1), the NXT is looking at a connection request (64) and a PIN request (128). Add them all together and you get 193!

Also, the third value (active) goes from UPD_IDLE (17) to UPD_PINREQ (6).

All this is very interesting, but how do we actually get the PINs exchanged? We could either write a little script to handle it, but since you only have to pair once, it's probably easier to just do it manually.

By now, your original pairing attempt should have failed, so just restart it and when the computer asks for a PIN code, type your PIN into the dialog box and THEN type the following at the NXT console:

nxt.BtSetPIN("yourPIN")  -- Put your own PIN in the quotes!

That's all there is to it! The pairing should be successful. The computer may have assigned a default COM port to the connection with your NXT. That's the one we'll use to verify the next step which is connecting to the NXT over Bluetooth!

Bluetooth Devices and Connections

Now that you've got some fundamental Bluetooth API calls under your belt, let's put them to use, starting with connecting to your computer's Bluetooth radio. But before we do that, it would be nice if we could get a quick look at the devices that the NXT has found. Download the following sample script to the NXT:

-- btDevice.txt
-- Dump n entries in the Bluetooth device table

function btDevice( n )
  for idx=0,n do
    name, class, addr, status = nxt.BtGetDeviceEntry( idx )
   
    -- Format the BT device address
    addr = string.format("%02x:%02x:%02x:%02x:%02x:%02x", string.byte( addr, 1, 6 ) )
   
    -- Format the BT class
    class = string.format("%02x:%02x:%02x:%02x", string.byte( class, 1, 4 ))

    -- Print the device info
    print(string.format("Name:%16s Addr:%s Class:%s Status:%i",name,addr,class,status))
  end
end

Now, type the first line below to dump the first 10 (out of a possible 30) devices the NXT knows about and and you'll probably get the following reply:

> btDevice(10)
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
>

Hmmm, what's going on here? There's no devices in the table. Let's fix that by originating a search from the NXT, which can take up to 30 seconds...

> nxt.BtSearch()
> btMonitor(30000)
0       65      10      3
13822   65      10      4
13823   65      10      5
15630   65      10      4
15631   65      17      0

When this returns the IDLE state (17) then type the following to get the first 4 Bluetooth devices the NXT knows about...

> btDevice(4)
Name:  Ralph DellD610 Addr:00:10:c6:62:f6:ba Class:00:1c:01:0c Status:65
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
>

Cool! There's my PC! And the status is telling me that there's still a pending connection request. That's not current anymore, but now let's try to establish a connection to it.

The NXT has up to 4 connection channels, the first one is 0 and it's reserved for incoming connections. The other three chanels can be used for connections originating at the NXT to another device.

If you're still paying attention, you'll expect another script to summarize the connection states, and here it is, straight from the sample directory:

-- btConnect.txt
-- Dump all the connection entries in the Bluetooth connection table

function  btConnect()
  for idx=0,3 do
    name, class, pin, addr, handle, status, linkq = nxt.BtGetConnectEntry( idx )

    -- Format the BT device address
    addr = string.format("%02x:%02x:%02x:%02x:%02x:%02x", string.byte( addr, 1, 6 ) )

    -- Format the BT class
    class = string.format("%02x:%02x:%02x:%02x", string.byte( class, 1, 4 ))

    -- Print the connection info
    print(string.format("Name:%16s Addr:%s Class:%s PIN:%s Status:%i",name,addr,class,pin,status))
  end
end

And then you can summarize the connections like this:

> btConnect()
Name:                 Addr:00:10:c6:62:f6:ba Class:00:00:00:00 PIN:xyzzy Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0

Maybe you don't want to print that PIN code too much...

The connection is not ready for streaming, as can be seen by the status of 0. Let's power cycle the NXT, reconnect to the console and start fresh. All the playing we're doing probably has an incomplete connection pending.

All done? Good, let's verify that the NXT knows about your computer and then try to connect it (device 0) to an outgoing connection (1). Reload the btDevice.txt and btConnect.txt sample scripts...

Verify that you can see your computer from the NXT using btDevice(4). Then try to connect to channel 0, the outgoing connection channel:

> nxt.BtConnect(0,0)  -- Device 0, connection 0

The connection may have worked, but sometimes it's only possible to connect from the computer to the NXT - depending on security settings. I'd really appreciate some feedback on this so I cna sort out the situation a bit better. This was all working on my laptop until I upgraded the Bluetooth driver...

If you do manage to connect from the computer or the NXT, dump the connection table, like this:

 > btConnect()
Name:  Ralph DellD610 Addr:00:10:c6:62:f6:ba Class:f8:7a:01:0c PIN: Status:1
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 PIN: Status:0

> nxt.BtStreamMode(1)
> nxt.BtStreamSend(0,"Hello world!")
> nxt.BtDisconnect(0)

You should see the prototypical programmer's greeting on your PC screen! That's all great, but how are we going to connect to a GPS? That's next.

Example: Connecting to a GPS

Now it's time to put it all together and learn a few new pbLua tricks in the process. Someone donated a Navibe GB735 GPS receiver to the project so we'll use that in our example.

First, we'll search for new devices and verify that we can actually see the Navibe:

> nxt.BtSearch()
> btDevice(4)
Name:  Ralph DellD610 Addr:00:10:c6:62:f6:ba Class:f8:7a:01:0c Status:66
Name:      BT GPS V10 Addr:00:0a:3a:24:33:97 Class:00:00:1f:00 Status:65
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0
Name:                 Addr:00:00:00:00:00:00 Class:00:00:00:00 Status:0

Yes, it's there. SO the next thing to do is connect to it. Note that I've already paired to my GPS, so I'm not sure how to describe that. When you do a connection and monitor using btMonitor(), you'll probably see it enter the PINREQ state where the Navibe has asked the NXT for a matching PIN. The Navibe default is "0000" if I recall.

> nxt.BtConnect(1,1) -- Device 1, connection 1
> -- You may have to also set the PIN for successful connection...
> nxt.BtSetPIN("0000")

When the connection completes, the Navibe Blue LED will turn on steady. Now we're reasy for some really simple GPS parsing. The standard GPS output from the Navibe looks something like this:

$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06
$GPRMC,140817.000,V,4433.2983,N,08056.3970,W,1.42,77.42,020707,,,E*51
$GPGGA,140818.000,4433.2984,N,08056.3964,W,6,00,50.0,168.7,M,-36.0,M,,0000*5C
$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06
$GPRMC,140818.000,V,4433.2984,N,08056.3964,W,1.42,77.42,020707,,,E*5C
$GPGGA,140819.000,4433.2984,N,08056.3959,W,6,00,50.0,168.7,M,-36.0,M,,0000*53
$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06
$GPGSV,3,1,10,18,71,320,30,21,62,184,32,09,46,130,,22,36,290,20*79
$GPGSV,3,2,10,24,29,154,,26,28,050,22,29,18,052,,03,13,300,*78
$GPGSV,3,3,10,14,12,233,,19,07,327,*75
$GPRMC,140819.000,V,4433.2984,N,08056.3959,W,1.42,77.42,020707,,,E*53
$GPGGA,140820.000,4433.2985,N,08056.3954,W,6,00,50.0,168.7,M,-36.0,M,,0000*55
$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06
$GPRMC,140820.000,V,4433.2985,N,08056.3954,W,1.42,77.42,020707,,,E*55
$GPGGA,140821.000,4433.2985,N,08056.3948,W,6,00,50.0,168.7,M,-36.0,M,,0000*59
$GPGSA,A,2,,,,,,,,,,,,,50.0,50.0,50.0*06

The strings we're interested in have the $GPGGA prefix, they give some general info like latitude, longitude, estimated altitude, time, etc. If you want to get really fancy, you can easily parse other GPS strings based on the example.

The GPS parsing routines count on the fact that every GPS string starts with $GP, so we're going to look for these characters and crop out an entire string from $GP to just before the next $GP, and then pass that to the parser.

To see what the NXT sees when it queries the Bluetooth connection, type this:

> nxt.BtStreamMode(1)
> print(nxt.BtStreamRecv())
128     $GPGGA,142149.000,4433.2814,N,08056.3932,W,1,03,4.0,228.4,M,-36.0,M,,0000*69
$GPGSA,A,2,21,09,24,,,,,,,,,,4.2,4.0,1.0*3C
$GPGS

Yes! It's some very basic GPS Stream data. Rather than bore you with all the details right now, I'm just going to post the example code to parse the basic GPS data and put it on the NXT screen...


-- btGPS(timeout) - dumps GPS data for timeout milliseconds

function parseGPGGA (s)
   print( s )
   _,_,time,lat,ns,long,ew,_,_,_,alt = string.find(s, "([^,]+),([^,]+),([NS]),([^,]+),([EW]),([^,]+),([^,]+),([^,]+),([^,]+)")
   
   -- If we have a valid string, then update the NXT display
   
   if( nil ~= alt ) then
     print( time,lat,ns,long,ew,alt )
     nxt.DisplayText( string.format( "Time %s",  time),       0,  0 )
     nxt.DisplayText( string.format( "Lat  %s%s", lat, ns),   0,  8 )
     nxt.DisplayText( string.format( "Long %s%s", long, ew),  0, 16 )
     nxt.DisplayText( string.format( "Alt  %sm",  alt),       0, 24 )
   end
end

function btGPS( timeout )
  local t=nxt.TimerRead()
  local len, s
  local gps = ""
  
  nxt.DisplayClear()
  
  -- Do a dummy send to get the NXT to accept streaming data
  nxt.BtStreamSend(1,"")

  -- And now loop until we get fully formed GPS messages
  while( t+timeout > nxt.TimerRead() ) do
  
    -- Get and available GPS data from the BT device
    len,s = nxt.BtStreamRecv()
 

    if len>0 then
--      print( "--" .. s )
      
      gps = gps .. s
      
      start = string.find( gps, "\$GP", 2 )
      
      while nil ~= start do
      
        if start > 1 then
          -- Here's where we pull out an entire $GP string
          
          data = string.sub( gps, 1, start-2 )
          _,_,sentence,data = string.find( data, "\$GP(%u+),(.+)" )
          
          -- And check to see if it's one we're interested in
          
          if "GGA" == sentence then
            parseGPGGA( data )
          end
          
          -- Here's where you can parse other strings...
          
          -- And now skip over the string we just parsed to get ready for
          -- the next one 
          
          gps = string.sub( gps, start, -1 )
        
        end
        
        start = string.find( gps, "\$GP", 2 )
      end

    end
  end
end


And to get the GPS data displayed on your screen, just type:

btGPS(20000)
143115.000      4433.3073       N       08056.3969      W       172.7
143116.000,433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*65
143116.000      433.3073        N       08056.3969      W       172.7
143117.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*64
143117.000      4433.3073       N       08056.3969      W       172.7
143118.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*6B
143118.000      4433.3073       N       08056.3969      W       172.7
143119.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*6A
143119.000      4433.3073       N       08056.3969      W       172.7
143120.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*60
143120.000      4433.3073       N       08056.3969      W       172.7
143121.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*61
143121.000      4433.3073       N       08056.3969      W       172.7
143122.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*62
143122.000      4433.3073       N       08056.3969      W       172.7
143123.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*63
143123.000      4433.3073       N       08056.3969      W       172.7
143124.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*64
143124.000      4433.3073       N       08056.3969      W       172.7
143125.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*65
143125.000      4433.3073       N       08056.3969      W       172.7
143126.000,4433.3073,N,08056.3969,W,1,03,2.8,172.7,M,-36.0,M,,0000*66
143126.000      4433.3073       N       08056.3969      W       172.7

For 20 seconds of GPS data updated in real time on your NXT display!

Have fun, and don't forget to contact me if you have success!