Protocol

From Albatross

Jump to: navigation, search

Contents

Communications Datagram

Note that all multi-byte data is in big-endian (network byte order). This allows the use of ntohs(), htons() and such functions for hardware independance

Bytes Description
0 SYNC Synchronization characters used to signal the receiving state machine that a stream packet may be forthcoming. Must be 0x50.
1 SYNC1 Must be 0x0A.
2 DestHigh High byte of the destination bit-mask; each possible recipient (the various daemons on Snapper) has a designated bit. Each recipient daemon can AND this field with the specified bit, and if the result is non-zero, the datagram is intended for this recipient.
3 DestLo Low byte of destination bit-mask.
4 SenderHigh High Byte of the sender bit-mask. The datagram sender includes their bit mask in this field.
5 SenderLo Low byte of Sender bit-mask.
6 CMDHi High byte of (currently unused) field that could be used for light-weight commands, such as Acknowledge or resend request.
7 CMDLo Lo byte of light-weight command field.
8 payloadSize Size of the the payload data (bytes 8 to Size+7).
9 HCHSUM Header Checksum - 8-bit XOR check sum of bytes 0 to 8. Provides rapid detection of bad SYNC matches.
10..(Size+9) The Payload See below: Payload Description.
(Size+10) CHKHi High byte of 16-bit Fletcher checksum of the entire datagram (excluding the CRC obviously), that is, bytes 0 to (Size+7).
(Size+11) CHKLo Lo byte of Fletcher-16 checksum.

The Fletcher-16 checksum is defined as:

Fletcher-16 is composed of two sums accumulated per byte: s1 is the sum of all the bytes, s2 is the sum of all s1 values. 
Both sums are done modulo 255 (or 256). s1 is inititialized to 1, s2 to zero. The checksum is stored as s2*256+s1 in 
little-endian order.

See [1], [2], [3], and [4] for information.

Payload Description

This design of the protocol layout has been chosen with the interests of

  • Code readability
  • How easy it is to add new data
  • code reuse

Its best to start with an example.

There exists a single header file protocol.h It contains 'addresses' for each piece of data that any daemon cares about, and may want to send to someone else. These addresses are not representative of any physical location, only relative to a offset, which is unique per daemon.

For example, consider two daemons, filter (the kalman filter) and path (the path planning daemon). Each daemon has local data which it uses, and maintains, and communicates with the other daemons.

#define FILTER_DAEMON_OFFSET 100
#define PATH_DAEMON_OFFSET 200

The we can specify daemon specific data like

//Filter Daemon Variables
#define LONGITUDE FILTER_OFFSET_DAEMON + 0
#define LATITUDE FILTER_OFFSET_DAEMON + 1
#define HEADING FILTER_OFFSET_DAEMON + 2
#define FILTER_DAEMON_OFFSET_END HEADING

//Path planning daemon variables
#define NEXT_WAYPOINT_ALT PATH_DAEMON_OFFSET + 0
#define NEXT_WAYPOINT_HEAD PATH_DAEMON_OFFSET + 1
#define NEXT_WAYPOINT_VELOCITY PATH_DAEMON_OFFSET + 2
#define PATH_DAEMON_OFFSET_END NEXT_WAYPOINT_VELOCITY

Now skip to the transmission part of the example. Say daemon foo transmits data to the filter daemon. This data is called the payload of the UDP datagram.

This will be transmitted as;

[Start Address][Number of Chunks][Longitude Data][Latitude Data][Heading Data]...[xxx]...[xxx]

In the filter daemon the payload will be decoded as per

index = payload[0]
if( (index > FILTER_DAEMON_OFFSET) && (index < FILTER_DAEMON_OFFSET_END) )
{
    for(i = 1, i < Number of Chunks, i++)
    {
        local_variable_buffer[index++] = payload[i]
    }
}

foobarfunction(local_variable_buffer[LATITUDE],local_variable_buffer[LONGITUDE])
{
}

This code is both reusable between daemons, and human readable.

The advantages of sending address-size-data tuples is if we don't want to send all the data we don't have to (i.e. may be the case when we have finalised stuff more), and we can have mixed commands and data, i.e. each daemon maintains a command array, if any element in that array is non zero after packet processing then pass that data to the command (excute the command).

commands[]
for(i=0,all in command,i++)
{
    if(command[i] != 0)
    {
        switch(command[i])
        {
            case 1:            //represents a possible command.
                foo(command[i]);
                break;
            case 2:
                bar(command[i]);
                break;
        }
    }
}

Note that commands are in there own address space. i.e.

#define COMMANDS_OFFSET 300
#define CMD_RESET_SNAPPER COMMANDS_OFFSET + 0
#define CMD_RESET_GPS COMMANDS_OFFSET + 1
#define COMMANDS_OFFSET_END CMD_RESET_GPS COMMANDS_OFFSET

This allows commands and data to exist in the same payload, eliminating the need for CMDHi and CMDLo at the datagram level. It also makes the code more universal.

decoding a mixed command data payload would go something like

for(all in payload)
{
    if(payload chunk is data)
        insert into local_variable_buffer as above
    else is command
        insert into command buffer
}
process command buffer as above

Addressing Schemes

Address-Data Tuples

  • Advantages
    • All daemons can send asynchronously to the serial daemon, then all the comms radio daemon needs to do is a simple buffer-and-send approach
    • Can mix commands and data
    • Can send small payloads with non contiguous data
  • Disadvantages
    • More overhead over radio communications

Start Address, then Sequential Data

  • Advantages
    • More efficient for larger packets, or packets containing logically grouped data (e.g. one packet containing all the latest GPS data).
  • Disadvantages
    • Cannot mix command and data, not really a big problem though (although the option would be nice - design for redundancy and all that)
    • Cannot send non-contiguous data in one packet (e.g, one packet containing GPS data and inertial data).
    • Not as elegant/generalized as the address-data tuple scheme.

Start Address, Size, then Sequential Data Tuples THIS IS THE CHOSEN METHOD

What would be sent:

  • start address
  • number of sequential items from the start address (size)
  • the data itself

This entire tuple would be repeated as necessary in the packet. Address (16-bit) and size (16-bit) would be sent first, followed by 16-bit chunks of data. After size chunks of data, another start address/size/data tuple would be expected.

  • Advantages
    • Combines advantage of both the other schemes above, without losing generality.
  • Disadvantages
    • For single packet trasnmissions (e.g. commands) or packets containing multiple unrelated field, this scheme adds 2 bytes of overhead (the size parameter).

Other Considerations

Another thing which needs to be considered is the chunk size. A trade-off has to be considered between 32bit alignment (for the PXA255), and the serial radio. Possible chunk arrangements include

  • 32bit chunk size, containing upper 16bits address, lower 16 bits data
  • 32bit chunk size, upper 8bits address, lower 24bits data
  • 16bit chunk size. address OR data (Not interleaved)

UDP Multicasting Stuff

References: http://www-users.cs.umn.edu/~bentlema/unix/advipc/ipc.html

Johns test program results (FIXME this doesn't really belong here):

  • Input:
UDP> send(alba_create(42, 0xab, alba_payload_pack([(0, "B", 15), (1, "H", 0x4321)])))
  • Output:
~ # ./test
Received 19 bytes
Datagram Contents:
        [0x50] Sync
        [0xA] Sync1
        Header Checksum: OK
        [0x2A] Destination
        [0xAB] Command
        [0x9] Payload Size
        Payload Checksum: OK
        Begin Payload
                [0x50][0xA][0x2A][0x0][0xAB][0x0][0x9][0xD2][0x0][0x0][0x1][0xF]        End Payload
Valid Datagram
Unpacking Payload
Addr: 0x0 Size: 0x1 Data: [0xF]
Addr: 0x1 Size: 0x2 Data: [0x21][0x43]
--DONE--
Personal tools