/*
  This file is part of MANTIS OS, Operating System for Nymph.
  See http://mantis.cs.colorado.edu/

  Copyright (C) 2003 University of Colorado, Boulder
*/

/**  
 * @File:     PAR_ADboard_CurrentMonitor.c
 * @Brief:    A file to test interface board w/4ea ads1112 adc's
 * @Author:   jwm
 * @Date:     feb-07
 *
 * Required Files:
 *      sprintff.c      formats floating point, and other 'sprintf' forms to buffer
 *      i2c_support.c   local, segregated subroutines for running the MOS i2c
 *      uart.c          'Aaron Beech's version that actually allows 'RAW_MODE' without mos header
 *      par_cal.h       NEW: location where the actual PAR wand calibrations are loaded from
 *                      NOTE: This file will need to be built prior to making this routine
 *                      It will consist of the .....
 *
 * Modifications
 *
 * HIGHLY ADAPTED VERSION OF PAR_ADboard
 * USED ONLY TO SAMPLE JUST 1 CHANNEL, DIFFERENTIALLY, WITH GAIN
 * TO BUILD-UP A SHUNT CURRENT MONITOR....
 *                  Version-1 27Aug07, jwm
 *                  - Disabled LEVEL sensor stuff not needed for above...may remove
 *                  - nwands=1, nsensors=1
 *                  - Added multiple samples in output message
 *                  - Disabled multiple sensors in sampling....swapped for above...
 *                  - removed 'wandSpecific' and put structs inline
 *                  - double indexed data: vraw, vdc
 *                  - disabled the radio to save power
 *                  Version-2 28Aug07, jwm
 *                  - Enabled 'CONTINUOUS_SAMPLING' via the Global: conversion_mode
 *                    to speed things up, esp. when single-channel sampling mode.
 * 
 * 
 * The reason this was done was to allow us (Ramesh/I) to measure the
 * current draw of the MOS 'Tsoil' application in comparison to the TOS
 * based old Tsoil application because it appears that the MOS code
 * style is drawing too much current, and thereby draining batteries
 * excessively as has been observed in the forest by Lynette.
 */

#include "mos.h"
#include "command_daemon.h"
#include "printf.h"
#include "com.h"
#include "led.h"
#include "dev.h"
#include "avr-i2c.h"
#include "avr-eeprom.h" //devines the DEV_AVR_EEPROM_SEEK ioctl
#include "avr-adc.h"
#include "adc.h"

// Declarations
//int sprintff( char *, const char *, ...);
//static char debugbuf[256];        // local buffer for all


// LED Masks
#define YELLOW 1
#define GREEN 2
#define RED 4

// Calibration Information / Ouput Type Definition
//
enum {
    RAW   =1,   // These must be different bits to allow 'both' output
    MV    =2,
    LINEAR=4,
    POLY  =8
};

// ----------------------------------------------------
// *** EDIT THESE DEFINITIONS TO CONTROL OPERATIONS ***
// ----------------------------------------------------

//#define PRINT_HEADER      //uncomment to have just the values reported

#define CAL_METHOD  LINEAR          // include all format desired. ie... RAW|MV|LINEAR|POLY

#define POLLING_RATE 1          // mSeconds between polling cycles

#define SAMPLING_TIME_FUDGE 8           // wild guess at mS eaten during i2c sampling
                                        // used to adjust the POLLING_RATE sleep time
#define NSAMPLES_PER_MESSAGE 1

#define WANDSENSORS 1       // 3 pars per wand
#define NWANDS 1
#define ADC_RATE 30         // Sampling frequency can be:
                            // 15 (default), 30, 60, 240 hz
                            // see adsRange structure

#define ADC_GAIN 3          // ADC internal gain setting can be 0,1,2,3
                            // see adsPGA structure
                            // 0 = gain1, 0-2.048 (in se mode)
                            // 1 = gain2, 0-1.024
                            // 2 = gain4, 0-0.512
                            // 3 = gain8, 0-0.256vdc

#define PAR_SENSOR_ENABLE               // Comment out to disable the PAR sampling

#define DEBUG                           // Comment out to disable garbage
//#define SHOW_ADS_CALLS
#define SHOW_SEC_TICKS

#define CBUFSZ 128          // Buffer size for output message, note 12pars need at least 120chars


//----------------------------------------
//  ADS1112 characteristics / Calibrations, and
//  PAR/Level Calibration stuff
//----------------------------------------
#include "ads1112_defs.h"


    /* ADC Calibrations and the structure definition
     * The 'specific' file contains the actual values
     * "adsCals[]" that are for the mote to be programmed....
     */
    struct adsCalStuff{
        char    *name;          // Name on the Wand
        uint8_t adsAddr;        // I2C addr...the way we named them it should be same a i2c-x
                                // Note they all better be different!!!
        float   offset;         // measured values:
        float   gain;           // per 'Reference / (Reading-Offset)
    };

    // PAR & Level Calibration Definitions....
    // Specific for individual sensors
    // The offsets for pars will be 0, and only taken up in the adc cal. above
    // The offsets for levels will be specific for the ADC channels
    struct adsChanStuff{
        char    *name;      // Name, actually number, labeled on the par diode
        uint8_t adsNum;     // Serves both as ADC index and I2C addr.
        uint8_t channel;    // See enum
        float offset; float gain; float gain2;
    };


//#include "wandSpecificCals.h"         // This is where they're at!
//----------------------------------------
//   File: "wandSpecificCals.h"
//   ADS1112 Specific Calibrations for 'THIS MOTE'
//
// SPECIAL CASE: FOR CURRENT-SENSING APPLICATION!!!
//
//----------------------------------------
struct adsCalStuff adsCals[4] = {
        {"48-8", ADC0,    0.0000,         1.0000 }, // First 3-PAR ADC
        {"4A-8", ADC1,    0.0004,         1.0003 },
        {"4C-8", ADC2,    0.0020,         1.0001 },
        {"4E-8", ADC3,    0.0017,         1.0000 }
};
// PAR Specific Calibrations
struct adsChanStuff parCoefs[4] = {
        {"iload1",     0, CHAN_DIFF0, -0.052,  192.641, 0},     //1st diff ADC
        {"iload2",     1, CHAN_SE1, 0.0,    1, 0},
        {"iload3",     2, CHAN_DIFF0, 0.0,    1, 0},
        {"iload4",     3, CHAN_DIFF0, 0.0,    1, 0}
 };




// Globals
//---------
static char cbuf[ CBUFSZ ];

    int     calType = CAL_METHOD;
    uint8_t adc_rate, adc_gain;         // Cfg masks for the ads
    uint8_t size1=1;
    uint8_t waitmS;
    uint8_t adsSEChan[3] = { 2,3,1 };   // 'inp1/0' bit patterns 0=2,1=3,2=1
    uint8_t adsDFChan[2] = { 0,1 };     // 'inp1/0' bit patterns, differential channels
    float   calibrated, fval;
    int16_t vraw[NWANDS][NSAMPLES_PER_MESSAGE];
    float   vadc[NWANDS][NSAMPLES_PER_MESSAGE];
    float   bitResolution;              // All inputs will be the same gain setting, and freq.
                                        // so this can be universal also...
    uint16_t pollWaitmS;
    long    iSecond=0;
    uint16_t subSec=0,loopmS;

uint8_t conversion_mode = CONTINUOUS_CONVERSION;
//uint8_t conversion_mode = SINGLE_CONVERSION;


// Definitions
// -----------
// routines here
void parwand_ads1112();
void sample_adcChannels(  uint8_t, uint8_t, struct adsChanStuff *);
void report_results( char *, uint8_t, uint8_t, struct adsChanStuff * );
void report_ADSconfig( char *, uint8_t, struct adsChanStuff *);
void report_ADCcals( char *, uint8_t, struct adsCalStuff *);
void command_adc_sampling(uint8_t, struct adsChanStuff *);
// routines elsewhere
int sprintff( char *, const char *, ...);
void i2c_disable_ack();


//========================================================================
void start (void)
{
//    mos_thread_new (parwand_ads1112, MOS_COMMANDER_STACK_SIZE + 256, PRIORITY_NORMAL);
    mos_thread_new (parwand_ads1112, MOS_COMMANDER_STACK_SIZE + 120, PRIORITY_NORMAL);
}



void parwand_ads1112()
{
    uint8_t  hz;
    uint16_t maxVal;
    uint8_t  nbytes;        // 'sizeof number of bytes to i2c read

    // ----------
    // Setups
    // MOS: turn radio off, and suspend state the lowest possible power.
    // Establish the resolution values based on gains.  These used in vraw calc.

    mos_thread_set_suspend_state(SUSPEND_STATE_SLEEP);  // for lowest power
    com_mode(IFACE_RADIO, IF_OFF);  // to turn off the radio
                                    // down to about 10microA
    nbytes = 3;

    switch( ADC_RATE ) {        // this sets the adc_rate value for it's configuration reg.
        case 30:   hz=30; adc_rate = READ30HZ; maxVal=adsRange[1].maxVal; break;
        case 60:   hz=60; adc_rate = READ60HZ;  maxVal=adsRange[2].maxVal; break;
        case 240: hz=240; adc_rate = READ240HZ; maxVal=adsRange[3].maxVal; break;
        default:
        case 15:   hz=15; adc_rate = READ15HZ; maxVal=adsRange[0].maxVal; break;
    }

    switch( ADC_GAIN ) {        // this sets the adc_gain value
        case 1:   adc_gain = 2; break;
        case 2:   adc_gain = 4; break;
        case 3:   adc_gain = 8; break;
        default:
        case 0:   adc_gain = 1; break;
    }

    // Determine sleep times between polling a set of adcs withing the sampling loop,
    // and between the overall polling rate
    waitmS = (1000 / hz );              // How long to wait between sampling ads

    pollWaitmS = (waitmS+SAMPLING_TIME_FUDGE)*WANDSENSORS;
    if( POLLING_RATE > pollWaitmS ) pollWaitmS = POLLING_RATE - pollWaitmS;
    else    pollWaitmS = 0;
    loopmS = SAMPLING_TIME_FUDGE + waitmS;

    bitResolution = 2.048 / (float) maxVal;
    bitResolution /= (float) adc_gain;

    // I2C Setup
    dev_ioctl(DEV_AVR_I2C, I2C_SET_BRR, 50);
    i2c_disable_ack();                  // Continue working if 'missing' reports


#ifdef DEBUG
    printf("\n\rPAR Sensor Sampling: %d wands, %d pars", NWANDS,WANDSENSORS);
    printf("\n\rOutput Calibration Set= %s %s %s %s",
        calType&RAW? "raw" : "", calType&MV? "mv" : "", calType&LINEAR? "linear" : "",
        calType&POLY? "poly" : "");
    printf("\n\rSampling waitmS = %d mSec (1000/hz + 7)",waitmS);
    printf("\tPolling  sleepmS= %d mSec",pollWaitmS);
    printf("\n\rADC\tSampling Freq = %d, (Cfg Mask = %x), Conversion_Mode=%x",hz,adc_rate,conversion_mode);
    printf("\n\r\tGain = %d, (Cfg Mask = %x),",adc_gain,ADC_GAIN);
    sprintff(cbuf,"\n\r\tMaximum ADC counts = %d, bitResolution = %.7f",maxVal,bitResolution);
    printf("%s",cbuf);
    report_ADCcals( "adcCals:", (uint8_t) NWANDS, &adsCals[0] );    // short changing not showing levels here
    report_ADSconfig( "PAR Config", (NWANDS*WANDSENSORS), &parCoefs[0] );
    mos_thread_sleep( 5000 );       // let them read it!
#endif

    // if continuous, then fire the adc's here....
    if( conversion_mode==CONTINUOUS_CONVERSION )
        command_adc_sampling( NWANDS, &parCoefs[0] );

    // -----------
    // Main Loop with sleep delay
    //
    while( TRUE ) {


//      printf("\n\r");

      // ===================================
      // --- PAR SAMPLING ---
      // See sampling routine....
      //
//    mos_led_display(GREEN);       // green during sampling
      sample_adcChannels( WANDSENSORS, NWANDS, &parCoefs[0] );
      report_results( "i", NSAMPLES_PER_MESSAGE, NWANDS, &parCoefs[0] );

      // --- END of SAMPLING LOOP ---

//    mos_led_display(0);               // turn off LEDs
      if( pollWaitmS )
          mos_thread_sleep( pollWaitmS );   // sleep until next....note some inaccuracy
                                        // because RATE isn't perfectly adjusted by time taken
                                        // in sampling above.....
    }

}



//----------------------------------------
// Send the VRAW + anything else to the serial port
// Requires:    'calType' declared above (raw,mv,etc.)
//              'vraw[]'  declared and loaded above
//              'vadc[]'  declared and loaded above
//
void report_results( char *header, uint8_t samples_per_message, uint8_t nwands, struct adsChanStuff *adsArray) 
{
    uint8_t ix, isample;
    char    *cp;
    struct adsChanStuff *ads=adsArray;



#ifdef SHOW_SEC_TICKS
    // This is funky and only for a guideline to try to have a 'time-tag' in
    // seconds heading to the message......
    if( !pollWaitmS ) {
        // increment decimal mS, and check for wrap
        if( (subSec += loopmS) >= 1000 )    {
            ++iSecond;
            subSec=0;
        }
    }
#endif

      // Done Sampling all Channels, Report results
      // ------------------------------------------
      // Depending upon what's requested....
      //
      if( calType & RAW ) {
        for( ix=0, cp=cbuf; ix<nwands; ++ix ) {
          for( isample=0; isample<samples_per_message; ++isample ) {
            sprintff(cp,"  %04x",vraw[ix][isample]);
            cp+=8;
          }
        }
#ifdef SHOW_SEC_TICKS
        printf("\n\r%l.%03d ",iSecond,subSec);
#else
        printf("\n\r");
#endif
#ifdef PRINT_HEADER
        printf("%sRAW:%s",header,cbuf);
#else
        printf("%s",cbuf);
#endif
      }

      if( calType & MV ) {
        for( ads=adsArray, ix=0, cp=cbuf; ix<nwands; ++ix, ++ads ) {
          for( isample=0; isample<samples_per_message; ++isample ) {
            sprintff(cp,"%7.4f",vadc[ix][isample]);
            cp+=8;
          }
        }
#ifdef SHOW_SEC_TICKS
        printf("\n\r%l.%03d ",iSecond,subSec);
#else
        printf("\n\r");
#endif
#ifdef PRINT_HEADER
        printf("%sVDC:%s",header,cbuf);
#else
        printf("%s",cbuf);
#endif
      }

      // QUESTION...do we want to calibrate on the raw values or 'vdc'?????
      if( calType & LINEAR ) {
        for( ads=adsArray, ix=0, cp=cbuf; ix<nwands; ++ix, ++ads ) {
          for( isample=0; isample<samples_per_message; ++isample ) {
            calibrated = (vadc[ix][isample] *ads->gain ) + ads->offset;
            sprintff(cp,"%7.4f ",calibrated);
            cp+=8;
          }
        }
        
#ifdef SHOW_SEC_TICKS
        printf("\n\r%l.%03d ",iSecond,subSec);
#else
        printf("\n\r");
#endif
#ifdef PRINT_HEADER
        printf("%s1st:%s",header,cbuf);
#else
        printf("%s",cbuf);
#endif
      }

      if( calType & POLY ) {
        for( ads=adsArray, ix=0, cp=cbuf; ix<nwands; ++ix, ++ads ) {
          for( isample=0; isample<samples_per_message; ++isample ) {
            fval = vadc[ix][isample];
            calibrated = (fval * ads->gain ) + ads->offset;
            calibrated = fval * ads->gain + ads->offset;
            calibrated+= fval * fval * ads->gain2;
            sprintff(cp,"%7.4f",calibrated);
            cp+=8;
          }
        }
#ifdef SHOW_SEC_TICKS
        printf("\n\r%l.%03d ",iSecond,subSec);
#else
        printf("\n\r");
#endif
#ifdef PRINT_HEADER
        printf("%s2nd:%s",header,cbuf);
#else
        printf("%s",cbuf);
#endif
      }
}


#ifdef DEBUG
//----------------------------------------
// Displays to Console what the ADS structure Settings Are

void report_ADSconfig( char *header, uint8_t nchannels, struct adsChanStuff *adsArray) 
{
    uint8_t ix;
    struct adsChanStuff *ads=adsArray;

    // Report What the ADS Settings Are
    //
    printf("\n\r%s",header);
    printf("\n\rIx  Name  adcIx i2c  ADC-Ch  Offset  Gain  Gain2");

    for( ix=0; ix<nchannels; ++ix, ++ads ) {
        printf("\n\r%2d  %s   %d  %02x  %x  ",
            ix, ads->name, ads->adsNum, adsCals[ads->adsNum].adsAddr, ads->channel);
        sprintff(cbuf, "%.2f %.2f %.2f",
            ads->offset, ads->gain, ads->gain2);
        printf("%s",cbuf);
    }
}

//----------------------------------------
void report_ADCcals( char *header, uint8_t nadcs, struct adsCalStuff *adsArray) 
{
    uint8_t ix;
    struct adsCalStuff *adccal=adsArray;

    // Report What the ADS Calibrations are
    //
    printf("\n\r%s.....ADC Calibrations:",header);
    printf("\n\rIx  name   i2c   Offset  Gain  ");

    for( ix=0; ix<nadcs; ++ix, ++adccal ) {
        printf("\n\r%2d  %s  %02x ",
            ix,adccal->name, adccal->adsAddr);
        sprintff(cbuf, "%.5f %.5f",
            adccal->offset, adccal->gain);
        printf("%s",cbuf);
    }
}
#endif


//----------------------------------------
void sample_adcChannels( uint8_t sensors_per_wand, uint8_t nwands, struct adsChanStuff *adsArray)
{
    uint8_t  ix, adsChan, isample, npolls, setCfg;
    uint8_t  arrayEntry;

    struct adsChanStuff *ads=adsArray, *ads0=adsArray;

    uint8_t  retval;            // Reply from I2C driver: #bytes
    uint8_t  ADSreply[3];   // Reply from ADS1112, output 16bits + config
    uint8_t i2cAddr;

      // double loop goes through each of the adc's for each of 'n' sample sets

#ifdef SHOW_ADS_CALLS
    printf("\n\r");
#endif

//    for( adsChan=0; adsChan<sensors_per_wand; )   // DO LATER FOR MULTI-CHAN
      for( isample=0; isample<NSAMPLES_PER_MESSAGE; )
      {

        // Command each ADC to read one wand channel
        // This will happen either once for each adc when 'continuous' and
        // that will be done in the 'setups', or else done here when we
        // wand to command each and every single sample
        //
        if( conversion_mode!=CONTINUOUS_CONVERSION ) {
            command_adc_sampling( NWANDS, &parCoefs[0] );
        }

        // Delay for anticpated sampling time period and then
        mos_thread_sleep(waitmS);

        // Read the ADC results
//      for( ads=ads0, ix=0; ix<nwands; ++ix, ads+=sensors_per_wand )
        for( ads=ads0, ix=0; ix<nwands; ++ix, ++ads )
        {
            ADSreply[0] = ADSreply[1] = ADSreply[2] = 0;
//          arrayEntry= isample + ix*sensors_per_wand;
            arrayEntry= isample + ix;
            i2cAddr = adsCals[ads->adsNum].adsAddr; // Set I2C Address

            dev_ioctl(DEV_AVR_I2C, I2C_DEST_ADDR, i2cAddr);
            npolls=0;
            do {
                ++npolls;                   // keep track of how many reads
                retval = dev_read(DEV_AVR_I2C, &ADSreply[0], 3);
            } while (ADSreply[2]&0x80);

            vraw[ix][arrayEntry]= (ADSreply[0]<<8) + ADSreply[1];
            vadc[ix][arrayEntry] = (float)vraw[ix][arrayEntry] * bitResolution;
            vadc[ix][arrayEntry] = (vadc[ix][arrayEntry] - adsCals[ads->adsNum].offset ) * adsCals[ads->adsNum].gain;

#ifdef SHOW_ADS_CALLS
            printf("  Read %d:",isample);
            // report index, replied cfgreg, and #polls required
            printf("%d/%d-%x-%d",ix,arrayEntry,i2cAddr,npolls);
#endif
        }
        ++isample;
      }
    return;
}



//----------------------------------------
void command_adc_sampling(uint8_t nwands, struct adsChanStuff *adsArray)
{
    // common rate/gain/conversion-rate settings
    uint8_t config_mask = adc_rate | ADC_GAIN | conversion_mode;

    uint8_t  ix, adsChan, setCfg;
    struct adsChanStuff *ads=adsArray, *ads0=adsArray;
    uint8_t i2cAddr;
    uint8_t  retval;            // Reply from I2C driver: #bytes

    // Command each ADC to read one wand channel
    // This will happen either once for each adc when 'continuous' 
    // or else done singly depending upon the 'conversion_mode'

//      for( ads=ads0, ix=0; ix<nwands; ++ix, ads+=sensors_per_wand )
        for( ads=ads0, ix=0; ix<nwands; ++ix, ++ads )
        {
            i2cAddr = adsCals[ads->adsNum].adsAddr; // Set I2C Address
            dev_ioctl(DEV_AVR_I2C, I2C_DEST_ADDR, i2cAddr);

            // Set the cfgCommand ADS Channel, 
            setCfg = config_mask | ads->channel;
            retval = dev_write(DEV_AVR_I2C, &setCfg, size1);

#ifdef SHOW_ADS_CALLS
            printf("\nSampling Setup: ");
            sprintff(cbuf,"  %d-%x-%x-%2d-%d",ix,i2cAddr,setCfg,ads->channel,retval);
            printf("%s",cbuf);
#endif
        }

    return;
}

//======================================================================