|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Pebbles Software ArchitectureFrom the Pittsburgh Pebbles PDA Projectby Rob Miller, Carl Evankovich, Benjamin Bostwick, Brad Myers
1. PurposeThe Pebbles architecture supports adding in new applications both on the PC and on the PDA side. We also provide a sample application called "Ping" with full source code, as a guide for others to develop new applications.
2. Ping Sample ProgramThe Ping application demonstrates simple two-way communication. When you press the button on the PDA screen, both the PC and the PDA should beep. The PC beeps because it receives a message from the PDA ("ping"), and the PDA beeps because the PC sends back a reply message ("pong"). If you hear both beeps, then both directions of communication are working. 2.1. Downloading PingThe Ping example application, including source code, is available in a ZIP archive: 2.2. Understanding PingLike all Pebbles applications, Ping consists of two components: an client that runs on your PDA, and a plugin that runs on your PC. In the ZIP file, you will find two versions of the client, one for Palm and one for Windows CE. There are also two versions of the plugin. One version is a DLL (dynamic link library) that is loaded dynamically into PebblesPC and communicates with PebblesPC using Windows messages. The other version of the plugin is a separate executable that runs in a different process and communicates with PebblesPC through a socket. Here is a guide to major directories and source code files in the ZIP file:
2.3. Compiling and Installing PingIf you want to compile Ping, you'll need the right tools. We use the following tools:
The ZIP file includes project files for these environments. The source code may work with other tools (such as gcc for the Pilot, or Borland C++ for Windows), but we haven't tried. The ZIP file includes precompiled binaries if you want to try Ping without compiling it first. The binaries are located in the same places the compilers would put them, so you can just skip the compiling steps in the instructions below.
Once you have at least one client and one plugin installed correctly, you should be able to connect your PDA to the PC and run Ping on your PDA. Tapping on the "Ping" button should make the PC beep, followed a short time later by a beep from your PDA.
3. Pebbles ProtocolThe communication protocol used by Pebbles applications is defined by the header file Pebbles.h (also available in the Ping ZIP archive as Ping\Pebbles.h). The following discussion essentially duplicates the comments in this header file. A Pebbles application consists of two parts: a program running on a PDA (the "client"), and a program running on a desktop computer (the "plugin"). The client and the plugin communicate through a central mediator called PebblesPC, a process which runs on the desktop computer and multiplexes clients with plugins. The basic architecture looks like this: +------------> DLL plugin | Windows | messages | PDA client <-------------> PebblesPC <-----+ serial port | or | network network socket | socket +------------> remote plugin process PDA clients communicate with PebblesPC through some kind of byte stream (serial ports and network sockets are supported in the current implementation). PebblesPC communicates with plugins using either Windows event messages (for DLL plugins, which are loaded into PebblesPC's address space) or a network socket (for plugins running in a different address space or across the network). This document defines the protocols for all three interfaces: the client protocol for the interface between clients and PebblesPC, the DLL plugin protocol for the interface between PebblesPC and DLL plugins, and the socket plugin protocol for the interface between PebblesPC and remote plugins. PalmPilot developers should be aware of the distinction between a Pebbles plugin and a HotSync conduit. The Pebbles architecture is intended to support applications that need a live connection between the PDA and the PC. Examples of such applications include chat and emulating the keyboard and mouse. A Pebbles plugin is ready to run at all times -- it responds immediately when a user connects a PDA to the PC and starts up the associated client on the PDA. HotSync conduits, on the other hand, are intended to synchronize data on the PDA with data on the PC, and are invoked only when the user initiates a HotSync. 3.1. Client ProtocolConnecting to PebblesPCAs indicated in the previous section, the PDA client communicates with PebblesPC through a byte stream. PebblesPC currently supports two byte stream interfaces:
Client Message FormatThe message protocol is the same regardless of whether the client connects through a serial port or a network socket. Messages are passed back and forth on the byte stream using a simple format. Short messages (less than 64KB) look like this: command 1 byte Command code (see below) length 2 bytes Length of data field in bytes, LSB first data <length> bytes Data or arguments for command
Pebbles.h defines several macros which are useful for parsing and assembling messages: Parsing: PEBBLES_CMD(msg) Returns the command code of a message PEBBLES_LEN(msg) Returns the length of a message's data block PEBBLES_DATA(msg) Returns a pointer to a message's data Assembling: PEBBLES_HEADER_SIZE # of bytes in a Pebbles short message header i.e., sizeof(command and length fields) PEBBLES_SET_CMD(msg, cmd) Sets the command code of a message PEBBLES_SET_LEN(msg, len) Sets the length of a message PEBBLES_DATA(msg) Returns a pointer to message data to be filled in Long Message FormatFor messages longer than 64KB, a long-message format is defined with a 32-bit length field:command 1 byte Command code (see below) flag 2 bytes Always 0xFFFF to indicate long message length 4 bytes Length of data field in bytes, little-endian data <length> bytes Data or arguments for command Several macros are provided for handling long messages: PEBBLES_LEN(msg) == PEBBLES_LONG_MESSAGE_SIZE Tests whether a message is short or long format Parsing: PEBBLES_CMD(msg) Returns the command code of a long message PEBBLES_LONG_LEN(msg) Returns the length of a long message's data block PEBBLES_LONG_DATA(msg) Returns a pointer to a long message's data Assembling: PEBBLES_LONG_HEADER_SIZE # of bytes in a Pebbles long message header i.e., sizeof(command, flag, and length fields) PEBBLES_SET_CMD(msg, cmd) Sets the command code of a long message PEBBLES_SET_LEN(msg, PEBBLES_LONG_MESSAGE_SIZE) Sets the flag field to indicate a long message PEBBLES_SET_LONG_LEN(msg, len) Sets the length of a long message PEBBLES_LONG_DATA(msg) Returns a pointer to message data to be filled in
Client Command CodesThe following command codes defined in Pebbles.h are reserved by PebblesPC for system commands: #define CMD_PEBBLES_NO_MESSAGE 0 // Reserved as a return value for PebblesReceiveHeader() #define CMD_PEBBLES_STARTUP 1 // Data: none // // Sent by PebblesPC to client when PebblesPC opens a serial port, // just in case an client is listening at the other end. // Client responds by sending CMD_PEBBLES_CHANGE_PLUGIN. #define CMD_PEBBLES_CHANGE_PLUGIN 2 // Data: // either no data // or three fields: plugin name (null-terminated string, case doesn't matter) // user name (null-terminated string) // hardware flow control, 1 byte (0 to disable, !0 to enable) // // Sent by client to switch to a plugin. If no data is sent with the message, // the client is disconnected from its plugin. Client should send a no-data // CMD_PEBBLES_CHANGE_PLUGIN when it exits, to notify plugin that it's // gone. #define CMD_PEBBLES_ACK_CHANGE_PLUGIN 3 // Data: none // // Sent by PebblesPC to acknowledge that it has found and // connected to the plugin requested by CMD_PEBBLES_CHANGE_PLUGIN. // Client should not send any user-defined commands // until it sees this acknowledgement. #define CMD_PEBBLES_NO_ACK_CHANGE_PLUGIN 4 // Data: none // // Sent by PebblesPC to inform the PDA that the requested // plugin could not be found. #define CMD_PEBBLES_KEEPALIVE 5 // Data: none // // Sent to ensure that connection between PDA and PC // is still active #define CMD_PEBBLES_FLOW_CONTROL 6 // Data: 1-byte flag to turn on or off flow control (1 means on, 0 means off) // // Sent by an app to change PebblesPC's output hardware flow control. // Flow control needs to be turned off for raw IR (which doesn't support it) // and turned on for regular serial ports. // // Hardware flow control is always re-enabled after every CHANGE_PLUGIN, // so the app must disable it after every CHANGE_PLUGIN if desired. #define CMD_PEBBLES_CHANGE_USERNAME 7 // Data: user name // // Sent by app to PC and contains user name #define CMD_PEBBLES_RESERVED1 8 // Reserved because of a bug -- PalmPilot sometimes sends this byte // when it opens its serial port. #define CMD_PEBBLES_RESERVED2 127 // Reserved because of a bug -- PalmPilot sometimes sends this byte // when it closes its serial port. #define CMD_PEBBLES_HOTSYNC -128 // Reserved because HotSync uses this byte to indicate the start of // a Hotsync session. To avoid conflicting with these and future system commands, Pebbles applications should limit their commands to the following range: #define CMD_PEBBLES_BASE 32 // smallest application-specific command #define CMD_PEBBLES_MAX 126 // largest application-specific command Under the current protocol, only one Pebbles application can use the serial connection at a time, so application developers need not worry that their application-defined command codes will conflict with those of other applications. 3.2. DLL Plugin ProtocolA DLL plugin is dynamically loaded into PebblesPC. When the plugin is loaded, it spawns a Win32 thread in PebblesPC's address space and returns the thread handle to PebblesPC. All subsequent communication between PebblesPC and the plugin occurs by passing Windows events back and forth, using the Win32 system call PostThreadMessage. Connecting to PebblesPCA plugin should be compiled as a DLL which exports the following startup function for PebblesPC to call: extern "C" BOOL WINAPI PebblesMain (HWND hwnd, PebblesPlugin *pPlugin); PebblesMain should perform initialization and create a thread for the plugin. The parameters are as follows:
PebblesMain should return true if the plugin started successfully. PebblesMain should start a running thread to serve as the plugin, and return the thread handle in pPlugin->thread. You can create a thread in a variety of ways under Win32: CreateThread(), _beginthread(), _beginthreadex(), or (using MFC) the class CWinThread are all possibilities. When PebblesPC starts up, it attempts to load all DLLs located in the same directory as PebblesPC.exe and calls PebblesMain on each one. Plugins which were successfully found and loaded are displayed in the "Plugins..." dialog box. Cautions For DLL Plugin DevelopersDLL plugins run in the same address space as PebblesPC. As a result, DLL plugin developers should observe a few cautions:
Plugin Event CodesAfter a plugin has been started, PebblesPC communicates with it by sending events to its Windows event queue. A Windows event has three fields: MSG (an event code), WPARAM (a 32-bit argument), and LPARAM (another 32-bit argument). The MSG codes used by PebblesPC are defined below: #define WM_PEBBLES_BASE (WM_USER + 4242) #define WM_PEBBLES_NEW_USER (WM_PEBBLES_BASE + 0) // Sent by PebblesPC to plugin when a new user connects to the plugin. // // wParam (PebblesUser*) points to PebblesUser struct. // Pointer is valid as long as user remains // connected; plugin should not free it. // // lParam 0 #define WM_PEBBLES_DONE_USER (WM_PEBBLES_BASE + 1) // Sent by PebblesPC to plugin when a user disconnects from the plugin. // // wParam (PebblesUser*) points to PebblesUser struct. // Pointer is valid as long as user remains // connected; plugin should not free it. // // lParam 0 #define WM_PEBBLES_RECEIVED (WM_PEBBLES_BASE + 2) // Sent by PebblesPC to the plugin when a user-defined command is // received from a connected app. // // wParam (PebblesUser*) points to PebblesUser struct. // Pointer is valid as long as user remains // connected; plugin should not free it. // // lParam (char*) points to the message, // which starts with a CMD_ code and a // length field, in the format described above. // Plugin must free this block with GlobalFree. #define WM_PEBBLES_SEND (WM_PEBBLES_BASE + 3) // Sent by plugin to PebblesPC to send a message back to the app. // The plugin should send this message with PostThreadMessage() // using the thread handle in the PebblesUser structure. // // wParam (PebblesUser*) points to PebblesUser struct. // Pointer is valid as long as user remains // connected; plugin should not free it. // // lParam (char*) points to the message, // which starts with a CMD_ code and a // length field, in the format described above. // Must be allocated with GlobalAlloc. // PebblesPC will free this block with GlobalFree. #define WM_PEBBLES_ABOUT (WM_PEBBLES_BASE + 6) // Sent by PebblesPC to plugin to display an About box describing // the plugin. Plugins with no About box can beep or do nothing. // // wParam (HWND) PebblesPC window handle // // lParam 0 #define WM_PEBBLES_PREFS (WM_PEBBLES_BASE + 7) // Sent by PebblesPC to plugin to display an optional configuration // dialog for the plugin. Plugins with no configuration can beep // or do nothing. // // wParam (HWND) PebblesPC window handle // // lParam 0 #define WM_PEBBLES_SOCKET_PLUGIN_NAME (WM_PEBBLES_BASE + 8) // Sent by PluginData to notify Pebbles that it has received a name // and is ready to be added to the plugin list // // wParam (CPebblesPluginData *) Pointer to Plugin Data class // // lParam 0 Several of these events include a pointer to PebblesUser, which is a structure describing a Pebbles user. The most important field of PebblesUser is thread, which is the handle of a PebblesPC thread that may be used to send messages back to the PDA corresponding to this user. typedef struct _PebblesUser { int id; // Small integer corresponding to user's serial // connection (numbered from 0). char *name; // User name (null-terminated; may be zero // length, but never NULL). unsigned long thread; // User thread ID; outgoing // WM_PEBBLES_SEND events should be posted // here. } PebblesUser; 3.3. Socket Plugin ProtocolPebblesPC also supports plugins running in a remote address space, either on the same machine or across the network, communicating by a network socket. If you have an existing executable program that you want to act as a Pebbles plugin, then you want to use this protocol. A simple implementation of the socket plugin protocol is provided with the Ping sample application (Ping\PC-exe\PebblesSocket.cpp and Ping\PC-exe\PebblesSocket.h). Connecting to PebblesPCTo connect to PebblesPC, call PebblesSocketConnect(). This section explains how the connection process works. The remote plugin originates the connection to PebblesPC. If PebblesPC is allowing network connections (controlled by a checkbox on the main PebblesPC dialog), it listens for plugin connections on port 4242 (PEBBLES_PLUGIN_PORTNUM in Pebbles.h). After opening a connection to PebblesPC, the plugin sends a CMD_SOCKET_PLUGIN_NAME message to provide its name to PebblesPC. Message FormatMessages in the socket plugin protocol are similar to the client protocol. Socket plugin messages have one additional field after the header: Short messages (less than 64KB): command 1 byte Command code (see below) length 2 bytes Length of data field in bytes, LSB first userid 1 byte User ID data <length> bytes Data or arguments for command Long messages: command 1 byte Command code (see below) flag 2 bytes Always 0xFFFF to indicate long message length 4 bytes Length of data field in bytes, little-endian userid 1 byte User ID data <length> bytes Data or arguments for command
Messages can be read and written using PebblesSocketRead() and PebblesSocketWrite(). These functions read and write full messages. For parsing and assembling socket plugin messages, Pebbles.h defines several macros. For the command and length fields, see the macros for the client protocol. PEBBLES_GET_USERID(msg) Returns the userid field of any kind of message PEBBLES_SET_USERID(msg, userid) Sets the userid field of any kind of message PEBBLES_SOCKET_MESSAGE(msg) Returns a pointer to a socket plugin message's data Socket Command CodesThe following command codes defined in Pebbles.h are used for the socket plugin protocol. // sent to socket plugin when a new user arrives #define CMD_SOCKET_NEW_USER 9 // Data: user name (no null terminator) // sent to socket plugin when a user leaves #define CMD_SOCKET_DONE_USER 10 // sent by socket plugin to announce its name to PebblesPC #define CMD_SOCKET_PLUGIN_NAME 12 // Data: plugin name (no null terminator)
4. Example InteractionIn this section, we illustrate the Pebbles protocol with a complete example using Ping. This example shows how PebblesPC starts up, how it finds and loads a DLL plugin, how it forwards messages between a client and the plugin, and how everything shuts down. The flowchart below depicts the three software entities (client, PebblesPC, and plugin) and the messages that pass between them.
Back to the Pebbles software PageBack to the Pebbles main PageMaintained by: Robert Miller |