LegOS Network Protocol

What is LNP?

LNP stands for LegOS Network Protocol. It allows for communication between brickOS-powered robots, and with host computers.

We will not be using LNP very much in the class, partly because few tools have been written for it. That said, here is a brief overview of how networking works in brickOS.

LNP has two messaging layers, the integrity layer and the logical layer. The integrity layer makes sure that packets get through uncorrupted, but they aren't directed anywhere in particular. The logical layer adds addressing on top of the integrity layer, so that packets can be directed to a specific port on a specific device. The integrity layer is marginally easier to use than the logical layer, but the minor additional detail is worth the ability to address specific ports and devices, so using the integrity layer will not be covered.

The logical layer provides similar functionality to the internet's UDP. Packets are guaranteed to arrive free of corruption, if they arrive at all. No guarantees are made as to whether your packets actually get delivered, and if they drop off into the bit bucket your program may not be notified, nor will another transmission be attempted. The rate of packet loss can be very low if the environment is clear and no network collisions happen, but it's still important to keep the possibility of lost packets in mind.

The cs148 TAs have added a third layer to LNP, a pseudo-reliable layer similar to TCP. If a packet arrives, it will arrive free of corruption, and all packets sent before it will probably have also arrived. When a packet is sent, the sender waits for an acknowledgement from the recipient before continuing. If no acknowledgement is detected after a certain period of time, then retransmissions are made. If the number of retransmissions exceeds a certain value (currently 5), then the packet is "dropped" and life goes on.

LNP was originally designed for facilitating communication between an RCX and a PC, but it also works well for communication between multiple RCXs. We will be using LNP to a limited extent in the course, and will be providing you with everything you need for your assignments.

Addressing

An LNP address is an 8-bit value. We currently have the hostmask set as 0xF0, which results in 16 hosts each with 16 ports. You will almost always see the host names referred to in hexadecimal (0-F) and the port numbers referred to in decimal.

Change the LNP address of your brick using the lnp_set_hostaddr ( addr ) command. That's for the RCX. If you want to use your PC to change the address of your brick, then use the util/changeBrickAddress command.

Sending Data

Addressing Layer:

Sending data is pretty simple as well. You need to have some data to send, of course. You need to know how big the data is. Finally, you need an address to send it to.

result = lnp_addressing_write(data, length, DEST_ADDR, MY_PORT);

Where data is a pointer to what you actually want to send, length is how many bytes to send, DEST_ADDR is the address you're sending to, and MY_PORT is the port you're sending from. This function returns 0 if successful, and usually nonzero if it failed. "Usually" because you are not guaranteed to know if the transmit failed. Never rely on your packets arriving 100% of the time. The function call will block, not returning until the transmission is complete

An alternative to this is to use lnp_printf ( #include <lnp/lnp_printf.h> ).

lnp_printf ( dest, formatted string )

Usage of lnp_printf is very similar to fprintf, but instead of a file descriptor, you use an LNP address.

Reliable Layer:

Sending data via the reliable layer requires that you initialize a connection.

lnp_reliable_init( <DEST_ADDR>, <port> );

Where DEST_ADDR is the address of the other device that you will be communicating with, and port is the port that both devices will use. You are only allowed to have one reliable "connection" open at a time, and you must initialize it with this command.

Once you've initialized a conection from both sides, you can send data using the following two commands:

 

  • lnp_reliable_printf( char *formatted string, ... ); //use this just like a normal printf
  • lnp_reliable_write( const void *data, int length );

    When you are down using the reliable layer, you need to shut it down by using the function

    lnp_reliable_shutdown();

    Receiving Data

    Addressing Layer - packet handlers

    In order to recieve data with your program, you must set up packet handlers. A handler will be called whenever a packet arrives on that handler's port. You must set up a handler for each port you expect to recieve data on. Addressing handlers are defined as follows:

    void addr_handler_1(const unsigned char *data, unsigned char length, unsigned char src);

    data is a pointer to the data in the packet. Length is how much data is in the packet. Src is the address of the program that sent the packet. Notice that since length is a char, it cannot hold more than 255 .With networking overhead, the limitation is actually 253 bytes per packet. Now the handler must be registered with LNP.

    lnp_addressing_set_handler (MY_PORT_1, addr_handler_1);

    This will install the addr_handler_1() function on port MY_PORT_1. Now, if any data is recieved on that port, your function will be called. Note that if your code is running under legOS (as opposed to on your PC), this function is called from the legOS interrupt routines, so avoid things like memory functions or thread control functions. Things like memcpy, or anything that doesn't cause memory allocation or task switching, are ok. It's also a bad idea to take too much time inside a handler. A good basic strategy would be to copy the data into a buffer and setting a flag, with a thread standing by to process the data. But that will be covered later

    Reliable Layer - lnp_reliable_read

    Use lnp_reliable_read to receive data on a reliable channel.

    int lnp_reliable_read (void *data, unsigned char *src) More about this is available in the code reference.