messages
Communication with a NXT can be useful to test program chunks, drive a running program, receive information to be processed on a regular computer or anything else you could think of.
The NXT provides a mailbox mechanism to exchange data with it using USB or Bluetooth. The program running on the NXT is able to post messages in a number of mailboxes or to retrieve messages dropped by the computer. The same mechanism is also used to communicate between several NXT using Bluetooth, where one of the NXT will act as the master.
There is 20 mailboxes available in the default firmware. The first ten mailboxes are used for incoming messages and the ten others are used for outgoing messages. The distinction seems to be quite subtle in the firmware code, so actually in many cases, any mailbox will work.
Each mailbox can contain up to five messages. If the program do not fetch them before another one is exchanged, the oldest message will be lost. Each message is a string of up to 59 characters.
I suspect that in many cases, you will use only two mailboxes, one for reception and the other one for emission.
As usual, I am interested in using python on the computer side. But this page may be interesting anyway if you plan to use another language as the concepts are the same.
Simple string exchange
Let's start with a simple example. I send a message from a python script to a running program and get a response. Here is the NXC source:
task main () { string in, out; while (true) { if (ReceiveMessage (0, true, in) == NO_ERR) { out = StrCat("Hello ", in); TextOut (0, LCD_LINE1, out, true); SendMessage (10, out); } } }
You will find the ReceiveMessage
and SendMessage
signatures in the NXC
guide, but it should be quite obvious. The second ReceiveMessage
argument
specify if the retrieved message should be removed from the mailbox.
It should be noted that the ReceiveMessage
function will return immediately
if there is no message available.
Now the python code:
import nxt.locator import nxt.brick import nxt.error # Find the brick and chat with NXT. sock = nxt.locator.find_one_brick () if sock: b = sock.connect () # Send my name :). b.message_write (0, "Ni") # Retrieve the response. (inbox, message) = b.message_read (10, 0, True) print inbox, message # What happen if no response is available? try: (inbox, message) = b.message_read (10, 0, True) except nxt.error.DirProtError, e: print "error: ", e.message # Bye bye NXT. sock.close ()
Here again no hard point. b.message_write (0, "Ni")
sends "Ni" to the
mailbox 0, and b.message_read (10, 0, True)
reads a message from the mailbox
10 to our mailbox 0 (I suppose the local mailbox number can be used for
asynchronous transfers). It seems that the NXT is fast enough to have the
response ready without any delay in the python script, but it may be safer to
add a delay loop to wait for the response.
If there is no message available, message_read
do not wait, but raise an
exception. Sadly, there is only one exception class for any protocol error in
NXT-Python, you will have to match the error string if you need robust error
checking. Here is the generated output:
0 Hello Ni
error: Specified mailbox queue is empty
Data serialization
This mechanism only handles strings, if you need to transfer something else, you have several options:
- Parse text messages: OK with python, but not easy with NXC (this is not exactly C...).
- Parse simplified text messages: this is the option chosen by The RWTH
Mindstorms NXT Toolbox which use Matlab, you use messages like
120990010002
and split it at known positions. - Use the
SendRemote*
andReceiveRemote*
family of NXC function: the limitation is that there is only one data in each message. You will have to use thestruct
module on the python side. - Use the
FlattenVar
andUnflattenVar
NXC function: I chose this one, so read on for more details.
The FlattenVar
function will serialize a variable into a string, and the
UnflattenVar
will do the reverse operation.
On the python side, I will use pack
and unpack
functions from the nice
struct
module to extract values from the string.
An example is better than a thousand words:
struct data_t { long a; int b; char c; long d[]; }; task main () { string in, out, s; data_t d; d.a = 0x12345678; d.b = 0xabcd; d.c = 0x42; ArrayInit (d.d, 0, 3); d.d[0] = 0x101; d.d[1] = 0x202; d.d[2] = 0x303; out = FlattenVar (d); SendMessage (10, out); while (true) { if (ReceiveMessage (0, true, in) == NO_ERR) { UnflattenVar (in, d); s = FormatNum("a: 0x%x", d.a); TextOut (0, LCD_LINE1, s, true); s = FormatNum("b: 0x%x", d.b); TextOut (0, LCD_LINE2, s); s = FormatNum("c: 0x%x", d.c); TextOut (0, LCD_LINE3, s); s = FormatNum("d: 0x%x", d.d[0]); TextOut (0, LCD_LINE4, s); s = FormatNum(" 0x%x", d.d[1]); TextOut (0, LCD_LINE5, s); s = FormatNum(" 0x%x", d.d[2]); TextOut (0, LCD_LINE6, s); } } }
And the python code:
import nxt.locator import nxt.brick import struct def hexdump (s): '''Dump message in hexadecimal.''' return ' '.join ('%02x' % ord (c) for c in s) # Find the brick and chat with NXT. sock = nxt.locator.find_one_brick () if sock: brick = sock.connect () # Get serialized data. (inbox, message) = brick.message_read (10, 0, True) # Print raw message. print hexdump (message) # Decode it. format = '<LHB3L' format_size = struct.calcsize (format) a, b, c, d0, d1, d2 = struct.unpack (format, message[0:format_size]) # Change and send back! a = a ^ 0x95511559L; b = b ^ 0xb9f9; d0 *= 2; d1 *= 3; d2 *= 4 message = struct.pack (format, a, b, c, d0, d1, d2) print hexdump (message) brick.message_write (0, message) sock.close ()
Here is the python code output which shows raw received and sent messages
(message_read
return an extra zero byte from the received message):
78 56 34 12 cd ab 42 01 01 00 00 02 02 00 00 03 03 00 00 00
21 43 65 87 34 12 42 02 02 00 00 06 06 00 00 0c 0c 00 00
As you can see, NXT use little endian.
And the NXT output:
There are caveats with those functions:
- array sizes are not encoded,
- for this reason, you have to initialise an array with the right size before
UnflattenVar
is called, UnflattenVar
does not seems to work with strings (but maybe I should investigate further).