/****************************************************************************
*                                                                           *
*                             a l m _ l i n e                               *
*                                                                           *
*                             by Kevin Millar                               *
*                                                                           *
*              Display latest alarm message on DM message line              *
*                                                                           *
*                                  Usage                                    *
*                                                                           *
*  alm_line  NAME  DM_LIST  [-d]                                            *
*                                                                           *
*  where                                                                    *
*        NAME    is the name the task will use register with on the IPC     *
*        DM_LIST is the name of a file containing a list of Display         *
*                Managers the alarm messages are to be displayed on.        *
*        -d      is an optional flag to activate the debug output           *
*                                                                           *
*                           W A R N I N G  !!!                              *
*                                                                           *
*  If a DM is not responding, the program will be very slow to respond, and *
*  alarm messages may be lost.                                              *
*                                                                           *
****************************************************************************/

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/param.h>
#include <fox/ipc.h>
#include <fox/om_user.h>
#include <fox/om_ecode.h>
#include <fox/alarmmsg.h>


#define MAX_DM 50				/* maximum number of DMs allowed */

#define RCV_FLAG 1				/* IPC flag */

char   task_name[14];			/* name of task object  */

char   rcv_buf[1024];			/* IPC receive buffer */
struct chk_clinfo info;			/* IPC connectionless data structure */
struct event_spec event;		/* IPC event data structure */
int    debug_flag = FALSE;		/* debug flag */

int    num_dms;					/* number of DMs in DM_FILE */
char   dm_list[128];			/* DM_List file name */
char   var_name[MAX_DM][14];	/* list of DM command variables */	
char   command[160];			/* DMCMD to be executed */

struct ALARM {
   short messg_type;
   char  date_time[DATE_TIME_LENGTH];
   char  letterbug[LETTERBUG_LENGTH];
   char  compound_name[COMPOUND_NAME_LENGTH];
   char  block_name[BLOCK_NAME_LENGTH];
   char  point_name[POINT_NAME_LENGTH];
   char  alarm_type_msg[ALARM_TYPE_MSG_LENGTH];
   short pnt_no;
   short sct_no;
   short opr_err;
   char  parameter_name[PARAMETER_NAME_LENGTH];
   char  tenths;
   char  inhprt;
   char  dummy[DUMMY_LENGTH];
   char  ack_state;
   short monotonic_time;
   short valid_time;
   short priority;
   short stepno;
   char  subrno;
   char  sbxno;
   char  alarm_limit[4];
   char  real_value[4];
   short block_dscrp_length;
   short msg__text_length;
   short in_out_length;
   short units_length;
   short state_text_length;
   char  text_buffer[LENGTH_LEFT];
} alarm_msg;					/* local copy of alarm message structure */

/* if the alarm message structure in the Foxboro include files is used, the
   size of the elements varies depending on the platform used.  There is
   probably a better way of doing this but it was easy and it worked */


/* declaration of local functions */
void cleanup();
int  swap_bytes( short *src, int num );
int  process_message( char *buf_ptr, int *size );
int  dump_data( char *addr, int size );



/****************************************************************************
*                                                                           *
* Function : main                                                           *
*                                                                           *
* Purpose  : process command line                                           *
*            read display manager list file                                 *
*            register task for connectionless IPC                           *
*            main loop                                                      *
*                wait for IPC message                                       *
*                process IPC message(s)                                     *
*            next IPC message                                               *
*                                                                           *
****************************************************************************/

main( argc, argv)
int  argc;
char *argv[];
{
   int result;
   int msg_id;
   int i, offset, size;
   char c;
   FILE *dm_file;


   /* display banner */

   printf("\n");
   printf("  Display alarms on message line (Ver 1.2) - Kevin Millar\n");
   printf(" =========================================================\n");
   printf("\n");

   /* process command line arguments */

   /* check number of arguments */	
   if ( argc<3 || argc>4 )
   {
      printf("Usage : %s NAME DM_LIST [-d]\n",argv[0]);
      printf("\n");
      printf("Alarm messages send to NAME will output to the message line\n");
      printf("of the DMs listed in the file DM_LIST\n");
      printf("\n");
      printf("                     W A R N I N G  !!!\n");
      printf("\n");
      printf("If a DM is not responding, the program will be very slow to\n");
      printf("respond, and alarm messages may be lost.\n");
      printf("\n");
      exit(0);
   }

   /* check for debug option */
   if ( argc==4 )
   {
      if ( strcmp( argv[3], "-d" ) == 0 )
      {
         printf("Debug output active\n\n");
         debug_flag = TRUE;
      }
   }

   /* process task name argument */
   if ( (int)strlen(argv[1]) > 12 )
   {
      printf("ERROR : task name too long\n");
      exit(-1);
   }

   strcpy( task_name, argv[1] );
   for (i=0; i<(int)strlen(task_name); i++)
      task_name[i] = toupper( task_name[i] );

   /* open the DM list file */
   dm_file = fopen( argv[2], "r" );
   if (dm_file==NULL)
   {
      printf("ERROR : unable to open DM list file \" %s \"\n", argv[2]);
      exit(-1);
   }

   if (debug_flag)
      printf("Message will be sent to the following DMs\n");

   /* loop thru contents of dm list file */
   num_dms = 0;
   fgets( var_name[num_dms], 8, dm_file );
   while ( !feof(dm_file) && num_dms<MAX_DM )
   {
	   /* process dm name */
      if ( var_name[num_dms][ strlen(var_name[num_dms])-1 ] == '\n' )
         var_name[num_dms][ strlen(var_name[num_dms])-1 ] = '\0';

      if ( (int)strlen(var_name[num_dms]) != 6 )
      {
         printf("ERROR : Invalid DM letterbug \"%s\"\n", var_name[num_dms] );
         fclose( dm_file );
         exit(-1);
      }

	  /* convert dm name to uppercase */
      for (i=0; i<6; i++)
         var_name[num_dms][i] = toupper( var_name[num_dms][i] );

      if (debug_flag)
         printf("%s\n", var_name[num_dms] );

	  /* build command variable name for DM */
      strcat( var_name[num_dms], "DMCMD" );

      num_dms++;

	  /* get next line from dm list file */
      fgets( var_name[num_dms], 8, dm_file );
   }

   /* close the dm list file */
   fclose( dm_file );

   if (debug_flag)
      printf("Total number of DMs = %d\n\n", num_dms );

   /* clear the IPC buffers */
   memset( &info, 0, sizeof(struct chk_clinfo) );
   memset( rcv_buf, 0, sizeof(rcv_buf) );

   /* fork the current process */
   i = fork();
   if (i<0)
   {
      /* error forking child process */
      if (debug_flag)
      {
         printf("Unable to execute in background\n");
         printf("Aborting...\n");
      }
      exit(-1);
   }
   else if ( i > 0 )
   {
      /* exit parent task */
      if ( debug_flag )
         printf("%s child PID = %d\n", argv[0], i );
      exit(0);
   }

   /* child task to continue - effect is task running in the background */

   if ( ! debug_flag )
   {
      for (i=0; i<NOFILE; i++)
         close(i);
   }

   /* divert signal processing to cleanup/exit function */
   if ( signal( SIGTERM, SIG_IGN ) != SIG_IGN )
      signal( SIGTERM, cleanup );

   signal( SIGHUP, cleanup );
   signal( SIGINT, cleanup );

   /* register and activate task for IPC comms */
   result = cs_register( task_name, 0, NULL, CDT, 0, 0 );

   result = cs_activate( task_name, CDT );

   /* create task object */
   result = obj_create( task_name, PROCESS );
   if ( result !=0 )
   {
      printf("Unable to create object - exiting\n");
      cs_unregister( task_name, CDT );
      exit(0);
   }

   printf("Ready to receive alarm messages.....\n\n");

   /* main (infinite) loop */
   while(1)
   {
      /* prepare to receive connectionless IPC message */
      event.efn = RCV_FLAG;
      msg_id = cl_receive( task_name, 1024L, &info, rcv_buf, &event);

	  /* wait for message */
      c_worevent((unsigned long)RCV_FLAG, 0L );

	  /* retrieve message */
      result = cl_chkio( msg_id, CLRECEIVE, &info, rcv_buf);

      if (debug_flag)
      {
         dump_data( rcv_buf, info.r_size );
      }

	  /* if message large enough then process */
      if ( info.r_size > 141 )
      {
         /* skip first 10 bytes */
         offset = 10;

		 /* loop through messages in buffer */
         while ( offset < info.r_size - 132 )
         {
            process_message( &rcv_buf[offset], &size );
            offset += size;
         }
      }
	  else
      {
         /* message too small to be an alarm message */
         if (debug_flag)
            printf("Message too small to be alarm message - ignoring\n");
      }

      /* repeat forever */
   }

}



/****************************************************************************
*                                                                           *
* Function : cleanup                                                        *
*                                                                           *
* Purpose  : unregister task                                                *
*            umimport command variables                                     *
*                                                                           *
****************************************************************************/

void cleanup()
{
   int i;

   printf("\nExiting...\n\n");

   cl_cancel( task_name );
   cs_unregister( task_name, CDT );
   obj_delete( task_name, PROCESS );

   for (i=0; i<num_dms; i++)
      unimport( var_name[i], VARIABLE );

   exit(0);
}



/****************************************************************************
*                                                                           *
* Function : swap_bytes                                                     *
*                                                                           *
* Purpose  : swap low order and high order bytes of 2-byte integers         *
*                                                                           *
*            Required for SPARC processors as the alarm messages are        *
*            in Intel format.  String elements do NOT require swapping, but *
*            integer and floating point fields require processing before    *
*            they can be used.                                              *
*                                                                           *
****************************************************************************/

swap_bytes( src, num )
short *src;
int num;
{
   short i; 
   short new;
   char *bytes;

   for (i=0; i<num; i++)
   {
      bytes = (char *)&src[i];
      new = (short)bytes[0] + (short)bytes[1]*256;
      src[i] = new;
   }
}



/****************************************************************************
*                                                                           *
* Function : process_message                                                *
*                                                                           *
* Purpose  : decode and process alarm message                               *
*                                                                           *
* NOTE THAT NOT ALL ELEMENTS IN THE ALARM MESSAGE STRUCTURE ARE DECODED IN  *
* THIS FUNCTION.  INTEGERS AND FLOATS REQUIRE BYTE SWAPPING TO CONVERT FROM *
* INTEL FORMAT TO SPARC                                                     *
*                                                                           *
****************************************************************************/

process_message( buf_ptr, size )
char *buf_ptr;
int *size;
{
   int dm, i, status, data_len, end_offset;
   short descrp_offset, msg_offset, state_offset, in_out_offset, units_offset;
   struct ALARM alarm_msg;
   char cbp_name[45];
   char alm_descrp[45];
   char blk_descrp[45];
   char message[80];

   /* transfer 132 bytes (fixed length portion) of message to the alarm message structure */
   memcpy( &alarm_msg, buf_ptr, 132 );

   /* swap bytes to convert from INTEL integers to SPARC integers */
   swap_bytes((short *)&alarm_msg.block_dscrp_length, 5 );
   swap_bytes((short *)&alarm_msg.messg_type, 1 );

   /* decode the text offset values */
   descrp_offset = ALM_NUMS+ALM_STGS;
   msg_offset    = descrp_offset + alarm_msg.block_dscrp_length;
   in_out_offset = msg_offset    + alarm_msg.msg__text_length;
   units_offset  = in_out_offset + alarm_msg.in_out_length;
   state_offset  = units_offset  + alarm_msg.units_length;
   end_offset    = state_offset  + alarm_msg.state_text_length;
   *size         = end_offset;

   /* transfer variable length fields to alarm message structure */
   memcpy( alarm_msg.text_buffer, &buf_ptr[end_offset], size-132 );

   /* extract message type */
   alarm_msg.messg_type &= 0xff;

   /* output debug messages */
   if (debug_flag)
      printf("alarm message type = %d\n", alarm_msg.messg_type );

   if (debug_flag)
   {
      printf("description   = %d\n",descrp_offset);
      printf("msg_offset    = %d\n",msg_offset );
      printf("in_out_offset = %d\n",in_out_offset);
      printf("units_offset  = %d\n",units_offset);
      printf("state_offset  = %d\n",state_offset);
      printf("end_offset    = %d\n",end_offset);
   }

   /* discard messages */
   if ( alarm_msg.messg_type > 28 || alarm_msg.messg_type == 22 )
      return;

   /* build C:B.P */
   sprintf( cbp_name, "%s:%s.%s", alarm_msg.compound_name,
                                  alarm_msg.block_name,
                                  alarm_msg.point_name );

   /* build message to display on DM message line */
   sprintf( message ," %s.%s  %s", alarm_msg.block_name,
                                   alarm_msg.alarm_type_msg,
                                   alarm_msg.text_buffer );

   /* build DM command string */
   sprintf( command, "msglin \"%s\"", message );

   if (debug_flag)
      printf("%s\n", command );

   /* write DM command to command variables */

   /* This section needs some work.  If the station is offline then setval()
      call will timeout after 12 seconds.  If there is a burst of alarms
	  then the delays could cause problems.  It needs to check the status of
	  of the stations, and only write to ones that are OK, and periodically
	  check the bad ones so it will detect when they come back online.  */

   for ( dm=0; dm<num_dms; dm++)
   {
      data_len = strlen( command );
      status = STRING;

      i = setval( var_name[dm], VARIABLE, 1, command, &status, data_len );

      if ( i != OM_SUCCESS )
         printf("ERROR : setval(\"%s\") = %d\n", var_name[dm], i );
   }

}



/****************************************************************************
*                                                                           *
* Function : dump_data                                                      *
*                                                                           *
* Purpose  : print hex and ascii codes of data (used in debug mode)         *
*                                                                           *
****************************************************************************/

dump_data( addr, size )
char *addr;
int size;
{
   int i, j;
   char c;

   for ( i=0; i<size; i+=16 )
   {
      for (j=0; j<16 && (i+j)<size; j++)
      {
         if ( j == 0 )
            printf("\n%03x : ",i);

         c = addr[i+j] & 0xff;

         printf("%02x ", c&0xff );
      }
      while (j<16)
      {
         printf("   ");
         j++;
      }
      printf("     ");
      for (j=0; j<16 && (i+j)<size; j++)
      {
         c = addr[i+j] & 0xff;
         if ( c>' ' && c<127)
            printf("%c",c);
         else
            printf(".");
      }
   }
   printf("\n");
}
