/*****************************************************************************/
/* Copyright 1991 to 1996  MBARI                                             */
/*****************************************************************************/
/* Summary  : RS485 Serial Protocol Client for Microcontroller Communications*/
/* Filename : sio32Client.c                                                  */
/* Author   : Andrew Pearce                                                  */
/* Project  : Tiburon ROV VME to Microcontroller Communications Interface    */
/* Version  : 2.1                                                            */
/* Created  : 10/22/91                                                       */
/* Modified : 03/08/96                                                       */
/* Archived :                                                                */
/*****************************************************************************/
/* Modification History:                                                     */
/* $Header: sio32Client.c,v 1.2 96/03/08 17:16:26 pean Exp $
 * $Log:        sio32Client.c,v $
 * Revision 1.2  96/03/08  17:16:26  17:16:26  pean (Andrew Pearce)
 * Added keep alive mode control. Cleaned up commends, fixed CHAN_CLOSE etc
 *
 * Revision 1.1  92/06/29  13:45:38  13:45:38  pean (Andrew Pearce 408-647-3746)
 * Initial revision
 *
 */
/*****************************************************************************/

#include "vxWorks.h"                /* vxWorks system declarations           */
#include "iosLib.h"                 /* vxWorks IO system declarations        */
#include "memLib.h"                 /* vxWorks Memory allocation declarations*/
#include "semLib.h"                 /* vxWorks semaphore functions           */
#include "msgQLib.h"                /* vxWorks Message Queue Library         */
#include "msgQSmLib.h"              /* vxWorks Sharded Memory Message Queues */
#include "smNameLib.h"              /* vxWorks Shared Memory Name Database   */

#include "mbariTypes.h"             /* mbari style guide type declarations   */

#include "sio32Drv.h"               /* SIO32 hardware and driver information */
#include "sio32Server.h"            /* Server side definitions               */
#include "sio32Client.h"            /* Application specific definitions      */

/*****************************************************************************/
/* Function    : sio32ServerRequest                                          */
/* Purpose     : sends request to sio32 Server specified                     */
/* Inputs      : sio32 server Identifier, request, reply buffer and length   */
/* Outputs     : Returns length of reply from server                         */
/*****************************************************************************/
    MLocal STATUS
sio32ServerRequest(sio32ServerId serverId, sio32Request sio32Req, char* reply,
    Nat16 maxBytes)
{
    char    msgQName[MAX_CHAN_NAME];/* Message O Name for name database      */
    MSG_Q_ID sio32RequestQId;       /* Message Q for requests to server      */
    int     objType;                /* Message Q Object Type                 */

                                    /* Validate server ID                    */
    if ((serverId != GMS_BOARD) && (serverId != TELEM_BOARD))
        return (ERROR);

    if (reply == NULL)              /* Does the client expect a reply ?      */
        sio32Req.replyQId = NULL;   /* No so ignore reply message Q          */

    else                            /* Client expects a reply so             */
    {                               /* Build Status Message Q Name for reply */
        sprintf(msgQName, "/%s/%s",
            (serverId == GMS_BOARD ? SIO32_BOARD_NAME : TELEM_BOARD_NAME),
            SIO32_STATUS_Q);
                                    /* Look up status message Q using name   */
        if (smNameFind (msgQName, (void **) &sio32Req.replyQId, &objType,
            WAIT_FOREVER) == ERROR)
               return (ERROR);
    } /* else */

    sprintf(msgQName, "/%s/%s",     /* Create request message Q name         */
        (serverId == GMS_BOARD ? SIO32_BOARD_NAME : TELEM_BOARD_NAME),
        SIO32_REQUEST_Q);
                                    /* Wait for server to initialize         */
    if (smNameFind (msgQName, (void **) &sio32RequestQId, &objType,
        WAIT_FOREVER) == ERROR)
            return (ERROR);
                                    /* Send request to SIO32 server          */
    if (msgQSend (sio32RequestQId, (char *) &sio32Req, sizeof(sio32Req),
        WAIT_FOREVER, MSG_PRI_NORMAL) == ERROR)
        return (ERROR);

                                    /* No reply expected so exit             */
    if ((reply == NULL) || (sio32Req.replyQId == NULL))
        return (OK);
                                    /* Read back the status of the request   */
    return (msgQReceive(sio32Req.replyQId, reply, maxBytes, WAIT_FOREVER));
} /* sio32ServerRequest() */

/*****************************************************************************/
/* Function    : sendWordPacket                                              */
/* Purpose     : creates a packet containing the command & sends it to micro */
/* Inputs      : sio32 channel struct, command word                          */
/* Outputs     : None                                                        */
/*****************************************************************************/
    Void
sendWordPacket(sio32Chan *sio32Channel, Word value)
{
    Char    buffer[sizeof(Word)];   /* buffer to hold the data               */

    wordToBuf(buffer, value);       /* copy the value into the buffer        */
                                    /* and send to the serial port           */
    writeServerPacket(sio32Channel, buffer, sizeof(Word));
} /* sendWordPacket */

/*****************************************************************************/
/* Function    : sendCmdArgPacket                                            */
/* Purpose     : creates a packet containing the command and argument given  */
/*               and sends it to the micro.                                  */
/* Inputs      : sio32 channel struct, command word and argument word        */
/* Outputs     : None                                                        */
/*****************************************************************************/
    Void
sendCmdArgPacket(sio32Chan *sio32Channel, Word command, Word argument)
{
    Char    buffer[sizeof(Word) * 2];/* buffer to hold the data              */

    wordToBuf(buffer, command);     /* copy the command into the buffer      */
                                    /* copy the argument into the buffer     */
    wordToBuf(buffer+sizeof(Word), argument);

                                    /* and send to the serial port           */
    writeServerPacket(sio32Channel, buffer, sizeof(Word) * 2);
} /* sendCmdArgPacket */

/*****************************************************************************/
/* Function    : writeServerPacket                                           */
/* Purpose     : called from application to send a packet to the micro       */
/* Inputs      : sio32 channel struct, packet and length                     */
/* Outputs     : returns result of sending packet to the server              */
/*****************************************************************************/
    STATUS
writeServerPacket(sio32Chan *sio32Channel, Byte *packet, Word length)
{
    STATUS  status;
                                    /* If Destination is Quad Serial Board   */
                                    /* Reformat Packet so Datacon will send  */
                                    /* Packet out Quad Serial Port           */
    if ( sio32Channel->channel & QUAD_SERIAL_BOARD )
        status = microForwardPacket(sio32Channel, packet, length);

    else                            /* Normal Data Packet Destination        */
                                    /* Send data to server                   */
        if ( (status = sendPacketToServer(sio32Channel, packet, length)) )
            logMsg("writeServerPacket ERROR\n");

    return(status);
} /* writeServerPacket() */

/*****************************************************************************/
/* Function    : sendPacketToServer                                          */
/* Purpose     : used by client to send a packet to the server/micro         */
/* Inputs      : sio32 channel struct, packet and length                     */
/* Outputs     : returns result of sending packet to the server              */
/*****************************************************************************/
    STATUS
sendPacketToServer(sio32Chan *sio32Channel, Byte *packet, Word length)
{
                                    /* Send data to server                   */
    return (msgQSend (sio32Channel->txDataQId, (char *) packet,
        length, WAIT_FOREVER, MSG_PRI_NORMAL));
} /* sendPacketToServer() */

/*****************************************************************************/
/* Function    : readServerPacket                                            */
/* Purpose     : used by the client to read a data packet from server/micro  */
/* Inputs      : sio32 channel struct, packet ptr, max length and timeout    */
/* Outputs     : returns length of packet read                               */
/*****************************************************************************/
    STATUS
readServerPacket(sio32Chan *sio32Channel, Byte *packet, Int16 maxBytes,
    Int32 timeout)
{
    return (msgQReceive(sio32Channel->rxDataQId, (char *) packet,
         maxBytes, timeout));
} /* readServerPacket() */

/*****************************************************************************/
/* Function    : readSRQPacket                                               */
/* Purpose     : used by the client to read a SRQ (alarm) from server/micro  */
/* Inputs      : sio32 channel struct, packet ptr, max length and timeout    */
/* Outputs     : returns length of packet read                               */
/*****************************************************************************/
    STATUS
readSRQPacket(sio32Chan *sio32Channel, Byte *packet, Int16 maxBytes,
    Int32 timeout)
{
    return (msgQReceive(sio32Channel->rxSrqQId, (char *) packet,
         maxBytes, WAIT_FOREVER));
} /* readSRQPacket() */

/*****************************************************************************/
/* Function    : clearSio32Statistics                                        */
/* Purpose     : Clears statistics for specified sio32 serial channel        */
/* Inputs      : sio32 serial channel number                                 */
/* Outputs     : None                                                        */
/*****************************************************************************/
    STATUS
clearSio32Statistics(Nat16 serialChan)
{
    sio32Request sio32Req;          /* Request structure to send to server   */

    sio32Req.cmd = SIO32_CLR_STATS; /* Set request to Clear Statistics       */
                                    /* Select serial channel                 */
    sio32Req.channel = SIO32_CHANNEL(serialChan);

                                    /* Send request to SIO32 server          */
    if (sio32ServerRequest(SIO32_SERVER(serialChan), sio32Req, NULL, 0)
         == ERROR)
    {
        logMsg("Error clearing statistics for board %s channel %d\n",
            (SIO32_SERVER(serialChan) == GMS_BOARD ? SIO32_BOARD_NAME :
               TELEM_BOARD_NAME), SIO32_CHANNEL(serialChan));
        return(ERROR);
    } /* if */

    return(OK);                     /* Statistics cleared so return OK       */
} /* clearSio32Statistics() */

    STATUS
getSio32Statistics(Nat16 serialChan, pktStat *statistics)
{
    sio32Request sio32Req;          /* Request structure to send to server   */

    sio32Req.cmd = SIO32_GET_STATS; /* Set request to Get Statistics         */
                                    /* Select serial channel                 */
    sio32Req.channel = SIO32_CHANNEL(serialChan);

                                    /* Send request to SIO32 server          */
    if (sio32ServerRequest(SIO32_SERVER(serialChan), sio32Req,
        (char *) statistics, sizeof(pktStat)) == ERROR)
    {
        logMsg("Error reading statistics for board %s channel %d\n",
            (SIO32_SERVER(serialChan) == GMS_BOARD ? SIO32_BOARD_NAME :
               TELEM_BOARD_NAME), SIO32_CHANNEL(serialChan));
        return(ERROR);
    } /* if */

    return(OK);                     /* Statistics received so return OK      */
} /* getSio32Statistics() */

/*****************************************************************************/
/* Function    : getSio32PSStatus                                            */
/* Purpose     : Returns Power Supply Status signals from the SIO32 hardware */
/*                These signals, PS1_GOOD and PS2_GOOD indicate that the 2   */
/*               power supplies on the SIO32 breakout board are operating.   */
/*               Returns word with bit 0 = PS1_GOOD and bit 1 = PS2_GOOD     */
/* Inputs      : server name                                                 */
/* Outputs     : Returns status word or ERROR and logs error message         */
/*****************************************************************************/
    Int32
getSio32PSStatus( sio32ServerId serverId )
{
    sio32Request sio32Req;          /* Request structure to send to server   */
    Int32   sio32PSStatus;          /* SIO32 Power supply status from server */

    sio32Req.cmd = SIO32_PS_STATUS; /* Set request to Get PSU Status         */
    sio32Req.channel = 0;           /* Set serial channel to 0 (not used)    */

                                    /* Send request to server on SIO32 board */
                                    /* and Read back the Power Supply Status */
    if (sio32ServerRequest(serverId, sio32Req,
        (char *) &sio32PSStatus,  sizeof(sio32PSStatus)) == ERROR)
    {
        logMsg("Error checking PSU Status for board %s\n",
            (serverId == GMS_BOARD ? SIO32_BOARD_NAME : TELEM_BOARD_NAME));
        return(ERROR);
    } /* if */

    return(sio32PSStatus);          /* Return PS_GOOD_STATUS to caller       */
} /* getSio32PSStatus() */

/****************************************************************************/
/* Function    : sio32DevChanCreate                                         */
/* Purpose     : Initialize RS232 Data channels to SIO32 Board              */
/* Inputs      : Chan No, thruster device name, Data channel structure      */
/* Outputs     : Returns OK or ERROR and logs error message                 */
/****************************************************************************/
    STATUS
sio32DevChanCreate(Nat16 serialChan, char *sio32DevName, sioProto proto, 
    Nat32 baud, Nat32 data, Nat32 stop, char *parity, sio32Chan *sio32DataChan)
{
    sio32Request sio32Req;          /* Request structure to send to server  */
    sio32InitReq *initReq;          /* Holds args for channel initialization*/

    Int32 initStatus;               /* Return status from server request    */
    char  msgQName[MAX_CHAN_NAME];  /* Message O Name for name database     */
    char  serverName[MAX_CHAN_NAME];/* Server name                          */
    int   objType;                  /* Message Q Object Type                */

                                    /* Validate server ID                   */
    if ((SIO32_SERVER(serialChan) != GMS_BOARD) &&
        (SIO32_SERVER(serialChan) != TELEM_BOARD))
    {
        logMsg("sio32ChanInit: Bad server id in serial channel number\n");
        return (ERROR);
    } /* if */

    sio32Req.cmd = SIO32_INIT_CHAN; /* Set request to Init SIO32 Channel    */
    sio32Req.channel = SIO32_CHANNEL(serialChan);

    initReq = (sio32InitReq *) sio32Req.args;

    strcpy(initReq->name, sio32DevName);
    strcpy(initReq->parity, parity);

    initReq->baudRate = baud;
    initReq->dataBits = data;
    initReq->stopBits = stop;
    initReq->protocol = proto;

    initReq->keepAlive.ticks = 0;    /* Use default Keep Alive packet params */
    initReq->keepAlive.count = 0;

    strcpy( serverName, (SIO32_SERVER(serialChan) == GMS_BOARD ?
        SIO32_BOARD_NAME : TELEM_BOARD_NAME) );

                                     /* Send request to SIO32 server         */
    if (sio32ServerRequest(SIO32_SERVER(serialChan), sio32Req,
        (char *) &initStatus, sizeof(initStatus)) == ERROR)
    {
        logMsg("Error initializing serial channel board %s channel %d\n",
            serverName, sio32Req.channel);
        return(ERROR);
    } /* if */

                                    /* Create Transmit Data Message Q Name   */
    sprintf(msgQName, "/%s/sio32Tx%02d", serverName,
	    SIO32_CHANNEL(serialChan));
    if (smNameFind (msgQName, (void **) &sio32DataChan->txDataQId, &objType,
        WAIT_FOREVER) == ERROR) return (ERROR);

                                    /* Create Receive Data Message Q Name    */
    sprintf(msgQName, "/%s/sio32Rx%02d", serverName,
	    SIO32_CHANNEL(serialChan));
    if (smNameFind (msgQName, (void **) &sio32DataChan->rxDataQId, &objType,
        WAIT_FOREVER) == ERROR) return (ERROR);

                                    /* Create SRQ Message Q Name             */
    sprintf(msgQName, "/%s/sio32SRQ%02d", serverName,
	    SIO32_CHANNEL(serialChan));
    if (smNameFind (msgQName, (void **) &sio32DataChan->rxSrqQId, &objType,
        WAIT_FOREVER) == ERROR) return (ERROR);

    sio32DataChan->channel = serialChan;
                                    /* Create mutex sem for serial Read/Write*/
    sio32DataChan->mutexSem = semMCreate (SEM_Q_FIFO | SEM_DELETE_SAFE);
                                    /* Set command/reply timeout = 500 msec  */
    sio32DataChan->timeout  = sysClkRateGet() / 2;

    return(OK);
} /* sio32DevChanCreate() */

/*****************************************************************************/
/* Function    : sio32ChanInit - same as sio32X_AndyChanInit                 */
/* Purpose     : Initialize Data and SRQ serial channels to microcontroller  */
/* Inputs      : Chan No, thruster device name, Data & SRQ chan structures   */
/* Outputs     : Returns OK or ERROR and logs error message                  */
/*****************************************************************************/
    STATUS
sio32ChanInit(Nat16 serialChan, char *sio32DevName,
    sio32Chan *sio32DataChan)
{
  return (sio32DevChanCreate(serialChan, sio32DevName, RS485_MODE,
	     SIO32_BAUD_RATE, 8, 1, "NONE", sio32DataChan));
} /* sio32ChanInit() */

/*****************************************************************************/
/* Function    : sio32X_AndyChanInit                                         */
/* Purpose     : Initialize Data and SRQ serial channels to microcontroller  */
/* Inputs      : Chan No, thruster device name, Data & SRQ chan structures   */
/* Outputs     : Returns OK or ERROR and logs error message                  */
/*****************************************************************************/
    STATUS
sio32X_AndyChanInit(Nat16 serialChan, char *sio32DevName,
    sio32Chan *sio32DataChan)
{
  return (sio32DevChanCreate(serialChan, sio32DevName, RS485_MODE,
	     SIO32_BAUD_RATE, 8, 1, "NONE", sio32DataChan));
} /* sio32X_AndyChanInit() */

/****************************************************************************/
/* Function    : sio32RS485ChanInit                                         */
/* Purpose     : Initialize RS485 Data channels to SIO32 Board              */
/* Inputs      : Chan No, thruster device name, Data channel structure      */
/* Outputs     : Returns OK or ERROR and logs error message                 */
/****************************************************************************/
    STATUS
sio32RS485ChanInit(Nat16 serialChan, char *sio32DevName, Nat32 baudRate,
    Nat32 dataBits, Nat32 stopBits, char *parity, sio32Chan *sio32DataChan)
{
  return (sio32DevChanCreate(serialChan, sio32DevName, RS485_NO_PROTO,
	     baudRate, dataBits, stopBits, parity, sio32DataChan));
} /* sio32RS232ChanInit() */

/****************************************************************************/
/* Function    : sio32RS232ChanInit                                         */
/* Purpose     : Initialize RS232 Data channels to SIO32 Board              */
/* Inputs      : Chan No, thruster device name, Data channel structure      */
/* Outputs     : Returns OK or ERROR and logs error message                 */
/****************************************************************************/
    STATUS
sio32RS232ChanInit(Nat16 serialChan, char *sio32DevName, Nat32 baudRate,
    Nat32 dataBits, Nat32 stopBits, char *parity, sio32Chan *sio32DataChan)
{
  return (sio32DevChanCreate(serialChan, sio32DevName, RS232_MODE,
	     baudRate, dataBits, stopBits, parity, sio32DataChan));
} /* sio32RS232ChanInit() */

/*****************************************************************************/
/* Function    : sio32ChanDestroy                                            */
/* Purpose     : Close serial channel & clean up serial channel resources    */
/* Inputs      : Serial channel number and sio32 channel data structure      */
/* Outputs     : None                                                        */
/*****************************************************************************/
    STATUS
sio32ChanDestroy( Nat16 serialChan, sio32Chan *sio32DataChan )
{
    sio32Request sio32Req;          /* Request structure to send to server   */

    sio32Req.cmd = SIO32_CLOSE_CHAN;/* Set request to Close SIO32 Channel    */
                                    /* Select serial channel                 */
    sio32Req.channel = SIO32_CHANNEL(serialChan);

                                    /* Send request to SIO32 server          */
    if (sio32ServerRequest(SIO32_SERVER(serialChan), sio32Req, NULL, 0)
         == ERROR)
        return(ERROR);
                                    /* Release miscelaneous resources        */
    semDelete(sio32DataChan->mutexSem);

    return(OK);                     /* Statistics received so return OK      */
} /* sio32ChanDestroy() */

/*****************************************************************************/
/* Function    : sio32ChanRxFlush                                            */
/* Purpose     : Flush receive packets from specified sio32 channel          */
/* Inputs      : Sio32 channel                                               */
/* Outputs     : Returns OK or ERROR                                         */
/*****************************************************************************/
    Void
sio32ChanRxFlush( sio32Chan *sio32Channel )
{
    Int32 bytes;
    char reply[sizeof(sio32Packet)];

    while (msgQNumMsgs(sio32Channel->rxDataQId))
    {
        bytes =  msgQReceive(sio32Channel->rxDataQId, reply,
                    sizeof(sio32Packet), NO_WAIT);
    } /* while */
} /* sio32ChanRxFlush() */

/*****************************************************************************/
/* Function    : sio32ChanSetRxTimeout                                       */
/* Purpose     : Sets serial I/O channel timeout value in ticks              */
/* Inputs      : Sio32 channel number, timeout                               */
/* Outputs     : None                                                        */
/*****************************************************************************/
    Void
sio32ChanSetRxTimeout( sio32Chan *sio32DataChan, Int32 timeout )
{
    sio32DataChan->timeout = timeout;
} /* sio32ChanSetRxTimeout() */

/*****************************************************************************/
/* Function    : sio32ChanSetKeepAliveMode                                   */
/* Purpose     : Sets serial I/O channel keep alive mode params ticks & count*/
/* Inputs      : Sio32 channel number, ticks and count                       */
/* Outputs     : Returns OK or ERROR                                         */
/*****************************************************************************/
/* Set ticks to WAIT_FOREVER to disable keep alive packets                   */
/* Set ticks to 0 for default behavior: keep alive packets every 333 msec    */
/* Set ticks to a value > 0 for a differnt keep alive packet rate            */
/* Set count to 0 for default behavior: continuous keep alive packets        */
/* Set count to a value > 0 to set consecutive keep alive packet count       */
/*****************************************************************************/
    STATUS
sio32ChanSetKeepAliveMode( Nat16 serialChan, Int32 keepAliveTicks,
    Nat32 keepAliveCount )
{
    sio32Request sio32Req;          /* Request structure to send to server   */
    sio32KeepAliveMode *kaMode;     /* Holds args for channel initialization */

    Int32 cmdStatus;                /* Return status from server request     */

                                    /* Validate server ID                    */
    if ((SIO32_SERVER(serialChan) != GMS_BOARD) &&
        (SIO32_SERVER(serialChan) != TELEM_BOARD))
    {
        logMsg("sio32ChanInit: Bad server id in serial channel number\n");
        return (ERROR);
    } /* if */

    sio32Req.cmd = SIO32_SET_KA_MODE; /* Set request to Set Keep Alive Mode  */
    sio32Req.channel = SIO32_CHANNEL(serialChan);

    kaMode = (sio32KeepAliveMode *) sio32Req.args;
    kaMode->ticks = keepAliveTicks;
    kaMode->count = keepAliveCount;

                                    /* Send request to SIO32 server          */
    if (sio32ServerRequest(SIO32_SERVER(serialChan), sio32Req,
        (char *) &cmdStatus, sizeof(cmdStatus)) == ERROR)
    {
        logMsg("Error setting keep alive mode for board %s channel %d\n",
            (SIO32_SERVER(serialChan) == GMS_BOARD ? SIO32_BOARD_NAME :
               TELEM_BOARD_NAME), SIO32_CHANNEL(serialChan));
        return(ERROR);
    } /* if */

    return(OK);
} /* sio32ChanSetKeepAliveMode() */

/*****************************************************************************/
/* Function    : sio32FwdChanInit                                            */
/* Purpose     : Initialize Data and SRQ serial channels to microcontroller  */
/* Inputs      : Chan No, device name, Data & SRQ chan structures            */
/* Outputs     : Returns OK or ERROR and logs error message                  */
/*****************************************************************************/
    STATUS
sio32FwdChanInit(Nat16 serialChan, char *sio32DevName, 
    sio32Chan *sio32DataChan, Nat16 attachedChan, sio32Chan *attachedDataChan)
{
    if ( sio32DataChan == ((sio32Chan *)NULL))
    {
        sio32ChanInit(serialChan, sio32DevName, sio32DataChan);
    } /* if */

    attachedDataChan->channel   = attachedChan;
    attachedDataChan->timeout   = sio32DataChan->timeout;
    attachedDataChan->mutexSem  = sio32DataChan->mutexSem;
    attachedDataChan->rxDataQId = sio32DataChan->rxDataQId;
    attachedDataChan->txDataQId = sio32DataChan->txDataQId;
    attachedDataChan->rxSrqQId  = sio32DataChan ->rxSrqQId;

    return(OK);
} /* sio32FwdChanInit() */

