[jump to content]

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* and ReceiveRemote* family of NXC function: the limitation is that there is only one data in each message. You will have to use the struct module on the python side.
  • Use the FlattenVar and UnflattenVar 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:

Screenshot of 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).