/* ================================================================
** main.c
**
** Driver for easyDAQ SERDIO8R relay card.
**
** 30 DEC 2008  ryan eustice  Created and written.
** ================================================================
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>  // umask()
#include <sys/stat.h>   // "
#include <unistd.h>     // fork()
#include <strings.h>
#include <stdbool.h>

#include "libcommon/timestamp.h"
#include "libcommon/config.h"
#include "libcommon/daemon.h"
#include "libcommon/getopt.h"
#include "libcommon/error.h"
#include "libcommon/lcm_util.h"

#include "lcmtypes/senlcm_easydaq_t.h"

#include "easydaq.h"

#define EASYDAQ_CHANNEL ("EASYDAQ")

typedef struct _state_t state_t;
struct _state_t
{
    int fd;
    Config *cfg;
    getopt_t *gopt;
    lcm_t *lcm;
    senlcm_easydaq_t easydaq;
    uint8_t bitmask;
};

static void 
add_commandline_options (state_t *state, int argc, char *argv[])
{
    getopt_add_description (state->gopt, "EasyDAQ SERDIO8R 8-channel relay-board driver.");
    getopt_add_help (state->gopt, NULL);
    getopt_add_bool (state->gopt, 'D', "daemon", 0, "Run as system daemon");
    getopt_add_bool (state->gopt, 'i', "ignore", 0, "Ignore init relay state request in .cfg");
    getopt_add_string (state->gopt, '\0', "device", 
                       config_get_string_or_default (state->cfg, "hotel.easydaq.device", "/dev/ttyS0"),
                       "Device to connect to");

    if (!getopt_parse (state->gopt, argc, argv, 1) || state->gopt->extraargs->len!=0) {
        getopt_do_usage (state->gopt, NULL);
        exit (EXIT_FAILURE);
    }
    else if (getopt_get_bool (state->gopt, "help")) {
        getopt_do_usage (state->gopt, NULL);
        exit (EXIT_SUCCESS);
    }
}

static void
update_state (state_t *state)
{
    easydaq_setdir (state->fd, 0x00);
    int err = easydaq_read (state->fd, &state->bitmask);
    if (!err) {
        for (int i=0; i<8; i++) {
            if (state->bitmask & (1 << i))
                state->easydaq.relay[i].state = 1;
            else
                state->easydaq.relay[i].state = 0;
        }
    } 
    else // try again
        update_state (state);
}

static void
easydaq_t_callback (const lcm_recv_buf_t *rbuf, const char *channel, 
                    const senlcm_easydaq_t *easydaq, void *user)
{
    state_t *state = (state_t *) user;

    if (easydaq->self)
        return;
    
    // toggle relays
    for (size_t i=0; i<8; i++) {
        if (easydaq->relay[i].state)
            state->bitmask |= (1 << i);
        else
            state->bitmask &= ~(1 << i);
    }
    easydaq_setdir (state->fd, 0x00);
    easydaq_write (state->fd, state->bitmask);

    // publish current relay state
    update_state (state);
    state->easydaq.self = 1;
    state->easydaq.utime = timestamp_now ();
    senlcm_easydaq_t_publish (state->lcm, EASYDAQ_CHANNEL, &state->easydaq);
}


int main (int argc, char *argv[]) 
{   
    // init state
    state_t *state = (state_t *) calloc (1, sizeof (state_t));
    state->cfg = config_parse_default ();
    state->gopt = getopt_create ();

    add_commandline_options (state, argc, argv);

    if (getopt_get_bool (state->gopt, "daemon"))
        daemon_fork ();

    const char *device = getopt_get_string (state->gopt, "device");
    state->fd = easydaq_open (device);
    if (state->fd < 0) {
        ERROR ("easydaq_open");
        exit (EXIT_FAILURE);
    }

    // init lcm and subscribe
    state->lcm = lcm_create (NULL);
    if (!state->lcm) {
        ERROR ("lcm_create() failed");
        exit (EXIT_FAILURE);
    }
    senlcm_easydaq_t_subscribe (state->lcm, EASYDAQ_CHANNEL, &easydaq_t_callback, state);

    // parse config file
    state->easydaq.utime = timestamp_now ();
    for (int i=0; i<8; i++) {
        char key[128];
        
        sprintf (key, "hotel.easydaq.relay%d.label", i+1);
        char *label = config_get_string_or_default (state->cfg, key, "unknown");

        sprintf (key, "hotel.easydaq.relay%d.group", i+1);
        char *group = config_get_string_or_default (state->cfg, key, "unspecified");

        sprintf (key, "hotel.easydaq.relay%d.state", i+1);
        bool onoff = config_get_boolean_or_default (state->cfg, key, 0);

        // assign our relays
        state->easydaq.relay[i].label = label;
        state->easydaq.relay[i].group = group;

        // queue up a relay request with our desired initial state
        if (!getopt_get_bool (state->gopt, "ignore"))
            state->easydaq.relay[i].state = onoff;
    }
    state->easydaq.self = 0;
    senlcm_easydaq_t_publish (state->lcm, EASYDAQ_CHANNEL, &state->easydaq);
    

    // main loop
    close (STDERR_FILENO);
    while (1) {
        struct timeval timeout;
        timeout.tv_sec = 0;
        timeout.tv_usec = 250000;
        int ret = lcmu_handle_timeout (state->lcm, &timeout);

        if (ret == 0) { /* timeout */
            update_state (state);
            state->easydaq.self = 1;
            state->easydaq.utime = timestamp_now ();
            senlcm_easydaq_t_publish (state->lcm, EASYDAQ_CHANNEL, &state->easydaq);
        }
    }

    // clean up
    if (lockf (state->fd, F_ULOCK, 0) < 0) {
        PERROR ("lockf");
        return -1;
    }
    easydaq_close (state->fd);
    lcm_destroy (state->lcm);
    free (state);
    return 0;
}
