The PalmPilot's Infrared Port

One of the powerful Palm III features is its support of data exchange over InfraRed (IR) ports. A.J. explores programming the IR port by presenting both an IR test application and a version of the venerable game BattleShip that can be played between two players via the IR port.


April 01, 1999
URL:http://www.drdobbs.com/the-palmpilots-infrared-port/184410909

Apr99: The PalmPilot's Infrared Port

A.J. is a freelance consultant and member of the technical staff at MCI WorldCom. He can be contacted at musgrove@ ccorp.com.


The PalmPilot was originally designed as a Personal Information Manager (PIM). Consequently, the software that came with it included a date book, address book, to-do list, note-taker, and the like. Recent versions of the PalmPilot have gone beyond the simple PIM model, however. The Palm III, for instance, provides extensive communications support (including Eudora Pro, cc:Mail, POP3 Internet e-mail, and more) and an infrared (IR) port.

Applications bundled with the Palm III support data exchange over IR. The exchange of these data objects is not the only application for the IR port. While the port is not powerful enough to act as, say, a remote control for a television, it does have enough power to support Palm-to-Palm communications of most any kind of data. In this article, I explore programming the IR port on the Palm. In doing so, I present both an IR test application and a version of the venerable game BattleShip that can be played between two players via the IR port. For the most part, I'll focus on the IR test application. Understanding how it works makes BattleShip self-evident. The development environment I use to implement these applications is Metrowerks's CodeWarrior for Palm Computing Platform Release 5 (available from 3Com at http://www.palm.com/). The source for both the BattleShip and IR test programs is available electronically (see "Resource Center," page 5).

The IR Port

The IR implementation in PalmOS 3.0 conforms to Infrared Data Association (IrDA) standards (http://www.irda.com/). The published protocol consists of a number of layers, some required and some optional; see Figure 1.

The Async Serial-IR layer, implemented purely in hardware, is a serial-style IR interface that supports speeds from 9600 to 115,200 bits/sec. The specification itself supports synchronous communication at speeds of up to 4 bits/sec., but this was not implemented on the Palm.

The IR Link Access Protocol (IrLAP) layer provides reliable data transfer on a device-to-device basis and device discover. IrLAP, which corresponds to the transport layer of the OSI network model, supports a single connection between two devices.

The IR Link Management Protocol (IrLMP) provides multiplexed, session- oriented communication on top of IrLAP. A component of IrLMP is the Information Access Service (IAS). It provides a service and protocol database complete with discovery mechanisms. Using IrLMP, a device can carry on multiple, noninterfering conversations with a peer device.

The Tiny Transport Protocol (TinyTP) is a lightweight transfer protocol built on top of IrLMP. It serves as a base for other higher-level protocols, such as the Object Exchange Protocol (OBEX) -- the only top-level protocol implemented on the Palm. OBEX is used by the Exchange Manager to transfer records between applications, such as Addresses, Appointments, and Applications. Palm vertically implemented the protocol layers necessary to implement the Exchange Manager. However, every software layer of the protocol, except OBEX, is exposed through APIs.

The Palm's IR Library

The IR library on the Palm is implemented as a system library instead of as a set of operating-system entry points. The IR library is unusual; its macros and function prototypes, for instance, are not even included when you use Palm.h -- you must also include irlib.h to have these. The first step of using any system library is to locate the library entry point using SysLibFind(). Example 1(a) is the SysLibFind() prototype.

The libName parameter is the name of the library you are trying to locate. The name is defined as irLibName in irlib.h. refNum is the library handle reference number and is a return value of this function. It must be used in all subsequent calls into the library.

Just like the rest of the Palm III's PalmOS 3.0 operating system, the IR library is event based. However, it does not use the standard event queue mechanism that everything else on the Palm uses. Instead, you must give the library the address of a callback function. This function is called to respond to every event for a communications session.

IrOpen() must be called before using any other IR library function. This function allocates the global data areas and allocates system resources. When you are finished with the IR library, call IrClose(). These are usually called in the StartApplication() and StopApplication() functions, respectively. Example 1(b) shows the prototypes for IrOpen() and IrClose(). The options parameter of IrOpen() specifies the desired connection speed. irOpenOptSpeed9600(), irOpenOptSpeed57600(), and irOpenOptSpeed115200() request that connections be opened at speeds of 9600, 57,600, or 115,200 bits/sec, respectively.

The connection phase of the communication introduces the notion of client-server. Once the connection is established, the two sides are in a peer-like relationship. The server is the process waiting for a connection. It can go about other tasks while waiting for an incoming connection request. The client is the process that initiates the connection. Data can be transferred during the connection-setup portion of the conversation. This is useful for doing things such as exchanging hardware IDs or other setup information (which I do with BattleShip).

As with most protocols, you must know what you want to connect to before actually connecting. The identifying unit for conversations is the LSAP selector. In both the IR test application and BattleShip, I exchange the LSAP selector during device discovery. I do this using the device-information feature. The local LSAP selector for the client is obtained during the protocol stack-binding phase of the application, which also sets up the callback function. The application must issue an IrBind() for each separate conversation it will carry on. Once an application is done with the connection, it should call IrUnbind() to remove the connection from the protocol stack. Example 1(c) presents the prototypes for IrBind() and IrUnbind().

The IrBind() call registers a connection with the protocol stack. It expects three parameters: the library reference number, a pointer to a connection structure, and a pointer to the callback function for the connection. The reference number is the number that came from the IrOpen() call. Example 1(d) is IrCallBack()'s definition.

All events from the library are sent to the callback function. Table 1 shows the structure of the IrCallbackParms data structure, while Table 2 presents the different event types that the IrCallback() routine can receive.

The IrBind() function also fills in the lLsap field in the IrConnect structure. This is the local LSAP selector. The only field in IrConnect that should have set users is rLsap. This is the remote LSAP selector for the connection. All other fields in this structure are for system use and are changed by the system or macros.

With a connection bound to the stack, the next logical thing to do is set your device info -- the information that's returned to the other device during its discovery. I use a 1-byte device information string that is the local LSAP selector. Example 1(e) is the prototype for the IrSetDevice() function.

Client or Server?

BattleShip's StartApplication() function (see Listing One) implements all of the steps covered to this point. This puts you in a position to act as either a client or a server later on.

Once you have a connection entry on the protocol stack, you can go forward as either a client or server. A server is easier -- you just wait for the callback routine to be activated by an incoming connection request. As a client, you must do some connection-setup work.

The first task is to find out to what you are going to connect. IrDiscoverReq() starts the discovery process. Example 2(a) is the prototype for this function. Like most of the IR library, this routine is asynchronous. The successful return of this function means that the discovery process has been started. It is not completed until an event is sent to the callback function with the results as an LEVENT_DISCOVERY_CNF event. Once the discovery process is completed, the device list will be contained in the deviceList field of the IrCallbackParms structure. The device list is of type IrDeviceList; see Table 3.

From the device list, you can obtain device addresses and LSAP selectors of prospective peer connections. You can then go about establishing a connection. Because all IrDA communication protocols are built on top of IrLAP, the first step in communication is starting the IrLAP connection. The 32-bit device address can be found in the deviceList returned from the discovery operation. The device address is contained in an IrDeviceInfo structure; see Table 4. This connection is started using IrConnectIrLap(); see Example 2(b). This operation cannot run after an IrDiscoverReq() until an LEVENT_STATUS is received with a status of IR_STATUS_MEDIA _NOT_BUSY.

The return value from this function should be IR_STATUS_PENDING. This means that the connection request has been initiated. This does not mean that the connection will fail or succeed. To determine this you must wait for the callback to be sent an event. If the return value is IR_STATUS_MEDIA_BUSY, then there is already an active connection or discovery process happening on the device. If the connection is completed successfully, an LEVENT_LAP_CON_CNF event is sent to the callback function. An LEVENT_LAP_DISCON_IND is sent in the case of failure. On the server side, the callback function in the application is called for the event LEVENT_LAP_CON_IND. This is the indication to the server that the IrLAP connection has come up.

Either the client or server can terminate the IrLAP connection with a call to IrDisconnectIrLap(). Example 2(c) presents its prototype. If IrLAP is disconnected, the event LEVENT_LAP_DISCON_IND will be sent to all bound IrConnect structures indicating that the connection has gone down. This means that all active LMP and TinyTP connections are also terminated.

There are two possible return values for this function. IR_STATUS_PENDING means that the disconnect request has been successfully submitted. The callback function will be called once the request has been completed. IR_STATUS_NO_LAP means there was no IrLAP connection to terminate.

The IR Test Application

When running the IR test application, you must first select one of the Palm devices to be your client, and the other to be the server. Start the application on both devices. You should see a screen like Figure 2. The buttons represent different actions that can be taken against the IR stack, and the Send line is where you can enter data to send to the peer once the connection is up.

On both devices, bind the connection to the protocol stack by pressing the Bind button. Next, on the device that you selected as the client, execute a discovery operation by pressing Discovery. Once you have an S_MEDIA_NOT_BUSY event, start the IrLAP connection by pressing IrLAP. Both the client and the server are given notification that the connection came up. From either device, choose to disconnect the IrLAP connection by pushing IrLAP again. Notifications are then received by both sides.

With the IrLAP connection in place, you are set to start one of the higher-level protocols -- IrLMP or TinyTP. Deciding which connection you would like to start is simple -- you just set the parameter in the connection structure. For the purpose of this example, I'll use IrLMP. Remember that the client and the server must be set the same, so they will both have to set the protocol before the connection is started. The protocol type is set by using the IrSetConTypeLMP() or IrSetConTypeTTP() functions; see Example 3(a).

Once you have set the connection type, everything else about the protocol is automatic. No matter which connection type you are using, the relevant events will begin with LEVNET_LM and there is no difference in the functions used to manipulate the connection. The client initiates a connection to its peer with a call to IrConnectReq(); see Example 3(b). The refNum is the library reference number; just as in all the other calls. con is a pointer to a connection that has been bound to the protocol stack and has the connection type and rLsap selector set. packet is a pointer to the data packet that will be sent to the other side along with the connection request. Even if you do not want to send data to the other side, packet must point to a valid IrPacket structure. credit is the amount of credit that will be advanced to the other side and is only valid for TinyTP connections. Since it will be ANDed with 0x7f, the value must also be less than 127 or results will be undefined.

There are three possible results of a call to the IrConnectReq() function:

1. IR_STATUS_PENDING means that the connection request has been successfully submitted and the results will be sent as an event to the callback function.

2. IR_STATUS_FAILED is an indication of the rejection of the connection request. A connection request could be rejected because the packet size exceeds IR_MAX_CON_PACKET for LMP connections or IR_MAX_TTP_CON_PACKET for TinyTP connections. The connection request could also be rejected if the connection is already connected or the IrConnect structure is not bound to the stack.

3. IR_STATUS_NO_IRLAP will be returned if there is no established IrLAP connection.

Whether or not the connection succeeds, the connection information packet is sent to the callback function with the event LEVENT_PACKET_HANDLED. This is important because the memory occupied by packet is owned by the stack until this event is sent. If you use this packet before you get this event the results will be undefined and probably unwanted.

Once the connection request has been processed, the results will be sent to the callback function as an event. An event of LEVENT_LM_DISCON_IND indicates that the connection failed. LEVENT_LM_CON _CNF indicates that the connection succeeded and the IrCallbackParms structure will contain data returned from the other side as part of the connection response.

An LEVENT_LM_CON_IND event is generated on the device that receives the connection request. The connection information from the peer device is included in the IrCallbackParms structure. To accept the connection, the application should issue an IrConnectRsp(); see Example 3(c). This looks similar to the IrConnectReq parameters, and each parameter has the same meaning. The packet parameter is the data that will be returned to the peer to accept the connection. You cannot use the packet until you receive the LEVENT_PACKET_HANDLED.

Use the IR test application to experiment with IrLMP. First, you must bind a connection, then do device discovery from the client, and finally start an IrLAP connection. Only then can you start an IrLMP connection.

Once you have the connection established, you will probably want to have a conversation. This is accomplished by using the IrDataReq(); see Example 3(d). This function transfers a data packet to the peer over the existing connection. The maximum size of the data packet can be found using IrMaxTxSize(). You must check IrMaxTxSize() for each connection, as each one could have a different maximum packet size.

The refNum parameter is the library reference number and the con parameter is the pointer to the IrConnect structure representing the connection. The packet parameter is a pointer to an IrPacket structure which contains the data to be sent to the peer. The possible return values of IrDataReq are IR_STATUS_PENDING or IR_STATUS_FAILED.

IR_STATUS_PENDING indicates that the request has been accepted by the stack for delivery. Once the packet has been delivered, the callback function receives the event LEVENT_PACKET_HANDLED and the packet is in the packet structure. IR_STATUS_FAILED could be returned if the IrConnect structure is not bound to the stack, the packet exceeds the maximum size, or the IrConnect structure does not represent an active connection.

When data is received, an LEVENT _DATA_IND event is generated. The rxBuff and rxLen parameters contain the received data. There is no response that should be sent to this event, as the stack has already handled the receipt acknowledgment.

The IR test application can be used to demonstrate the data transfer. To use it, first establish an IrLMP connection to a peer. Then, enter the string you would like to transmit into the Send field and press the Data button. The data will appear in the Rec field on the other device.

During processing, it may be necessary for an application to stop receiving data. If you are using IrLMP and only one connection exists, you can set the busy state on the protocol. If more than one connection exists, never set the busy state. Likewise, never set the busy state for TinyTP. Busy is represented by the device's transmission of Receive Not Ready (RNR) frames. The IrLocalBusy() function sets and unsets the busy state. Its prototype is in Example 3(e). The flag variable should be True to set busy, and False to unset it.

There are five functions provided that view different aspects of the protocol stack. Example 3(f) presents their prototypes. All of these functions expect the library reference number, and IrMaxRxSize() expects the connection to which you are referring. The IrIsLapConnected() function returns True if there is a currently active IrLAP connection. IrIsMediaBusy() returns the busy status of the medium. If the medium is busy, any connection or discovery requests would fail. IrIsNoProgess() returns True if there is no progress being made on the IrLAP connection. If the peer device is removed from the transmission range of the IR port, then progress could cease on data transfers. IrIsRemoteBusy() returns True if the peer device is currently transmitting RNR frames indicating that it does not wish to receive data. IrMaxRxSize() returns the maximum data packet size that you can expect to receive. The value is valid only for the connection used in the function call.

Testing the IR Connection

The final part of the Palm's IR API involves testing. You can initiate a test packet to the peer device to see the link status. This is done using the IrTestReq() function; see Example 4. The TEST packet can only be sent when the stack is in the Normal Disconnect Mode (NDM) state. Generally, this means that IrLAP cannot be connected and discovery operations cannot be in progress.

The refNum parameter is the library reference number. The devAddr parameter is the 32-bit address of the device to which the test frame should be transmitted. con is a pointer to an IrConnect structure whose callback function will be the recipient of the test status. packet is a pointer to an IrPacket that will be sent as the test data. There are three possible return values:

1. IR_STATUS_PENDING means that the test packet has been accepted by the protocol stack. The callback function is sent an LEVENT_TEST_CNF once the test has been completed. The status field contains IR_STATUS_SUCCESS to indicate success or IR_STATUS_FAILED to indicate a test failure.

2. IR_STATUS_MEDIA_BUSY means that the media is not in the NDM state and cannot start a test.

3. IR_STATUS_FAILED could be returned if the IrConnect structure was not bound to the stack or the packet size exceeds the maximum size.

On the server side, you will receive an LEVENT_TEST_IND event. rxBuff and rxLen in IrCallBackParms contain the test packet. The IrPacket structure contains the response packet. By default, it will contain the same packet that was sent as the test. However, you can change the data and that is what will be returned. Once the callback function returns, the test response will be sent.

Conclusion

This covers the IR API on the Palm III. If you carefully study the IR test application, you should have little trouble understanding the BattleShip application. I'd like to add a special thanks to Todd Warren at MCI WorldCom for his generous loan of the additional hardware used in development.

DDJ

Listing One

static void StartApplication(void )
{
    // check for ir
    if (SysLibFind(irLibName,&irref) != 0)
    {
        irAvail = false;
    }
    else
    {
       if (IrOpen(irref,irOpenOptSpeed115200) != 0)
            irAvail = false;
        else
        {
            irAvail = true;
            IrBind(irref,&connect,BSIrCallBack);
            IrSetDeviceInfo(irref,(BytePtr)&connect.lLsap,1);
            IrSetConTypeLMP(&connect);
        }
    }
    if (!irAvail)
        FrmAlert(NoIRAlert);
    SysGetROMToken(0,sysROMTokenSnum,&serialNo,&serialNoLen);
    SysRandom(TimGetSeconds());
    game.recno = 65535;
    game.started = false;
    CurrentView = StartupFormForm;
    CurrentMenu = MenuInit(StartupMenuBar);
    FrmGotoForm(CurrentView);

    OpenDatabase(); 
}

Back to Article


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port


(a)
Err SysLibFind(CharPtr libName, Word *refNum);

(b)
Err IrOpen(Word refnum, DWord options);
Err IrClose(Word refnum);

(c)
IrStatus IrBind (UInt refNum, IrConnect* con, IrCallBack CallBack);
IrStatus IrUnbind (UInt refNum, IrConnect* con);

(d)
typedef void (*IrCallBack)(IrConnect* con, IrCallbackParms* irData);

(e)
IrStatus IrSetDeviceInfo(UInt refnum, BytePtr info, Byte len);

Example 1: (a) The SysLibFind() prototype; (b) IrOpen() and IrClose() prototypes; (c) prototypes for IrBind() and IrUnbind(); (d) IrCallBack()'s definition; (e) IrSetDevice() prototype.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port


(a)
IrStatus IrDiscoverReq(UInt refNum, IrConnect *con);

(b)
IrStatus IrConnectIrLap(Uint refnum, IrDeviceAddr deviceAddr);

(c)
IrStatus IrDisconnectIrLap (UInt refnum);

Example 2: (a) IrDiscoverReq() prototype; (b) starting a connection using IrConnectIrLap(); (c) IrDisconnectIrLap()'s prototype.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port


(a)
void IrSetConTypeLMP (IrConnect* con);
void IrSetConTypeTTP (IrConnect *con);

(b)
IrStatus IrConnectReq(UInt refnum, IrConnect* con, 
                              IrPacket* packet, Byte credit);

(c)
IrStatus IrConnectRsp (UInt refnum, IrConnect* con, 
                              IrPacket* packet, Byte credit);

(d)
IrStatus IrDataReq (UInt refnum, IrConnect* con, IrPacket* packet);
Word IrMaxRxSize(UInt refnum, IrConnect* con);

(e)
void IrLocalBusy (UInt refnum, BOOL flag);

(f)
BOOL IrIsLapConnected (UInt refnum);
BOOL IrIsMediaBusy (UInt refnum);
BOOL IrIsNoProgress(UInt refnum);
BOOL IrIsRemoveBusy(UInt refnum);
Word IrMaxRxSize(UInt refnum, IrConnect* con);

Example 3: (a) Using the IrSetConTypeLMP or IrSetConTypeTTP functions; (b) initiating a connection peer with a call to IrConnectReq; (c) accepting the connection via IrConnectRsp; (d) imitating a conversation using IrDataReq; (e) IrLocalBusy prototype; (f) prototypes of functions available for protocol stack.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port


IrStatus IrTestReq (UInt refnum, IrDeviceAddr devAddr, 
                               IrConnect* con, IrPacket* packet);

Example 4: The IrTestReq function.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port

Figure 1: The IrDA protocol stack.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port

Figure 2: Running the IR test application.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port

Table 1: IrCallbackParms structure.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port

Table 2: Events for the IrCallback() function.


Copyright © 1999, Dr. Dobb's Journal
Apr99: The PalmPilot's Infrared Port

Table 3: IrDeviceList structure.


Copyright © 1999, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.