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.
Remember to refer to the online version of Programming in Lua . It is a reference that you should be looking at in parallel with this text.
By the end of this tutorial, you’ll be able to use the Bluetooth system on your NXT to communicate with your PC, other NXTs, and even other Bluetooth devices that support the Serial Port Profile.
- The Basic pbLua Bluetooth API
- Bluetooth Names and PINs
- Bluetooth Devices and Connections
- Example: Connecting to a GPS
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() local active repeat _,active = nxt.BtGetStatus() 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 even after you power down the NXT. If you get a new NXT or computer and want to use it with your current NXT, you will have to do a new search.
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.
-- Turn on the Bluetooth system but make the NXT invisible to discovery 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:
-- Make the NXT visible to discovery 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:
-- Change the name of the NXT 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. Do another search to find your newly renamed NXT.
-- Search for the NXT nxt.BtSearch() -- This takes about 20 seconds! btIdleWait()
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(n) monitors the Bluetooth system for n seconds and -- prints a message any time there's a change 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() function into the NXT, let’s run through the connection process from the point of view of the computer. Before your start up the Bluetooth detection utility on your PC, type this to your NXT console:
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, and have read the Bluetooth section of the pbLua API document, 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:
-- Set the PIN in your NXT 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!
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 in previous seraches. Download the following sample script to the NXT:
-- btDevice(n) dumps the first n entries in the Bluetooth device table function btDevice( n ) for idx=0,n-1 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:
-- Dump the first 4 entries in the device table btDevice(4) -- Results are below, do not paste the following text to the console! 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…
-- Initiate a new search for Bluetooth devices nxt.BtSearch() btMonitor(30000) -- Results are below, do not paste the following text to the console! 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…
-- Dump the first 4 entries in the device table btDevice(4) -- Results are below, do not paste the following text to the console! 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
Cool! There’s my laptop! 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:
-- btConnect() dumps 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:
-- Summarize the Bluetooth connection table btConnect() -- Results are below, do not paste the following text to the console! 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 (0). Reload the btDevice() and btConnect() sample scripts from the previous section.
Verify that you can see your computer from the NXT using btDevice(4). Then try to connect to channel 0, the outgoing connection channel:
-- Connect device 0 to channel 0 nxt.BtConnect(0,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 can sort out the situation a bit better.
Open up the COM port that was assigned to the NXT by your computer’s Bluetooth manager. When I say open up, I mean connect using a terminal emulator such as Hyperterminal (yuck) or PuTTY.
If you do manage to connect from the computer or the NXT, dump the connection table, like this:
-- Dump the connection table again btConnect() -- Results are below, do not paste the following text to the console! 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
Note the Status, it’s 1 which means we’re connected! Now we can send text to the PC via Bluetooth and it should show up on the terminal emulator theat’s connected to the Bluetooth rasio on the PC.
-- Put the NXT Bluetooth into raw streaming mode, and send some text nxt.BtStreamMode(1) nxt.BtStreamSend(0,"Hello world!") -- And disconnect if we're done... nxt.BtDisconnect(0)
You should see the prototypical programmer’s greeting on the terminal emulator window that’s connected to the Bluetooth COM port.
That’s all great, but how are we going to connect to a GPS? That’s next.
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 turn on the Navibe and search for new devices to verify that we can actually see the Navibe:
-- Search for the Navibe nxt.BtSearch() -- Wait 20 seconds, then dump the device table to see if we can find it btDevice(4) -- Results are below, do not paste the following text to the console! 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
Yes, it’s there. So the next thing to do is connect to it. Note that I’ve already paired to my GPS. If you attempt to connect to the Navibe with a new device, you’ll get a pairing request.
If you were to monitor the state of the Bluetooth system during this attempt, you’ll probably see the NXT enter the PINREQ state where the Navibe has asked the NXT for a matching PIN. The Navibe default is “0000″ if I recall.
Here’s how I paired my NXT with the Navibe:
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:
-- Connect, set to stream mode, and then send a dummy character to -- get communications running. Remember to have btIdleWait() loaded ! nxt.BtConnect(1,1) btIdleWait() nxt.BtStreamMode(1) nxt.BtStreamSend(1,"") -- Wait a few seconds for BT data to accumulate, then enter: print(nxt.BtStreamRecv()) -- Results are below, do not paste the following text to the console! $GPRMC,170837.063,A,4433.3037,N,08056.4227,W,3.28,309.27,161108,,,D*70 $GPGGA,170838.063,4433.3023,N,08056.4205,W,2,03,2 -- Now disconnect nxt.BtDisconnect(1)
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…
-- Code to parse out a GPGGA string 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 -- Main routine that looks for strings from the function btGPS( timeout ) local t=nxt.TimerRead() local s local gps = "" nxt.DisplayClear() -- 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 s = nxt.BtStreamRecv() if s 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:
-- Set up the connection to the Navibe GPS nxt.BtConnect(1,1) -- Device 1, connection 1 btIdleWait() -- Put the strean into binary mode nxt.BtStreamMode(1) -- Do a dummy send to get the NXT to accept streaming data nxt.BtStreamSend(1,"") -- And now start reading for 5 seconds btGPS(5000) -- Results are below, do not paste the following text to the console! 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
For 5 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!