/* Counter.c  :  This program demonstrates the use of the
 * Demand Peripherals API for the Baseboard4 FPGA cards.
 *
 * The idea is that we create a counter and watch the
 * buttons on the FPGA card.  If button 1 is press the
 * count goes down.  If button 2 is pressed the count
 * is zeroed, and if button 3 is pressed the count is
 * incremented.  The count is an 8 bit signed number
 * that is displayed on the LEDs of the FPGA card.
 *
 * Build with: gcc -o counter counter.c
 * Be sure dpdaemon is running and listening on port 8870
 */


/* Overview:
 *  Open a TCP connection to the FPGA card.
 *  Send commands to flash the LEDs
 *  Open a second connection to the FPGA card
 *  dpcat the button presses on the second connection
 *  loop forever
 *   -- wait for button press (ignore release events)
 *   -- decrement, clear, or increment counter
 *   
 * As a tutorial, this program is not flexible, uses
 * magic numbers,  ignores key bounce, and does a very
 * poor job of error checking.  It tries to be brief and 
 * readable instead.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>    /* for memset */
#include <arpa/inet.h> /* for inet_addr() */


static int  cmdfd;          // FD for commands to the FPGA card
static void sndcmd(char *cmd); // send a command to the board, get prompt

int main()
{
    int8_t counter;         // the 8-bit count to display
    int  tmp_int;           // a temporary integer
    int  evtfd;             // FD for button events from the FPGA card
    struct sockaddr_in skt; // network address for dpdaemon
    int  adrlen;
    char strled[99];        // command to set the leds
    char strevt[99];        // where to receive the button press string
    int  buttons;           // latest button event as an integer

    // Open connection to DPserver daemon
    adrlen = sizeof(struct sockaddr_in);
    (void) memset((void *) &skt, 0, (size_t) adrlen);
    skt.sin_family = AF_INET;
    skt.sin_port = htons(8870);
    if ((inet_aton("127.0.0.1", &(skt.sin_addr)) == 0) ||
        ((cmdfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
        (connect(cmdfd, (struct sockaddr *) &skt, adrlen) < 0)) {
        printf("Error: unable to connect to dpdaemon.\n");
        exit(-1);
    }

    /* Blink the LEDs */
    sndcmd("dpset bb4io leds ff\n");
    sleep(1);
    sndcmd("dpset bb4io leds 00\n");
    sleep(1);
    sndcmd("dpset bb4io leds ff\n");
    sleep(1);
    sndcmd("dpset bb4io leds 00\n");

    /* Open another connection to receive button events */
    (void) memset((void *) &skt, 0, (size_t) adrlen);
    skt.sin_family = AF_INET;
    skt.sin_port = htons(8870);
    if ((inet_aton("127.0.0.1", &(skt.sin_addr)) == 0) ||
        ((evtfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) ||
        (connect(evtfd, (struct sockaddr *) &skt, adrlen) < 0)) {
        printf("Error: unable to connect to dpdaemon.\n");
        exit(-1);
    }

    counter = 0;   // leds are already showing zero

    /* Start the stream of button events */
    write(evtfd, "dpcat bb4io buttons\n", 20);
    /* the above command never returns so we do not use sndcmd() */

    while(1) {
        /* wait for a button press */
        read(evtfd, strevt, 3);    // two digits and a newline
        sscanf(strevt, "%d", &buttons);

        /* Examine pressed button and change counter */
        /* (We don't keep the old button value so do not do
           edge detection.  ie. One button at a time please) */
        if (buttons & 1) {
            counter--;
        }
        if (buttons & 2) {
            counter = 0;
        }
        if (buttons & 4) {
            counter++;
        }

        /* display new value of count */
        sprintf(strled, "dpset bb4io leds %02x\n", (counter & 0xff));
        sndcmd(strled);
    }
}

/* sndcmd():  Send a command to dpdaemon and wait for a response.  The
 *     response will be a prompt character, which we ignore and return,
 *     or an error message which we send to stderr. */
static void sndcmd(char *cmd)
{
    size_t count;          // number of chars in command to send
    char   c;              // prompt or error message character
    int    retval;         // return value of read()

    count = strlen(cmd);        // should sanity check count
    write(cmdfd, cmd, count);   // should look at write() return value

    /* loop getting characters.  Return on a prompt character '\' and
     * send any other character to stderr. */
    while (1) {
        retval = read(cmdfd, &c, 1);
        if (0 >= retval)
            exit(1);       // did TCP conn go down?
        else if ('\\' == c)
            return;        // got a prompt char.  Done with command
        else
            write(2, &c, 1);    // send to stderr
    }
}
