Documentation  |   Table of Contents   |  < Previous   |  Next >   |  Index

8    Using the IrDA Protocols

Low-Level Communications

Exploring Palm OS®

The IrLAP Protocol Layer ^TOP^

IrLAP, the Infrared Link Access Protocol, lies at the bottom of the IrDA protocol stack. It provides a reliable, sequenced exchange of frames between two IrDA-capable devices, as well as a process for detecting (or "discovering") other nearby IrDA devices.

Palm OS® Cobaltdoes not allow applications to directly interface with the IrLAP protocol layer. IrLAP connections and discoveries are managed entirely by the IrLMP protocol.

The IrLMP Protocol Layer ^TOP^

The IrLMP (Infrared Link Management Protocol) layer sits just above IrLAP in the IrDA protocol stack. It serves as a multiplexer on an IrLAP connection, allowing multiple concurrent "conversations" between a pair of connected devices. Each conversation consists of a sequenced stream of reliably-delivered messages; the messages are guaranteed to be delivered to applications in the same order in which they were sent. Message boundaries are preserved.

In addition, IrLMP provides an exclusive mode, in which a single conversation can take full control of the underlying IrLAP connection, locking out all others. This is useful for applications that require a reduced-latency connection. Exclusive mode can be controlled using the SO_IREXCLUSIVE setsockopt() command.

The IrLMP Sequenced Packet Interface ^TOP^

The IrLMP sequenced packet interface does not provide any segmentation or reassembly of large messages, so the maximum size of incoming and outgoing messages is limited to the data sizes negotiated during the establishment of the IrLAP connection. IrLMP also provides no end-to-end flow control; when data is sent through an IrLMP socket faster than it can be consumed at the other end, messages will be discarded by the receiver's IrLMP protocol, thereby being lost to the receiving application.

Listing 8.1 demonstrates how to create and use IrDA socket connections. This example creates a socket and listens for an incoming connection. Once a connection is initiated, the code creates a new socket for sending data to the remote device. Once that's been done, the code makes the first socket idle by calling setsockopt() with the SO_IRIDLE command, gives the new socket exclusive control with the SO_IREXCLUSIVE command, and transmits data.

Once the data has been transmitted, the data transfer socket is closed and the control socket is reactivated.

Listing 8.1  Creating and using IrDA socket connections


int s1, s2, s3; 
struct sockaddr_irda addr; 
int mtu, len; 
char *buf; 
int zero = 0; 
int one = 1; 
 
// create an IrLMP socket, and wait for someone to call us 
s1 = socket(AF_IRDA, SOCK_SEQPACKET, IRPROTO_LMP); 
 
memset(&addr, 0, sizeof(addr)); 
addr.sir_family = AF_IRDA; 
addr.sir_lsap = 0x69; 
 
bind(s1, (struct sockaddr *)&addr, sizeof(addr)); 
listen(s1, 1); 
s2 = accept(s1, NULL, NULL); 
 
// initiate another IrLMP socket connection to our caller 
s3 = socket(AF_IRDA, SOCK_SEQPACKET, IRPROTO_LMP); 
 
addr.sir_lsap = 0x12; 
addr.sir_addr = IRADDR_ANY; 
connect(s3, (struct sockaddr *)&addr, sizeof(addr)); 
 
// mark first connection idle 
setsockopt(s2, SOL_SOCKET, SO_IRIDLE, (const char *)&one, sizeof(one)); 
 
// take exclusive control of IrLAP connection 
setsockopt(s3, SOL_SOCKET, SO_IREXCLUSIVE, (const char *)&one, sizeof(one)); 
 
// send a maximum-sized message 
len = sizeof(mtu); 
getsockopt(s3, SOL_SOCKET, SO_IRMTU, (const char *)&mtu, &len); 
buf = malloc(mtu); 
memset(buf, 0xff, mtu); 
send(s3, buf, mtu, 0); 
 
// close second connection, thereby relinquishing exclusive control 
close(s3); 
 
// the first connection can now become active 
setsockopt(s2, SOL_SOCKET, SO_IRIDLE, (const char *)&zero, sizeof(zero)); 

The IrLMP Datagram Interface ^TOP^

IrLMP provides a datagram interface for connectionless data exchange. Datagram messages are sent unreliably; it is not possible for the sending application to be certain that any other device has received a sent message. Additionally, datagram messages are always broadcast, so they may be received by any and all IrDA-capable devices within range of the sending device.

Listing 8.2 demonstrates how to broadcast a datagram and wait for a reply.

Listing 8.2  Broadcasting a datagram message


int s; 
sockaddr_irda addr; 
char buf[40]; 
int len; 
 
// create IrLMP datagram socket 
s = socket(AF_IRDA, SOCK_DGRAM, 0); 
 
// bind to IrLMP's connectionless SAP 
memset(&addr, 0, sizeof(addr)); 
addr.sir_family = AF_IRDA; 
addr.sir_lsap = irLsapUnitdata; 
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) != 0) { 
	// another application beat us to it 
	return; 
} 
 
// broadcast datagram 
memset(&buf, 0x69, sizeof(buf)); 
addr.sir_addr = IRADDR_BROADCAST; 
sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&addr, sizeof(addr)); 
 
// listen for response 
len = sizeof(addr); 
recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &len); 

Discovering IrDA Devices ^TOP^

Before you can establish a connection to an IrDA device, you have to find it. This is done by discovering the available devices via the IrDADiscoverDevices() function in the IrDALib shared library. The sample code in Listing 8.3 demonstrates this process.

Listing 8.3  Discovery of IrDA devices


	int s; 
	uint32_t nLogs; 
	IrLmpDeviceInfoType logs[2]; 
	Boolean cached; 
	int i, j; 
	struct sockaddr_irda addr; 
 
	// perform discovery 
	nLogs = 2; 
	IrDADiscoverDevices(&nLogs, logs, &cached)); 
 
	printf("discovery found %d %sdevices:\n", nLogs, (cached ? "cached " : "")); 
	for (i = 0; i < nLogs; i++) { 
		printf("   %08x ", logs[i].deviceAddr); 
		switch (logs[i].method) { 
		case kIirLmpSniffing: 
			printf("sniffed"); 
			break; 
		 
		case kIirLmpActiveDiscovery: 
			printf("discovered"); 
			break; 
		 
		case kIirLmpPassiveDiscovery: 
			printf("found us"); 
			break; 
		} 
 
		printf(" %d bytes of device info: ", logs[i].infoLen); 
		for (j = 0; j < logs[i].infoLen; j++) 
			printf("%c", logs[i].deviceInfo[j]); 
		printf("\n"); 
	} 
 
	if (nLogs == 0) { 
		printf("found no devices - aborting test\n"); 
		return; 
	} 
 
	// open irlmp socket 
	s = socket(AF_IRDA, SOCK_STREAM, 0); 
	 
	// connect to first device discovered 
	memset(&addr, 0, sizeof(addr)); 
	addr.sir_family = AF_IRDA; 
	strcpy(addr.sir_name, "OBEX"); 
	addr.sir_addr = logs[0].deviceAddr; 
 
	connect(s, (struct sockaddr *)&addr, sizeof(addr)); 

This code discovers the available devices, printing out information about them, then connects to the first device discovered by creating a new socket and connecting to the socket using OBEX on a STREAM based socket.

The TinyTP Protocol Layer ^TOP^

The TinyTP Sequenced Packet Interface ^TOP^

The Tiny Transport Protocol (TinyTP) sits on top of IrLMP in the IrDA protocol stack. It builds upon the functionality of the IrLMP sequenced packet interface by providing segmentation and reassembly of large messages, as well as end-to-end flow control for individual IrLMP connections.

The code in Listing 8.4 shows how to create a socket to listen for a sequenced TinyTP connection, enable automatic reassembly of incoming messages, determine the connection's MTU, and mark the control socket as idle. Once this code is done executing, it's time to transfer data as seen in Listing 8.1.

Listing 8.4  Setting up a TinyTP sequenced packet connection


int s1, s2; 
struct sockaddr_irda addr; 
int mru, mtu, len; 
int one = 1; 
 
// create sequenced TTP socket 
s1 = socket(AF_IRDA, SOCK_SEQPACKET, 0); 
 
// bind to well-known lsap 
memset(&addr, 0, sizeof(addr)); 
addr.sir_family = AF_IRDA; 
addr.sir_lsap = irLsapAny; 
strncpy(addr.sir_name, "IrTest", sizeof(addr.sir_name)); 
bind(s1, (struct sockaddr *)&addr, sizeof(addr)); 
 
// enable automatic re-assembly of incoming messages 
mru = 2345; 
setsockopt(s1, SO_IRMRU, (const char *)&mru, sizeof(mru)); 
 
// wait for someone to connect to us 
listen(s1, 1); 
s2 = accept(s1, NULL, NULL); 
 
// retrieve connection MTU 
len = sizeof(mtu); 
getsockopt(s2, SOL_SOCKET, SO_IRMTU, (const char *)&mtu, &len); 
 
// mark connection idle 
setsockopt(s2, SOL_SOCKET, SO_IRIDLE, (const char *)&one, sizeof(one)); 

The TinyTP Stream Interface ^TOP^

In addition to the sequenced packet interface, TinyTP provides a stream interface, in which it manages all aspects of segmentation and reassembly for the application. Applications can ignore the TinyTP MTU negotiated for the connection and send messages of any length. The TinyTP stream interface will take care of segmenting them as necessary, so attempting to send a message larger than the MTU will not result in failure.


NOTE: When using the TinyTP stream interface, message boundaries are not preserved end-to-end. The TinyTP protocol module may break messages into multiple pieces, or combine multiple small messages into a single larger message.

This is the easiest way to send data, as you can see from Listing 8.5. All you do is open the connection and send and receive data on it. No worrying about packet sizes or sequencing; it just works.

Listing 8.5  Sending and receiving data using a TinyTP stream


int s; 
struct sockaddr_irda addr; 
int mtu, len; 
char *buf; 
int i; 
char c; 
 
// create TinyTP stream socket 
s = socket(AF_IRDA, SOCK_STREAM, IRPROTO_TTP); 
 
// connect to anyone 
memset(&addr, 0, sizeof(addr)); 
addr.sir_family = AF_IRDA; 
strncpy(addr.sir_name, "IrTest", sizeof(addr.sir_name)); 
addr.sir_addr = IRADDR_BROADCAST; 
connect(s, (struct sockaddr *)&addr, sizeof(addr)); 
 
// retrieve connection MTU 
len = sizeof(mtu); 
getsockopt(s, SOL_SOCKET, SO_IRMTU, (const char *)&mtu, &len); 
 
// send lots of data 
buf = malloc(mtu * 2); 
memset(buf, 0x69, mtu * 2); 
send(s, buf, mtu * 2, 0); 
 
// read response 
for (i = 0; i < 200; i++) 
	recv(s, &c, sizeof(c), 0); 

Getting and Providing Information About IrDA Services ^TOP^

Every device with an IrDA protocol stack includes the Information Access Service (IAS), which consists of a directory listing all of the IrDA services that the device offers, as well as server software that allows other devices to access this directory. The IAS query interface provides a method for discovering services offered by a remote device, by querying its IAS directory server.

Structure of the IAS Database ^TOP^

Entries in the IAS database consist of IAS objects, each of which describes a single service offered by the device. These objects consist of sets of attributes, typed name-value pairs containing specific information about the service. Additionally, each object contains a class name, which is a string that describes the object's type. The type indicates what attributes the object includes.

Getting Information about IrDA Services ^TOP^

The IASGetValueByClass() function lets an application ask a remote device for attribute values with a given name belonging to a given class. The connect() and bind() functions automatically handle some aspects of IAS and provide a simplified interface to it, as shown in "The IrLMP Protocol Layer".

Listing 8.6  Querying IAS


IASQueryType query; 
uint8_t buffer[256]; 
 
// look for an IrOBEX server on any remote device (the class and attribute 
// names are well-known, specified by the IrOBEX standard document) 
query.addr = IRADDR_BROADCAST; 
query.className = "OBEX"; 
query.attribName = "IrDA:TinyTP:LsapSel"; 
query.resultBuf = buffer; 
query.resultBufLen = sizeof(buffer); 
if (IASQueryValueByClass(&query) == errNone && 
    query.attribCount > 0 && 
    query.attribValues[0]->attribType == kIASiasAttribIntegerAttrib) 
{ 
	printf("Found IrOBEX server on device %08lx, at LSAP %04x\n", 
			query.addr, query.attribValues[0]->value.integer); 
} 

Providing Information About Offered IrDA Services ^TOP^

To advertise the existence of an IrDA service, an application needs to add the service to its IAS directory. This can be done using the bind() function, which automatically creates the IAS entry, or manually as shown in Listing 8.7.

The IASRegisterObject() function associates an IAS service entry with an IrDA socket. This is generally used by a server application to inform remote devices of the location and type of services the application offers. The function returns an object ID that uniquely identifies the object. This object ID can be used to unregister the service later through a call to IASUnregisterObject().

An easier method of associating a service name with a server socket is provided by the IASRegisterService() function. This function creates an IAS entry of a service class specified when calling it, containing a single entry specifying the LSAP address of the specified socket. This attribute's name will correctly reflect the type of socket. For example, if a TinyTP socket is specified, the attribute's name will be "IrDA:TinyTP:LsapSel".

Once registered, a service entry remains in the device's IAS database until either the socket with which it is associated is unbound, or the IASUnregisterObject() function is called with its ID.

Listing 8.7  Registering a service with IAS


#define N_ATTRIBS 2 
IASObjectType obj; 
const char *names[N_ATTRIBS]  = { 
	"IrDA:IrLMP:InstanceName", 
	"IrDA:IrLMP:LsapSel" 
}; 
IASAttribValueType *values[N_ATTRIBS]; 
uint8_t buf1[10], buf2[20]; 
 
// fill out attribute values 
values[0] = (IASAttribValueType *)buf1; 
values[0]->attribType = iasAttribInteger; 
values[0]->value.integer = <listener socket's LSAP>; 
 
values[1] = (IASAttribValueType *)buf2; 
values[1]->attribType = kIASiasAttribUserStringAttrib; 
values[1]->value.userString.charSet = iasAttribUserString; 
strcpy(values[1]->value.userString.chars, "Bar"); 
 
// advertise service on our listener socket 
obj.className = "Foo"; 
obj.attribCount = N_ATTRIBS; 
obj.attribNames = names; 
obj.attribValues = values; 
if (IASRegisterObject(<listener socket>, &obj, false) == errNone) { 
	// our object is now in this device's IAS information base, and will 
	// remain there until <listener socket> is closed 
} 

This code creates an array of IAS attribute structures (of type IASAttribValueType) and fills each of them out with information describing the attribute. It then sets up an IAS object (of type IASObjectType) and calls IASRegisterObject() to register the service.