Sending Pulses from Photon to Neutrino

This demo consists of two simple programs: a Neutrino program that simply waits for pulses to be received, and prints out their contents, and a Photon application that sends pulses containing data chosen by the user. Note that this demo uses Neutrino pulses, not Photon pulses, which are substantially different.

It is normal practice in IPC systems to call the process that listens for messages the server, and the process that sends messages the client. This is because the message is often a means of requesting that some work be done. The client requests the work, and the server performs the work. This applies mostly to messages, as opposed to pulses, but the same principles apply in general.

The advantage of pulses is that they don't block the sender (unlike messages). This is especially useful for Photon applications, since blocking one of these will prevent the GUI display from being updated, with obvious ramifications. The disadvantage is that theey do not contain much data (only a byte code, and a 4-byte value).

The Photon Client Application

To build this app, create a base window, and add three PtLabels, two PtNumericIntegers, and a PtButton. Arrange them along these lines:

There are four functions. The ph_init() function contains the startup code for the app. It must be declared as the initialisation routine in PhAB. The btnSend_Activate() function is attached to the Activate callback of the PtButton. The wndBase_GotFocus() function is attached to the GotFocus callback of the base window. The GotFocus event should be used for arranging widgets contained by the window, since they are not realised until this event, so any attempt to set their resources in the window's earlier events (Opening, Realised) will fail. The final function, baseWnd_Closing(), is attached to the Closing callback of the main window, and is used to disconnect from the server's channel. A bit of good housekeeping.

The application reads the process and channel IDs for the server from the file pid.txt, and creates a connection to the channel. When a button is pressed, it sends a pulse to the server, based on the values of two number widgets.

The key QNX calls for sending pulses are ConnectAttach(), ConnectDetach(), and MsgSendPulse().

 
/*          P H O T O N    P U L S E    C L I E N T
/*                            AppBuilder Photon Code Lib */
/*                                         Version 2.01  */

/* Standard headers */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

/* Local headers */
#include "ablibs.h"
#include "abimport.h"
#include "proto.h"

#include <sys/neutrino.h>

/* Application Options string */
const char ApOptions[] =
   AB_OPTIONS ""; /* Add your options in the "" */

int channel_id;
pid_t process_id;
int conn_id;


/* ______________________________________________________________________ */
int
ph_clnt( int argc, char *argv[] )

{
   FILE *temp_file;

   /* eliminate 'unreferenced' warnings */
   argc = argc, argv = argv;

   /* First, read the nto process' ID from a file. */
   temp_file = fopen( "pid.txt", "r" );
   if (temp_file == NULL )
   {
      fprintf( stderr, "ph_clnt: ERROR: Can't open server-info file\n" );
      PtExit( EXIT_FAILURE );
   }
   fscanf( temp_file, "%d", &process_id );
   fscanf( temp_file, "%d", &channel_id );
   fclose( temp_file );

   conn_id = ConnectAttach(0, process_id, channel_id, 0, 0);
   if (conn_id == -1)
   {
      fprintf( stderr, "ph_clnt: ERROR: Can't connect\n" );
      PtExit( EXIT_FAILURE );
   }

   return( Pt_CONTINUE );
}


/* ______________________________________________________________________ */
int
btnSend_Activate( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )

{
   int this_priority;
   unsigned int *int_p;
   int pulse_code;
   int pulse_value;

   /* eliminate 'unreferenced' warnings */
   widget = widget, apinfo = apinfo, cbinfo = cbinfo;
   
   /* Send the pulse with the same priority level as this process */
   this_priority = getprio(0);

   /* Get the pulse code from the code widget */
   PtGetResource( ABW_numCode, Pt_ARG_NUMERIC_VALUE, &int_p, 0 );
   pulse_code = *int_p ;
   
   /* Get the pulse value from the value widget */
   PtGetResource( ABW_numValue, Pt_ARG_NUMERIC_VALUE, &int_p, 0 );
   pulse_value = *int_p ;
   
   /* Tell the user what's happenING */
   /* Note that this message will vanish almost immediately, since sending a
    * pulse is non-blocking. */
   PtSetResource( ABW_lblStatus, Pt_ARG_TEXT_STRING, "Sending pulse...", 0 );
   
   /* SEND THE PULSE */
   MsgSendPulse( conn_id, this_priority, pulse_code, pulse_value );

   /* Tell the user what happenED */
   PtSetResource( ABW_lblStatus, Pt_ARG_TEXT_STRING, "Pulse sent", 0 );

   return( Pt_CONTINUE );
}


/* ______________________________________________________________________ */
int
wndBase_gotFocus( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )

{
   char temp_str[255];
   
   /* eliminate 'unreferenced' warnings */
   widget = widget, apinfo = apinfo, cbinfo = cbinfo;
   
   sprintf( temp_str, "Connecting to process id: %d, channel id: %d", process_id, channel_id );
   PtSetResource( ABW_lblInfo, Pt_ARG_TEXT_STRING, temp_str, 0 );
   
   sprintf( temp_str, "Pulse code (>%d, <%d)", _PULSE_CODE_MINAVAIL, _PULSE_CODE_MAXAVAIL );
   PtSetResource( ABW_lblCode, Pt_ARG_TEXT_STRING, temp_str, 0 );

   /* Prevent the user from selecting an invalid pulse code */
   PtSetResource( ABW_numCode, Pt_ARG_NUMERIC_MIN, _PULSE_CODE_MINAVAIL, 0 );
   PtSetResource( ABW_numCode, Pt_ARG_NUMERIC_MAX, _PULSE_CODE_MAXAVAIL, 0 );

   /* Prevent the user from overflowing the 32-bit pulse value */
   PtSetResource( ABW_numValue, Pt_ARG_NUMERIC_MIN, -2147483647, 0 );
   PtSetResource( ABW_numValue, Pt_ARG_NUMERIC_MAX, 2147483647, 0 );
   
   return( Pt_CONTINUE );
}


/* ______________________________________________________________________ */
int
wndBase_closing( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )

{
   /* eliminate 'unreferenced' warnings */
   widget = widget, apinfo = apinfo, cbinfo = cbinfo;

   /* Disconnect from the channel, now that we are exiting. If we don't do
    * this, control doesn't return to the shell after the app window has been
    * closed. Both the client *and* the server have to be exited before the
    * shell regains control. */
   ConnectDetach( conn_id );
   
   printf( "ph_clnt: window closing\n" );

   return( Pt_CONTINUE );
}

The Neutrino Server Process

This Neutrino program creates a channel and writes its process and channel IDs to a file. It then runs an infinite loop waiting for pulses to be sent to it from clients. If this program is run in the background (using '&' on the shell command line), then make sure that this program is killed before running it a second time. Use ps to see if it is running, and check the file pid.txt to confirm the pid number, then kill that pid.

The key QNX calls for receiving pulses are ChannelCreate(), ChannelDestroy(), and MsgReceivePulse().


/*         N E U T R I N O    P U L S E    S E R V E R
#include <stdio.h>
#include <stdlib.h> /* for EXIT_FAILURE */
#include <sys/neutrino.h>


int
main( void )
{
   int recv_id;
   int channel_id;
   struct _pulse recv_msg;
   pid_t this_pid;
   FILE *temp_file;
   
   this_pid = getpid();

   /* Set up a channel through which clients can connect to me. */
   channel_id = ChannelCreate(0);
   if (channel_id == -1)
   {
      fprintf( stderr, "nto_srvr: ERROR: Couldn't create a channel\n" );
      return( EXIT_FAILURE );
   }
   
   printf( "nto_srvr: Process id = %d, channel id = %d\n", this_pid,  channel_id );
   
   /* Store our process and channel IDs for the client to find me. */
   temp_file = fopen( "pid.txt", "w" );
   fprintf( temp_file, "%d\n", this_pid );
   fprintf( temp_file, "%d", channel_id );
   fclose( temp_file );

   /* Keep running indefinitely. */
   while (1) 
   {
      /* Block until a message is ready for me on the specified channel.
       * Store the ID of the client, so I can reply to it uniquely. */
      recv_id = MsgReceivePulse( channel_id, &recv_msg, sizeof(recv_msg), NULL );

      printf( "nto_srvr: Received pulse ID: %d, code: %d, value: %d\n", 
            recv_id, recv_msg.code, recv_msg.value.sival_int );
   } /* while true */

   /* We'll never get here, but the OS will destroy the channel automatically 
    * when the process is terminated (by a CTRL-C for instance) anyway. */
   ChannelDestroy( channel_id );

   return;
}
   
   

Running the demo

The server needs to be run first (so that it can create a channel and publish its process and channel identities), so open a terminal window and run ./nto_srvr. It will display its process id and channel id to the screen, then wait for a pulse to be received. To avoid cluttering the output, open another terminal window then run the Photon application. The executable will be in the platform-specific directory (e.g. gcc_ntox86/) of the src/ directory. The filename will reflect the name you've given the app (when you first saved it).

In the Photon app, select any values you like for the code and value, then click the Send button to fire the pulse. On clicking the button, you should see the neutrino process in the other terminal window respond by displaying the data that you sent with the pulse. Note that the number selection widgets limit your choices to within valid ranges. The app displays the allowable range for the code, but for the value, because the size of the number that can be stored in 32 bits is so large, I didn't bother displaying the range, even though the widget still applies the limit.

Here is a screenshot of the two programs running together.


Home About Me
Copyright © Neil Carter

Content last updated: 2003-11-19