http://www.developer.com/

Back to article

Palm OS Communications Primer: IR Library


August 31, 2004

We have already covered Serial Manager and Net Library briefly in earlier articles. One more important communication player left untouched is the Infrared Library (IR Library). Actually, you might use IR either with Exchange Manager or Serial Manager (IrCOMM). IR Library gives you low-level access to the IR capabilities of a Palm device. When may you want to deal with IR? Well, when you need to print via the infrared port or transmit data to different devices supporting IrDA are good examples. So, let's start.

Quick introduction

The IR support provided by the Palm OS is compliant with the IrDA specifications. As usual, an IR stack consists of several protocol layers. You will find more information at http://www.IrDA.org/. Here, we only put on a stack diagram taken from PalmOS Help as in figure 1.

In a couple of words, there is a low hardware level (SerialIR/FastIR); then, over it is the IR Link Access Protocol (IrLAP) that provides a data pipe between IrDA devices. IrLMP, the IR Link Management Protocol, manages multiple sessions using the IrLAP. Tiny TP is a lightweight transfer protocol on which some higher-level IrDA layers are built. All these layers are a must.

IrComm and IrLAN are optional; hence, their support is not guaranteed under Palm OS. But OBEX is actually available through the Exchange Manager API. In addition, The Palm OS IR Library supports IAS, a database service devices may use to publish information about themselves and services provided and get the same info about others.

How it all works

After such an academic start, let's consider some practical aspects. Serial communications is the simplest case. All you need to do is specify sysFileCVirtIrComm as the serial port name. The next sample shows all the stuff:

static void DoPDA()
{
   Char szError[128];
   MemSet(szError,sizeof(szError),0);
   Char szBuffer[128];
   StrCopy(szBuffer,"Sample Message");
   Char szAnswer[128];
   MemSet(szAnswer,sizeof(szAnswer),0);

   UInt16 dwSize = StrLen(szBuffer) + 1;

   UInt16 nPortId;
   Err err = SrmOpen(sysFileCVirtIrComm,115200,&nPortId);
   if ( err != errNone )
   {
      StrPrintF(_szInfo,"SrmOpen returned error = 0x%X - %s\n",err,
                GetSerialErrorText(err, szError));
      Log(_szInfo);
      return;
   }

   // Send buffer size
   UInt32 nBytes = SrmSend(nPortId, &dwSize, sizeof(dwSize), &err);
   StrPrintF(_szInfo,"1. SrmSend sent %lu bytes\n",nBytes);
   Log(_szInfo);

   if ( err != errNone )
   {
      StrPrintF(_szInfo,"1. SrmSend returned error = 0x%X - %s\n",
                err,GetSerialErrorText(err, szError));
      Log(_szInfo);
      goto CleanupSer;
   }

   // Receive ACK
   err = SrmReceiveWait(nPortId,3,5 * SysTicksPerSecond());
   if ( err != errNone )
   {
      StrPrintF(_szInfo,"1. SrmReceiveWait returned error = 
                0x%X - %s\n",err,GetSerialErrorText(err, szError));
      Log(_szInfo);
      goto CleanupSer;
   }
   Log("1. SrmReceiveWait is OK\n");

   nBytes = SrmReceive(nPortId, (void*)szAnswer, 3,
                       5 * SysTicksPerSecond(), &err);
   if ( err != errNone )
   {
      StrPrintF(_szInfo,"1. SrmReceive returned error =
                0x%X - %s\n", err,GetSerialErrorText(err, szError));
      Log(_szInfo);
      goto CleanupSer;
   }
   StrPrintF(_szInfo,"1. SrmReceive returned buffer %s\n",szAnswer);
   Log(_szInfo);

   // Send buffer
   nBytes = SrmSend(nPortId, szBuffer, StrLen(szBuffer) + 1, &err);
   StrPrintF(_szInfo,"2. SrmSend sent %lu bytes\n",nBytes);
   Log(_szInfo);

   if ( err != errNone )
   {
      StrPrintF(_szInfo,"2. SrmSend returned error = 0x%X - %s\n",
                err,GetSerialErrorText(err, szError));
      Log(_szInfo);
      goto CleanupSer;
   }

CleanupSer:

   SrmClose(nPortId);
}

That's just a slightly modified example from the first article in this cycle. Using similar code at the 'server' side, we will be able to exchange data over a virtual serial port.

Coming back to IR Library itself, let's first overview a typical scenario. Usually, you'll be required to do the following steps:

  • Search for IR Library with SysLibFind
  • Open IR Library with IrOpen
  • Obtain a local LSAP selector and bind the connection with the protocol stack with IrBind
  • Check that media is ready
  • Discover devices in range with IrDiscoverReq
  • Make IrLAP connection to desired device with IrConnectIrLap
  • Run IAS query to get remote LSAP
  • Make IrLmp or Tiny TP connection with IrConnectReq
  • Exchange data with e.g. printer or phone IrDataReq
  • Disconnect
  • Close IR Library with IrClose

Most of the IR Library functions return control immediately. Your application will be notified about events occurring in the system via a callback mechanism. You're passing a pointer to the callback function as the last parameter of IrBind API. The same model also works for IAS queries. As a result, your application must support some method of waiting for the result of the IR API called. Besides, it'd be nice not to hang up the UI while communications are in a waitable state, so you have to take care about event processing when it's needed. Below, we will consider all stages step by step.

Initialization

This part is relatively simple. Take a look at following code snippet:

Byte _deviceInfoXID[] = { IR_HINT_PDA, 0, 'P', 'a', 'l', 'm' };

static void MainFormInit(FormType *frmP)
{
   ...
   if(SysLibFind(irLibName, &_IrLibraryRefNum)!=0)
   {
      StrPrintF(_szInfo,"SysLibFind(%s) failed\n", irLibName);
      Log(_szInfo);
      _bIsIrLibAvailable = false;
   }
   StrPrintF(_szInfo,"IR library reference: %d\n", _IrLibraryRefNum);
   Log(_szInfo);

   if ( IrOpen(_IrLibraryRefNum, irOpenOptSpeed115200) != 0 )
   {
      Log("IrOpen() failed\n");
      return;
   }

   if( IrBind(_IrLibraryRefNum, &_IrConnection, IrCallback) !=
              IR_STATUS_SUCCESS )
   {
      Log("IrBind() failed\n");
      IrClose(_IrLibraryRefNum);
      return;
   }

   if( IrSetDeviceInfo(_IrLibraryRefNum, _deviceInfoXID,
                       sizeof(_deviceInfoXID)) != IR_STATUS_SUCCESS )
   {
      Log("IrSetDeviceInfo() failed\n");
      return;
   }

   if ( !waitUntilMediaReady() )
   {
      Log("IR media is busy!\n");
      return;
   }
   ...
}

It does exactly those calls we discussed above. The only thing that was not mentioned before is the IrSetDeviceInfo function. According its name, it sets the XID part of IrDeviceInfo struct. This information will be used during the discovery process; thus, other devices can know who are you. The XID struct contains the device hints (first two bytes) and nickname. By using the attached sample project, you can get some examples for different types of discovered IR devices, such as printers or mobile phones. Finally, after all initialization is completed, we wait for the media to be ready to carry out the next IR calls.

Callback Events

Throughout the rest of this article, we will refer to different events occuring at different stages of an IR session. The following table gives you short explanation of each of them:

Event Description
LEVENT_TEST_IND This event indicates that a TEST command frame has been received by the IR library. The received data is in rxBuff with size rxLen. The default response packet is set up with the received data, but it can be changed by modifying the packet. No explicit action is required for the TEST command to be responded to.
LEVENT_TEST_CNF This event indicates that a TEST command has completed. The status field indicates whether the test was successful. IR_STATUS_SUCCESS indicates success, and the response can be found in rxBuff with size rxLen. IR_STATUS_FAILED indicates that no TEST response was received. The packet passed to perform the test command is passed back in the packet field, and is now available. No separate packet handled event will occur.
LEVENT_DISCOVERY_CNF This event indicates that a discovery operation has completed. The field deviceList points to the discovery list. This list may be empty. Discovery can never fail, so this event always occurs after a discovery is initiated.
LEVENT_LAP_CON_IND This event occurs when the other side is trying to make a LAP connection. In practice, this event may be immediately followed by an LEVENT_LAP_DISCON_IND if the connection attempt fails.
LEVENT_LAP_CON_CNF This event occurs when a requested IrLAP connection has been successfully made.
LEVENT_LAP_DISCON_IND This event occurs when a request to initiate an IrLAP connection fails. It also occurs when the IrLAP connection has gone down. The event occurs on both sides. In practice, this event may immediately follow an LEVENT_LAP_CON_IND if the connection attempt fails. This event implies that the IrLMP connection has also gone down, if it was up. No separate event occurs for the IrLMP connection, except on Palm OS 4.0.
LEVENT_LM_CON_IND This event occurs when the other side has initiated an IrLMP connection. If we want to accept this connection, we must respond by calling IrConnectRsp(). The connection request includes arbitrary data that can be found at rxBuff with size rxLen. This data can be used to decide whether to accept the connection. The response also includes data to be sent back.
LEVENT_LM_CON_CNF This event indicates that the requested IrLMP connection has been made successfully. The connection response includes arbitrary data that can be found at rxBuff with size rxLen.
LEVENT_LM_DISCON_IND This event indicates that the IrLMP connection has been disconnected. Any data associated with the disconnection indication can be found at rxBuff with size rxLen. In practice, this event does not occur when the connection is broken, but does occur when this side attempts to initiate an IrLMP connection and the attempt fails. On Palm OS 4.0, this event also occurs when this side disconnects at the LAP level. The documentation is vague on this issue. In particular, the documentation says that LEVENT_LAP_DISCON_IND implies that the IrLMP connection has gone down, and that no other event will occur for this.
LEVENT_PACKET_HANDLED This event occurs when a packet passed in earlier becomes free again. This event occurs after IrDataReq, IrConnectReq, and IrConnectRsp, but not after IrTestReq. The test packet is considered handled when LEVENT_TEST_IND occurs.
LEVENT_DATA_IND This event occurs when data has been received. It can be accessed via rxBuff and rxLen. This event only occurs when data is sent by the other side using IrDataReq. It doesn't occur when data is received as part of an IrLMP connection request or response.
LEVENT_STATUS_IND This event occurs when the status changes. In practice, it is possible to get consecutive status events with the same status. The status field contains the new status, which can be one of:
  • IR_STATUS_NO_PROGRESS
    Means that IrLAP has made no progress for three seconds (beam is blocked).
  • IR_STATUS_LINK_OK
    Means that a no progress condition has cleared.
  • IR_STATUS_MEDIA_NOT_BUSY
    Means IR media was busy and is no longer.

You'll be responsible for providing the required code in response to incoming events.

Discovering devices in range

To to make any connection, you should first know what your target is. This process is called 'discovering'. The following sample code gives a simple example of how to do it:

static void DoDiscover()
{
   IrStatus ret;

   if ( !_bIsIrLibAvailable )
      return;

   _pendingCommand =  DiscoveryCommand;

   if( (ret=IrDiscoverReq(_IrLibraryRefNum, &_IrConnection)) !=
                         IR_STATUS_PENDING )
   {
      StrPrintF(_szInfo,"IrDiscoverReq() failed: %s\n",
                StatusName(ret));
      Log(_szInfo);
      return;
   }

   if ( !waitForResult() )
   {
      Log("Discovery has failed due to timeout or error\n");
   }
}

void IrCallback(IrConnect *connect, IrCallBackParms *parms)
{
   int i;
   
   switch(parms->event)
   {
   ...
   case LEVENT_DISCOVERY_CNF:
      if(parms->deviceList->nItems > 0)
      {
         MemMove(&_deviceInfo,&parms->deviceList->dev[0],
                 sizeof(IrDeviceInfo));
         for(i=0;i<parms->deviceList->nItems;i++)
         {
            DeviceType(parms->deviceList->dev[i],szDevInfo);
            StrPrintF(_szInfo,"Discovered: %s\n%sAddress: 0x%lx\n",
               &parms->deviceList->dev[i].xid[2],
               szDevInfo,
               parms->deviceList->dev[i].hDevice.u32);
            Log(_szInfo);
         }

         _commandSucceed = true;
      }
      else
      {
         Log("no IrDA devices in range\n");
         _commandSucceed = false;
      }
      _pendingCommand = NoCommand;
      break;
   ...
    }
}

Actually, the main part is happening in callback. You just save the received info about discovered devices somewhere for possible future usage.

Executing IAS queries

As we have noted earlier, you can use IAS queries to get information about devices within range. It's necessary to make a LAP connection before query execution. IAS is some kind of database where devices may publish info about themselves and their services. It accumulates records based on device class, protocol, parameter type, and so forth. The IR Library contains about a dozen IAS functions. The core one is IrIAS_Query. It enables you to retrieve requested information according to device class and parameter name. You will find some basical implementation of such queries in the article's sample project. If you need more details about IAS, you may find them at the IrDA site. This short article is just not a good place to discuss it.

Making a connection

After your application has gotten a remote LSAP value, it's possible to start a connection request. It is carried out by several steps. First, according to IrDA specifications, you should establish a LAP connection and then work over either a LMP or Tiny TP session. This last phase requires to know remote LSAP; thus, you should execute the IAS query after a successfull LAP connection. The following sample will help you to get the first glance on the whole process:

static void DoPrint()
{
   _pendingCommand = ConnectLapCommand;

   // _deviceInfo must be set by discovery command
   if (IrConnectIrLap(_IrLibraryRefNum, _deviceInfo.hDevice) !=
                      IR_STATUS_PENDING)
   {
      Log("Fail to establish LAP connection\n");
   return;
   }

   if ( !waitForResult() )
   {
      Log("Timeout is over for IrConnectIrLap\n");
      return;
   }

   if ( !connectIrLPT() )
   {
      Log("Fail to establish connection to printer\n");
      return;
   }

   // Do data transmission here
   ....

   if (IrDisconnectIrLap(_IrLibraryRefNum) != IR_STATUS_PENDING)
   {
      Log("Fail to disconnect via IrDisconnectIrLap\n");
      return;
   }
}

static bool connectIrLPT()
{
   Byte remoteLsap;
   IasQuery iasQuery(_IrLibraryRefNum);

   // Try connect to printer. LPT port uses LMP protocol and has
   // fixed IrLPT class
   if (iasQuery.getRemoteLsap(IrLptClass, IrLmpProtocol, remoteLsap))
   {
      StrPrintF(_szInfo,"remote LSAP is %u\n",remoteLsap);
      Log(_szInfo);

      if ( !connectLmp(remoteLsap) )
      {
         Log("Failed to connectLmp\n");
         return false;
      }
      return true;
   }

   Log("Fail to obtain remote LSAP\n");
   return false;
}

static bool connectLmp(Byte remoteLsap)
{
   _pendingCommand = ConnectLmCommand;

   IrSetConTypeLMP(&_IrConnection);

   _IrConnection.rLsap = remoteLsap;
   _packet.buff = NULL;
   _packet.len  = 0;

   if (IrConnectReq(_IrLibraryRefNum, &_IrConnection, &_packet, 0)
                    != IR_STATUS_PENDING)
   {
      Log("Fail to establish LMP connection\n");
      return false;
   }
   return waitForResult();
}

In fact, there is nothing too complicated here, except possibly the error handling, disconnection events, and so forth.

Transmitting the data

Because the connection is established, you can transmit data. That's quite a trivial task. The following code snippet shows all the story:

...
   char szTestData[] =
      "*******************\r\n"
      "IrLib Sample Packet\r\n"
      "*******************\r\n\r\n";

   _packet.buff = (Byte*)szTestData;
   _packet.len = StrLen(szTestData);

   _pendingCommand = SendDataCommand;
   if (IrDataReq(_IrLibraryRefNum, &_IrConnection, &_packet) !=
                 IR_STATUS_PENDING)
   {
      Log("Fail to send data\n");
      return;
   }

   if ( !waitForResult() )
   {
      Log("Timeout is over for sending data\n");
      return;
   }
...

Shutting down

All in all, you should unbind an existing connection from the IR protocol stack and close the IR Library:

static void AppStop(void)
{
   IrUnbind(_IrLibraryRefNum, &_IrConnection);
   IrClose(_IrLibraryRefNum);

   /* Close all the open forms. */
   FrmCloseAllForms();

}

Summary

As you might see, IR communications under Palm OS are a doable thing. A bit complicated from the very first touch, it's pretty usable later. I hope this article will help you to solve most of the common programming IR tasks.

Download

Download the accompanying code's zip file here.

About the Author

Alex Gusev started to play with mainframes at the end of the 1980s, using Pascal and REXX, but soon switched to C/C++ and Java on different platforms. When mobile PDAs seriously rose their heads in the IT market, Alex did it too. Now, he works at an international retail software company as a team leader of the Mobile R department, making programmers' lives in the mobile jungles a little bit simpler.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date