A.3. Registering for exceptions from the Cascade DataHub

/*
 * Cascade DataHub point waiter:  waiter.c
 *
 * (C) Copyright Cogent Real-Time Systems Inc., 1997.  All rights reserved.
 *
 * This program registers for exceptions on all or selected points
 * in the Cascade DataHub.
 *
 * The program waits in an infinite receive loop for exceptions.
 * Upon receipt of a message the program checks that the message is
 * a Cascade DataHub exception and then parses and prints the point
 * to stdout.
 *
 * Since this program can be made to register for exceptions on multiple
 * points a linked-list facility is used to create a list of point names
 * from the passed args.  This linked list is then walked to register
 * for exceptions on each point individually.
 *
 * This program is supplied with the Cascade DataHub programming API.  It
 * may be copied or modified, in whole or in part, for the sole purpose of
 * creating applications to be used with the Cascade DataHub.
 */

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <unistd.h>
#include    <time.h>
#include    <sys/kernel.h>

#include    <cogent/cogdb.h>

/*
 * millisecs function converts nanoseconds (10E-9) to milliseconds (10E-3).
 */
static int millisecs(PT_pCPOINT ppoint)
{
    return (ppoint->nanoseconds / 1000000);
}

/*
 * Generate an ASCII representation of the date and time for a given
 * point, truncating the nanoseconds to the next lowest millisecond.
 * If the time values are 0,0, then generate the string "none".
 */

static char* timeof(PT_pCPOINT ppoint)
{
    static char    ctm[64], msec[16];
    char        *thetime;
    time_t        tm;

    if (ppoint->seconds)
    {
        tm = ppoint->seconds;
        thetime = ctime (&tm);
        strncpy (ctm, thetime, 11);
        strncpy (&ctm[11], &thetime[20], 4);
        strncpy (&ctm[15], &thetime[10], 9);
        ctm[24]='\0';
        sprintf (msec, ".%03d", millisecs(ppoint));
        strcat (ctm, msec);
    }
    else
    {
        strcpy (ctm, "none");
    }
    return (ctm);
}

/*
 * Print point name, value and information to the standard output
 */
void print_point (PT_pCPOINT ppoint)
{
    printf ("Point: %s\n", ppoint->name);
    switch (ppoint->type)
    {
      case PT_TYPE_INT32:
        printf ("Value: %d\n", ppoint->value.i);
        break;
      case PT_TYPE_REAL:
        printf ("Value: %.20g\n", ppoint->value.r);
        break;
      case PT_TYPE_STRING:
        printf ("Value: %s\n", ppoint->value.s);
        break;
      default:
        printf ("Value: Unknown\n");
        break;
    }
    printf ("Conf:  %d, Lock: %s, Time: %s, Security: %d\n",
            ppoint->conf, (ppoint->locked ? "yes" : "no"),
            timeof(ppoint),
            ppoint->security);
}

void Usage (char** argv)
{
    print_usage (argv);
    exit(1);
}

#ifdef __USAGE
%C [-d domain] [-q queuename] pointname...
    -d domain - set the domain for the operation
    -q        - name of queue for point changes
    -v        - print debugging information

    Points in another domain may be watched by
    texplicitly naming the domain followed by a colon
    and the point name.
        e.g.,
            waiter mixer:Mixer_1_Weight
        is the same as
            waiter -d mixer Mixer_1_Weight
#endif

void main (int argc, char** argv)
{
    IP_hMSG            hmsg;
    ST_STATUS        status;
    PT_stCPOINT        point;
    char            *ptname = NULL, *qname = NULL, *domain=NULL;
    char            *s, namebuf[256], *name = NULL, *msgend;
    int                i, debugging=0;
    IP_hTASK        htask, sender;
    IP_MSG_TYPE        type;
    LL_LIST            names;
    LL_stITERATOR    it;

    hmsg = IP_CreateMsg (0, 0, NULL, IP_MAX_MESSAGE, NULL);

    /*
      * Create an empty link list 
     */
    names = LL_New();
    
    for (i=1; i<argc; i++)
    {
        if (argv[i][0] == '-')
        {
            switch (argv[i][1])
            {
              case 'd':
                domain = argv[++i];
                if (strlen(domain) > 15)
                    domain[15] = '\0';
                break;
              case 'q':
                qname = argv[++i];
                break;
              case 'v':
                debugging = 1;
                break;
              default:
                Usage (argv);
                break;
            }
        }
        else
        {
            /*
             * This else clause adds the current arg to the end of the
             * linked list
             */
            LL_AddTail (names, argv[i]);
        }
    }

    /*
     * If a queue name is not specified then generate one based on the
     * process pid
     */
    if (!qname)
    {
        sprintf (namebuf, "sc/wait%d", getpid());
        qname = strdup (namebuf);
    }
    if ((s = strrchr (argv[0], '/')))
        s++;
    else
        s = argv[0];

    /*
     * Generate a registered name based on the process pid
     */
    if (!name)
    {
        sprintf (namebuf, "sc/wait%d", getpid());
        name = strdup (namebuf);
    }
    
    /*
     * Initialize this process with the name server and queue server
     */
    htask = NS_Init (name, qname, domain, 0);

    memset (&point, 0, sizeof(point));
    
    /*
     * Traverse the link list of points (if passed) and register for
     * exceptions on each.  Otherwise register for all exceptions.
     */
    if (LL_Count(names))
    {
        LL_TRAVERSE (names, char*, ptname, it)
        {
            printf ("Register %s\n", ptname);
            point.name = ptname;
            point.conf = 0;
            point.address = NULL;
            if ((status = DH_RegisterPoint (htask, &point, hmsg, NULL))
                != ST_OK)
                printf ("Register point failed: %d\n", status);
        }
    }
    else
    {
        if ((status = DH_RegisterAllPoints (htask, NULL, 1,
                                            hmsg, NULL)) 
!= ST_OK)
        {
            printf ("Register all points failed: %d\n", status);
            exit (-1);
        }
    }

    /*
     * Provide a point name buffer separately from the rest of the point
     * structure.  There is no way for the API to know what the allocation
     * status of a point name is, so it will never attempt to free this
     * buffer, nor write into it.
     */
    point.name = namebuf;

    /*
     * Infinite event loop
     */
    for (;;)
    {
        /*
         * Sit receive blocked
         */
        sender = IP_TaskReceive (htask, NULL, hmsg, &type, NULL);
        if (debugging)
            printf ("Received: %s\n", IP_ReadMsgBuffer (hmsg));

        /*
         * If the message is an exception notice from the Cascade DataHub
         * then parse and print the point.  We could get messages from
         * other sources as well, which we effectively ignore.  If any
         * process attempts to Send a message to this task, we just send
         * back a nil response.
         */
        switch (type)
        {
          case IP_ASYNC:            /* DataHub point, probably */
            switch (IP_MsgCommand (hmsg))
            {
              case ST_DH_EXCEPTION:
                /* Deal with exceptions by parsing and printing the
                 * result */
                for (msgend = IP_ReadMsgBuffer(hmsg); *msgend; )
                {
                    DH_ParsePointString (&point, point.name, msgend,
                                         &msgend, NULL);
                    print_point (&point);
                }
                break;
              case ST_DH_ECHO:
                /* We never write a point, so ECHO is impossible in this
                 * app.  If we expected echoes, we could handle them in
                 * the same way as exceptions.  The message contents are
                 * identical for both. */
                break;
              default:
                /* Async general ASCII message, sent by another task using
                 * the Cascade DataHub API.  One such task is the Cascade NameServer,
                 * nserve.  It will send taskstarted and
                 * taskdied messages when other tasks start and stop:
                 * (taskstarted <name> <queue> <domain> <node> <pid>)
                 * (taskdied <name> <queue> <domain> <node> <pid>)
                 */
                break;
            }
            break;
          case IP_ERROR:
            /* Receive returned zero, which should never happen. */
            break;
          case IP_SYSTEM:
            /* A task death message that can only be received by a task
             * that has set its QNX INFORMED bit.  You should avoid this,
             * and use the taskdied and taskstarted messages in the
             * IP_ASYNC section if possible. */
            break;
          case IP_SIGNAL:
            /* A signal caused the Receive to exit prematurely.  No message
             * was received.  The value of sender is not defined. */
            break;
          case IP_NO_MSG:
            /* The queue reported a message waiting, but none was available.
             * This happens if the application intentionally drains the queue,
             * which is generally a bad idea. */
            break;
          case IP_SYNC:
            /* Synchronous general ASCII message, sent by another task using
             * the Cascade DataHub API. */
            IP_FillMsg (hmsg, IP_NOP, ST_ERROR, "Unsupported", 12, NULL);
            IP_TaskReply (htask, sender, hmsg, NULL);
            break;
          case IP_RAW_MSG:
            /* Message sent using QNX Send command, not through the
             * Cascade DataHub API.  This is also where a proxy would
             * be delivered if we had set up a proxy within this task,
             * for example, a periodic timer.  In that case, the proxy
             * ID would be (pid_t)sender, and we would not Reply to it. */
            Reply ((pid_t)sender, "Unsupported", 12);
            break;
        }
    }
}

Copyright 1995-2002 by Cogent Real-Time Systems, Inc.