/****************************************************************************/
/* Copyright (c) 2015 MBARI                                                 */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Summary  : Command interface to the CANBus power buoy controllers.       */
/* Filename : pbcom.c                                                       */
/* Author   : Henthorn                                                      */
/* Project  : Power Buoy                                                    */
/* Version  : 1.0                                                           */
/* Created  : 04/10/2015                                                    */
/* Modified :                                                               */
/* Archived :                                                               */
/****************************************************************************/
/* Modification History:                                                    */
/****************************************************************************/
/*                                                                          */
/* This application is the entry point for many (if not all) the CANBus     */
/* controllers. The design is similar to 'busybox' where argv[0] determines */
/* what command is executed. Soft-links to this program are used for each   */
/* supported command. To illustrate:                                        */
/*  $                                                                       */
/*  $ ls -l                                                                 */
/*  -rwxr-xr-x 1 henthorn dialout 12107 Apr  8 17:26 pbcom                  */
/*  lrwxrwxrwx 1 henthorn dialout    11 Apr  9 09:52 setmode -> pbcom       */
/*  $                                                                       */
/*  $                                                                       */
/*  $ setmode                # runs pbcom with argv[0] == "setmode"         */
/*                                                                          */
/* When a soft-link does not exist, the '-c' option can be used to execute  */
/* the supported command:                                                   */
/*  $                                                                       */
/*  $ pbcom -c setmode       # runs pbcom and executes setmode function     */
/*                                                                          */
/****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/can.h>
#include <linux/can/raw.h>

#define  OK         0
#define  BAD_SOCK  -1      // Socket comms failed
#define  BAD_OPTS  -2      // Inconsistent option values
#define  CAN0      "can0"
#define  BUOY_CON  0x00    // ID of buoy controller

/* Standard stuff passed to each command function */
typedef struct pb_stuff
{
   // Universal options
   int   can_fd;         // CANBus socket descriptor
   char  verbose;        // Verbose option
   char  interactive;    // Interactive option (prompt for params)
   char  help;           // Command help
   char* command;        // Command name

   // Set mode factors
   float scale;
   float retract;

} pb_stuff;

/* Supported command functions                                              */
/* Set mode to 00, and to set associated scale and retract factors. Returns */
/* 0 when successful.                                                       */
int setmode(pb_stuff *env, int argc, char** argv);


void usage(const char* cmd)
{
   printf("usage:\n");
   printf(" %s [-vhi] [-c CMD]\n", cmd);
   printf("   -v       Verbose     - chatty\n");
   printf("   -h,?     Help        - print this message\n");
//   printf("   -i       Interactive - prompting for options\n");
   printf("   -c CMD   Command specification\n");
   printf("            CMD = [setmode]\n");
}

int main(int argc, char **argv)
{
   pb_stuff pbs;
   pbs.can_fd = pbs.verbose = pbs.interactive = pbs.help = 0;
   pbs.scale = -1.0;
   pbs.retract = 3.0;
   pbs.command = strdup(argv[0]);  // -c option overrides command line 

   // Lets get the universal options first
   int opt;
   while ( (opt = getopt(argc, argv, "vihc:s:r:")) != -1)
   {
      switch(opt)
      {
         case 'v':
            pbs.verbose = 1;
            break;

         case 'h':
            pbs.help = 1;
            usage(pbs.command);
            return OK;
            break;

         // TODO: interactive mode prompts user for parameters
         case 'i':
            pbs.interactive = 1;
            break;

         // setmode scale factor
         case 's':
            pbs.scale = strtof(optarg, NULL);
            break;

         // setmode retract factor
         case 'r':
            pbs.retract = strtof(optarg, NULL);
            break;

         // user specified the command using the command option
         case 'c':
            pbs.command = strdup(optarg); // -c option overrides command line
            break;

         default:
            usage(argv[0]);
            return -1;
      }
   }

   // Execute the command function
   //
   if (strstr(pbs.command, "setmode"))
   {
      return setmode(&pbs, argc, argv);
   }
   else
   {
      printf("pbcom: Unrecognized command: %s\n", pbs.command);
      usage(argv[0]);
      return -1;
   }
}

// Return a socket fd setup for CANBus comms.
// Returns BAD_SOCK on failure.
//
int open_cansock(const char *port)
{
   struct ifreq ifr;
   struct sockaddr_can addr;
   int cansock;

   // Get a CAN_RAW socket
   //
   if ( (cansock = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
   {
      perror("pbcom: Unable to acquire socket");
      return BAD_SOCK;
   }

   // Get appropriate ioctl() for socket
   //
   addr.can_family = AF_CAN;
   strcpy(ifr.ifr_name, port);
   if (ioctl(cansock, SIOCGIFINDEX, &ifr) < 0)
   {
      perror("pbcom: socket ioctl() failed");
      return BAD_SOCK;
   }
   addr.can_ifindex = ifr.ifr_ifindex;

   // Set 
   fcntl(cansock, F_SETFL, O_NONBLOCK);
   if (bind(cansock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
   {
      return BAD_SOCK;
   }

   return cansock;
}

int send_frame(int cansock, struct can_frame *frame)
{
   int retval = write(cansock, frame, sizeof(struct can_frame));
   if (retval != sizeof(struct can_frame))
   {
      return BAD_SOCK;
   }
   else
   {
      return OK;
   }
}


// Set mode to 00, and to set associated scale and retract factors. Returns
// 0 when successful. Get command-specific arguments here.
// 
int setmode(pb_stuff *pbs, int argc, char** argv)
{
   if (pbs->verbose) printf("setmode...\n");

   if (pbs->help)
   {
      printf("setmode: Help here...\n");
      // report command-specific usage here
      //
      return OK;
   }

   if (pbs->verbose) printf("setmode: Using scale=%.3f, retract=%.3f\n", pbs->scale, pbs->retract);

   //Perform Range checking
   //
   if (pbs->scale < 0. || pbs->scale > 2.55)
   {
      // report command-specific usage here
      //
      printf("setmode: Scale factor must be specified >= 0.0 and <= 2.55\n");
      return BAD_OPTS;
   }

   if (pbs->retract < 0.)
   {
      // report command-specific usage here
      //
      printf("setmode: Retract factor must be specified >= 0.0\n");
      return BAD_OPTS;
   }

   // Open a socket to the CANBus interface CAN0
   //
   if (pbs->verbose) printf("setmode: Opening channel to canbus...\n");
   int cansock = open_cansock(CAN0);
   if (BAD_SOCK == cansock)
   {
      return BAD_SOCK;
   }

   // Format the command in the CANBus frame
   //
   struct can_frame frame;
   frame.can_id = 0x0000l;
   frame.can_dlc = 3;
   frame.data[0] = BUOY_CON;
   frame.data[1] = (char)pbs->scale * 100;
   frame.data[2] = 70;

   // Send the command (fire-and-forget)
   //
   if (pbs->verbose) printf("setmode: Sending command...\n");
   if (OK != send_frame(cansock, &frame))
   {
      perror("setmode: Comms failure");
      return BAD_SOCK;
   }

   return OK;
}