/****************************************************************************/
/* Copyright 2009-2014 MBARI.                                               */
/* MBARI Proprietary Information. All rights reserved.                      */
/****************************************************************************/
/* Written by Mike Risi for Respirometer project in 2009                    */
/* Modified by Bob Herlien for SensorNode for xFOCE, May 2013               */
/****************************************************************************/

#include "p24Fxxxx.h"
#include <uart.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dig_io.h"
#include "adc.h"
#include "spi.h"
#include "flash_mem.h"
#include "periph.h"
#include "serial.h"
#include "config.h"
#include "buffer.h"
#include "sys_timer.h"
#include "misc.h"
#include "gf.h"
#include "mvdebug.h"

#define NumberOf(arr)		((sizeof(arr) / sizeof(arr[0])))

/***************************** Modbus includes ******************************/
#include "mb.h"
#include "port.h"
/***************************** Modbus includes ******************************/


/***************************** PIC Configuration ****************************/
/* JTAG/Code Protect/Write Protect/Clip-on Emulation mode Watchdog Timer/ICD pins select */
_CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & FWDTEN_OFF & BKBUG_OFF & ICS_PGx1 & COE_OFF) 
/* Enable CLK switch, Disable CLK monitor, OSCO or Fosc/2, 
Primary Oscillator Mode: Disabled, Internal Fast RC oscillator */
_CONFIG2(POSCMOD_XT & FCKSM_CSDCMD & OSCIOFNC_OFF & FNOSC_PRI)
/* Disable CLK switch and CLK monitor, OSCO or Fosc/2, HS oscillator, Primary oscillator */
_CONFIG3(WPEND_WPSTARTMEM & WPCFG_WPCFGEN & WPDIS_WPDIS & WPFP_WPFP2)


/****************************** Modbus variables ****************************/
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS];

static BOOL usGotModbusFrame = FALSE;

static int usBlinkyDebug = TRUE;
static int usBlinkyState = FALSE;

/*************************** Local Data *************************************/
ConfigData configData;
static char buff[256];
static int resetFlag = FALSE;

/*************************** Menu Strings ***********************************/
const char *menuStrings[] =
{ "0 - Save config changes and reset\r\n",
  "1 - Show build date and config\r\n",
  "2 - Set Slave ID\r\n",
  "3 - Set Modbus baudrate\r\n",
  "4 - Set blinky debug state\r\n",
  "5 - Set power reset state\r\n",
  "6 - Set board serial number\r\n",
  "7 - Set default GF channels sampled\r\n",
  "D - Debug Menu\r\n",
  NULL, NULL, NULL
};


void menuTask(int port);
void menuBanner(int port);
void menuConfig(int port);
void menuHelp(int port);
int menuOpt0(int port);
int menuOpt1(int port);
int menuOpt2(int port);
int menuOpt3(int port);
int menuOpt4(int port);
int menuOpt5(int port);
int menuOpt6(int port);
int menuOpt7(int port);
int menuOpt8(int port);
int menuOpt9(int port);


void modBusTask()
{
    /* poll the modbus stack */
    eMBPoll(); 

    /* Here we simply count the number of poll cycles. */
    if (++usRegInputBuf[3] == 0)
	if (++usRegInputBuf[2] == 0)
	    if (++usRegInputBuf[1] == 0)
		++usRegInputBuf[0];
}

int main (void)
{
    unsigned int i = 0, tmrChanged;

    /* init functions */
    periphInit();
    digInit();
    serInit();
    tmrInit();
    spiInit();
    adcInit();
    miscInit();
    gfInit();

    /* read the config struct */
    if ( cfgRead(&configData) != 0 )
    {
        /* set the defaults */
        configData.id = DEFAULT_ID;
        configData.baud = DEFAULT_BAUD;
        configData.blinkyDebug = usBlinkyDebug;
	configData.serialNum = 0L;
	configData.pwrAtReset = 0;
	configData.gfChans = 0;

        menuBanner(CFG_SERPORT);
        serPutString(CFG_SERPORT, "Failed to read config, using default config\r\n");
        
        /* write the config to flash memory */
        cfgWrite(&configData);
    }
    else
    {
        /* put out banner and config */
        menuBanner(CFG_SERPORT);
    }

    digSetVicor(1, configData.pwrAtReset & 1);
    digSetVicor(2, configData.pwrAtReset & 2);
    digSetRelay(1, configData.pwrAtReset & 4);
    digSetRelay(2, configData.pwrAtReset & 8);
    digSetFan(1, configData.pwrAtReset & 0x10);
    digSetFan(2, configData.pwrAtReset & 0x20);
    setGfCtrl(configData.gfChans);

    menuConfig(CFG_SERPORT);
    menuHelp(CFG_SERPORT);

    menuBanner(SER_ALT_CONSOLE);
    menuConfig(SER_ALT_CONSOLE);
    menuHelp(SER_ALT_CONSOLE);

    /* set blinky debug mode based on config */
    usBlinkyDebug = configData.blinkyDebug;

    eMBInit(MB_RTU, (UCHAR)configData.id, 0, configData.baud, MB_PAR_NONE);

    /* Enable the Modbus Protocol Stack. */
    eMBEnable();

    /* turn blinky on if in debug mode */
    if ( usBlinkyDebug )
        digSetBlinky(1);

    for(;;)
    {
        /* service various functions that need periodic polling */
        tmrChanged = tmrTask();
        modBusTask();

        /* start polling application tasks	*/
        /* once modbus messages have started	*/
        if ( usGotModbusFrame )
        {
	    if (tmrChanged)
		gfTask();

            /* if blinky is in debug mode toggle it */
            if ( usBlinkyDebug && tmrChanged)
	    {
		if ( ++i > 25 )
		{
		    i = 0;
		    digSetBlinky(!digGetBlinky());
		}
	    }
        }
        else
        {
	    if (serStatus(CFG_SERPORT))
		menuTask(CFG_SERPORT);

	    else if (serStatus(SER_ALT_CONSOLE))
		menuTask(SER_ALT_CONSOLE);

            /* If the resetFlag is set return from main which will reset
            the processor */

	    if ( resetFlag )
		return 0;

        }
    }
}

/****************************************************************************/
/*                             Modbus callbacks                             */
/****************************************************************************/

eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    int             iRegIndex;

    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    if( ( usAddress >= REG_INPUT_START )
        && ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) )
    {
        iRegIndex = ( int )( usAddress - usRegInputStart );
        while( usNRegs > 0 )
        {
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
            *pucRegBuffer++ =
                ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }


    return eStatus;
}

eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
                 eMBRegisterMode eMode )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          loByte;
    USHORT          hiByte;
    USHORT          reg;

    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    if ((usAddress < REG_HOLDING_START ) || ((usAddress + usNRegs) > REG_HOLDING_MAX+1))
        return MB_ENOREG;

    /* read mode */
    if ( eMode == MB_REG_READ )
    {
	/* read the register(s) */
	if ( ((usAddress + usNRegs) > ADC_CONVERT_START) && 
	     (usAddress <= ADC_CONVERT_END) )
        {
	    adcConvert(usAddress, usNRegs);
	}

	while( usNRegs > 0 )
	{
	    /* set the reg to zero just in case there is no representation */
	    reg = 0;

	    if ((usAddress >= MISC_START) && (usAddress <= MISC_END))
		reg = miscRegRead(usAddress);

	    if ((usAddress >= ADC_CONVERT_START) && (usAddress <= ADC_PARM_END))
		reg = adcReadReg(usAddress);

	    /* assign it to the reg buffer */
	    *pucRegBuffer++ = (unsigned char)( reg >> 8 );
	    *pucRegBuffer++ = (unsigned char)( reg & 0xFF );

	    /* increment register index and address */
	    --usNRegs;
	    ++usAddress;
	}
    }

    /* write mode */
    if ( eMode == MB_REG_WRITE )
    {
        /* grab a register */
	if ( ((usAddress + usNRegs) > ADC_HOLDING_START) && 
	     (usAddress <= ADC_HOLDING_END) )
        {
	    eStatus = adcWriteRegs(pucRegBuffer, usAddress, usNRegs);
	}

	while( usNRegs > 0 )
	{
	    /* get a register from the reg buffer */
	    hiByte = *(pucRegBuffer++);
	    hiByte <<= 8;
	    loByte = *(pucRegBuffer++);

	    reg = ((hiByte & 0xFF00) + (loByte & 0x00FF));

	    if ((usAddress >= MISC_START) && (usAddress <= MISC_END))
		eStatus = miscRegWrite(usAddress, reg);
	    
	    /* increment register index and address */
	    --usNRegs;
	    ++usAddress;
	}

    }

    return eStatus;
}


int isBitSet(int bitNum, UCHAR* buff)
{
    return(buff[bitNum/8] & (1 << (bitNum%0x7)));
}

void bitSet(int bitNum, UCHAR* buff, int val)
{
    /* set of clear the bit depending on the val */
    if ( val ) 
        buff[(bitNum / 8)] |= (1 << (bitNum&0x7));
    else
        buff[(bitNum / 8)] &= ~(1 << (bitNum&0x7));

    return;
}

eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,
               eMBRegisterMode eMode )
{
    int i, coil, val;
    
    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    /* only coils 1 - 6 are represented */
    if ( (usAddress < FIRST_COIL) || 
         (usAddress + usNCoils) > (TOTAL_COILS + FIRST_COIL) )
        return(MB_ENOREG);

    /* perform a coil write(s) */
    if ( eMode == MB_REG_WRITE )
    {
        for (i = 0; i < usNCoils; ++i)
        {
            /* calculate the coil number */
            coil = i + usAddress;

            /* see if the bit is set for that coil */
            val = isBitSet(i, pucRegBuffer);

            /* take the appropriate action for that coil */
            switch ( coil )
            {
                /* instrument port power bits */
                case  1: digSetVicor(1, val); break;
                case  2: digSetVicor(2, val); break;
                case  3: digSetRelay(1, val); break;
                case  4: digSetRelay(2, val); break;
                case  5: digSetFan(1, val); break;
                case  6: digSetFan(2, val); break;
                case  7: usBlinkyDebug = val; break;
                case  8: usBlinkyState = val; break; 
	        case  9: usGotModbusFrame = FALSE;
		    digSetBlinky(1);
		    break;
	        default: return(MB_ENOREG);
            }
        }

        /* set blinky based on debug state */
        if ( !usBlinkyDebug )
            digSetBlinky(usBlinkyState);

        return(MB_ENOERR);
    }


    /* perform a coil read(s) */
    if ( eMode == MB_REG_READ )
    {
        /* clear all of the coil bytes in the buffer */
        memset(pucRegBuffer, 0, ((usNCoils / 8) + 1));

        for (i = 0; i < usNCoils; ++i)
        {
            /* calculate the coil number */
            coil = i + usAddress;
            
            switch ( coil )
            {
	        case  1: bitSet(i, pucRegBuffer, VICOR_01); break;
	        case  2: bitSet(i, pucRegBuffer, VICOR_02); break;
	        case  3: bitSet(i, pucRegBuffer, RELAY_01); break;
	        case  4: bitSet(i, pucRegBuffer, RELAY_02); break;
	        case  5: bitSet(i, pucRegBuffer, FAN_01); break;
	        case  6: bitSet(i, pucRegBuffer, FAN_02); break;
                case  7: bitSet(i, pucRegBuffer, usBlinkyDebug); break;
                case  8: bitSet(i, pucRegBuffer, usBlinkyState); break;
	        case  9: break;
                default: return(MB_ENOREG);
            }
        }

        return(MB_ENOERR);
    }

    return(MB_EIO);
}

eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    /* set the modbus frame received flag to true */
    usGotModbusFrame = TRUE;

    return MB_ENOREG;
}

/****************************************************************************/
/*                              Menu functions                              */
/****************************************************************************/
#define MAX_CHARS   32
int getLine(int port, char* line)
{
    static int char_count = 0;
    static char local_line[MAX_CHARS] = "";
    char terminator = '\r';
    int got_line;
    BYTE b;
    char c;
    int i;
    
    got_line = FALSE;

    if ( serGetByte(port, &b) )
    {
        /* echo the characters */
        serPutByte(port, b);
        
        /* assign byte to char */
        c = (char)b;
            
        if (c == '\r')
            serPutByte(port, (BYTE)'\n');

        if ( c != terminator )
        {
            if ( (c != '\b') && (char_count < MAX_CHARS) )
                local_line[char_count++] = c;
            else if ( char_count > 0 )
                --char_count;

            line[0] = '\0';
        } 
        else
        {
            got_line = TRUE;
            
            /* copy local_line buffer to line */
            for (i = 0; i <= char_count; i++)
                line[i] = local_line[i];
            
            /* terminate the line */
            line[char_count] = '\0';
            
            /* clear the char counter for the new line */
            char_count = 0;
        }
    }

    return got_line;
}

void menuTask(int port)
{
    BYTE b;

    if (isSerPortValid(port) && serGetByte(port, &b))
    {
        switch ( b )
        {
            case '0': menuOpt0(port); break;
            case '1': menuOpt1(port); break;
	    case '2': menuOpt2(port); return;
            case '3': menuOpt3(port); break;
            case '4': menuOpt4(port); break;
            case '5': menuOpt5(port); break;
            case '6': menuOpt6(port); break;
            case '7': menuOpt7(port); break;
            case '8': menuOpt8(port); break;
            case '9': menuOpt9(port); break;
	    case 'd':
	    case 'D': debugMenu(port);
		serRxFlush(CFG_SERPORT);
		serRxFlush(SER_ALT_CONSOLE);
		break; 
            case '?':
	    case '\r': menuHelp(port); break;
            default: serPutString(port, "Invalid command, ");
                     serPutString(port, "try '?' for help.\r\n");
        }
    }
}

void menuBanner(int port)
{
    sprintf(buff, "\r\nMedium Voltage Converter SW Rev %u.%02u %s, %s\r\n",
	    SW_REV_HIGH, SW_REV_LOW, __DATE__, __TIME__);
    serPutString(port, buff);
    serPutString(port, "Type ? for menu\r\n\r\n");
}

void menuConfig(int port)
{
    sprintf(buff, "SLAVE ID:     %u\r\n", configData.id);
    serPutString(port, buff);
    sprintf(buff, "BAUD RATE:    %lu\r\n", configData.baud);
    serPutString(port, buff);

    serPutStringBool(port, "BLINKY DEBUG: ", configData.blinkyDebug);

    sprintf(buff, "RESET POWER:  0x%02x ( ", configData.pwrAtReset);
    if (configData.pwrAtReset & 1)
	strcat(buff, "VICOR1 ");
    if (configData.pwrAtReset & 2)
	strcat(buff, "VICOR2 ");
    if (configData.pwrAtReset & 4)
	strcat(buff, "RELAY1 ");
    if (configData.pwrAtReset & 8)
	strcat(buff, "RELAY2 ");
    if (configData.pwrAtReset & 0x10)
	strcat(buff, "FAN1 ");
    if (configData.pwrAtReset & 0x20)
	strcat(buff, "FAN2 ");
    if ((configData.pwrAtReset & 0x3f) == 0)
	strcat(buff, "None ");
    strcat(buff, ")\r\n");
    serPutString(port, buff);

    sprintf(buff, "GF CTRL:      0x%02x\r\n", configData.gfChans);
    serPutString(port, buff);

    sprintf(buff, "SERIAL NUM:   %lu\r\n", configData.serialNum);
    serPutString(port, buff);
}

void menuHelp(int port)
{
    int	i;

    serPutString(port, "\r\n");
    for (i = 0; i < NumberOf(menuStrings); i++)
	if (menuStrings[i] != NULL)
	    serPutString(port, menuStrings[i]);
}


int menuOpt0(int port)
{
    /* erase the old config */
    memErase();
    cfgWrite(&configData);
    resetFlag = TRUE;
    return 0;
}

int menuOpt1(int port)
{
    menuBanner(port);
    menuConfig(port);
    return 0;
}

int menuOpt2(int port)
{
    unsigned int id = 0;

    serPutString(port, "Enter SLAVE ID: ");
        
    /* wait for user input */
    while ( !getLine(port, buff) )
    {
	modBusTask();

	/* if you get a modbus message, bail out of menuOpt */
	if ( usGotModbusFrame )
	    return 0;
    }

    id = (unsigned int)atoi(buff);

    if ( (id < 1) || (id > 32) )
    {
	sprintf(buff, "Invalid ID: %u, valid range is [1,32]\r\n", id);
	serPutString(port, buff);
	return -1;
    }

    configData.id = id;
        
    return 0;
}

int menuOpt3(int port)
{
    unsigned long baud;

    serPutString(port, "Enter BAUD RATE: ");
        
    /* wait for user input */
    while ( !getLine(port, buff) )
    {
	modBusTask();
        
	/* if you get a modbus message, bail out of menuOpt */
	if ( usGotModbusFrame )
	    return 0;
    }

    baud = (unsigned long)atol(buff);

    switch ( baud )
    {
      case 9600: break;
      case 19200: break;
      case 38400: break;
      case 57600: break;
      case 115200: break;
      default:
	  sprintf(buff, "Invalid BAUD RATE: %lu\r\n", baud);
	  serPutString(port, buff);
	  serPutString(port, "Valid rates are\r\n");
	  serPutString(port, "9600 19200 38400 57600 or 115200\r\n");
	  return -1;
    }

    configData.baud = baud;

    return 0;
}

int menuOpt4(int port)
{
    int state = 0;

    serPutString(port, "Enter BLINKY DEBUG [0 = off | 1 = on]: ");
        
    /* wait for user input */
    while ( !getLine(port, buff) )
    {
	modBusTask();

	/* if you get a modbus message, bail out of menuOpt */
	if ( usGotModbusFrame )
	    return 0;
    }
        
    state = atoi(buff);

    if ( (state != 0) && (state != 1) )
    {
	sprintf(buff, "Invalid state: %d, [0 = off | 1 = on]\r\n", state);
	serPutString(port, buff);
	return -1;
    }

    configData.blinkyDebug = state;

    return 0;
}

int menuOpt5(int port)
{
   int state = 0;

   serPutString(port, "Enter power state at RESET, hex\r\n"
		"[Bits 0,1 = Vicor 1,2 | Bits 2,3 = Relay 1,2 | Bits 4,5 = Fan 1,2]\r\n");
        
   /* wait for user input */
   while ( !getLine(port, buff) )
   {
       modBusTask();

       /* if you get a modbus message, bail out of menuOpt */
       if ( usGotModbusFrame )
	   return 0;
   }
        
   state = strtol(buff, NULL, 16);

   if (state & ~0x3f)
   {
       sprintf(buff, "Invalid state: 0x%02x\r\n", state);
       serPutString(port, buff);
       return -1;
   }

   configData.pwrAtReset = state;

   return 0;
}

int menuOpt6(int port)
{
    serPutString(port, "Enter Board Serial Number: ");
        
    /* wait for user input */
    while ( !getLine(port, buff) )
    {
	modBusTask();
        
	/* if you get a modbus message, bail out of menuOpt */
	if ( usGotModbusFrame )
	    return 0;
    }

    configData.serialNum = (unsigned int)atoi(buff);

    return 0;
}

int menuOpt7(int port)
{
   int ctrl = 0;

   serPutString(port, "Enter Ground Fault channels on at reset [Bits 0-5 valid]\r\n");
        
   /* wait for user input */
   while ( !getLine(port, buff) )
   {
       modBusTask();

       /* if you get a modbus message, bail out of menuOpt */
       if ( usGotModbusFrame )
	   return 0;
   }
        
   ctrl = strtol(buff, NULL, 16);

   configData.gfChans = ctrl & 0x3f;

   return 0;
}

int menuOpt8(int port)
{
    return(-1);
}

int menuOpt9(int port)
{
    return(-1);
}


