/**
  ******************************************************************************
  * @file    OSExtBatt.c
  * @author  C. Otten
  * @version V0.0.0
  * @date    04/27/2011
  * @brief   Ocean Server External Battery
  ******************************************************************************
  *
  * COPYRIGHT 2011 Liquid Robotics, Inc.
  **/

#include "includes.h"
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/power.h>
#include "swversion.h"


#include "base.h"
#include "uart.h"
#include "pib.h"
#include "timer.h"
#include "lrstat.h"
#include "spidebug.h"
#include "wdtrig.h"
#include "pib_apps.h"
#include "User_app_init.h"
#include "pib_testpts.h"
#include "fltPyldComms.h"
#include "UserData.h"
#include "FileTransfer.h"
#include "OSExtBatt.h"
#include "TADebug.h"

#define OSEB_SLAVECMD_BIN           0x01        // Collect binary data.
#define OSEB_SLAVECMD_HEX           0x02        // Collect hex data.
#define OSEB_SLAVECMD_ASCII         0x03        // Collect ASCII data.
#define OSEB_SLAVECMD_X91           0x04        // Collect data for X91-type packet.

// For success, the lower nibble will indicate the Port Number index.
//  May not be a zero value since it is being passed as a message (pointer).
#define OSEB_SLAVECMD_SUCCESS       0x80        // Task completed successfully (generic).
#define OSEB_SLAVECMD_FAIL          0xFE        // Data collection had an issue.


#define OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE  0x0000
#define OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE  0x0001

#define OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL  0x0002
#define OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL  0x0001

//#define SUCCESSCMD  (SUCCESS + 10)
//#define FAILCMD     (FAIL    + 10)

#define MAX_SAMPLE_INTERVAL          60         // In counts of base sample time. (Currently set to
                                                //  one minute each.)

#define OSEB_DCAPT_TEMPERATURE      (1 << 0)
#define OSEB_DCAPT_VOLTAGE          (1 << 1)
#define OSEB_DCAPT_CURRENT          (1 << 2)
#define OSEB_DCAPT_PWRREMAIN        (1 << 3)
#define OSEB_DCAPT_STATUS_OTHER     (1 << 4)
#define OSEB_DCAPT_STATUS_BP        (1 << 5)
#define OSEB_DCAPT_UNUSED           0xC0

typedef struct
{
  uint8_t                     hour;
  uint8_t                     minute;
  uint8_t                     second;
  uint8_t                     month;
  uint8_t                     day;
  uint8_t                     year;
  int16_t                     payload1_current;
  int16_t                     payload2_current;
  int16_t                     umbilical_current;
  int16_t                     internal_current;
  int16_t                     payload3_current;
  int16_t                     payload4_current;
  int16_t                     powerextension_current;
  int16_t                     lightbar_current;
  uint32_t                    power_remaining;
  uint16_t                    battery_current;
  uint16_t                    payload_voltage;
  uint16_t                    float_voltage;
  uint16_t                    payload_current;
  uint16_t                    float_current;
  uint16_t                    modem_current;
  uint16_t                    solar1_power;
  uint16_t                    solar2_power;
  uint16_t                    aux_power;
  uint16_t                    reserved_power;
  uint8_t                     pm_status;
  uint8_t                     reserved[13];
} PacketHeader_t;

typedef struct
{
  uint8_t                     status;           // === SmartBatt cmd: 0x16 (bit flags)
  uint8_t                     temp;             // === SmartBatt cmd: 0x08 (in K)
  uint16_t                    power_remaining;  // === SmartBatt cmd: 0x0F (in mAh)
  uint16_t                    current;          // === SmartBatt cmd: 0x0A (in mA)
  uint16_t                    voltage;          // === SmartBatt cmd: 0x09 (in mV)
} Battery_t;

typedef struct
{
  uint8_t                     bmask;            // When set, indicates the batteries that are
                                                //  expected in circuit.
  uint8_t                     bpresent;         // When set, each bit indicates that the corresponding
                                                //  battery is detected. Bits 0 through 7 match
                                                //  batteries 1 through 8.
  uint8_t                     dcaptured[MAX_OSCTLRBATTERIES];
                                                // When set, each bit indicates that a particular
                                                //  piece of data has been obtained. The mapping
                                                //  depends on the data required. For this case it is:
                                                //   Bit SBReg Type
                                                //   --- ----- ----
                                                //    0 - 08 - Temperature
                                                //    1 - 09 - Voltage
                                                //    2 - 0A - Current
                                                //    3 - 0F - Power Remaining
                                                //    4 - 16 - Status (from battery)
                                                //    5 - 96 - Status (battery present)
                                                //    6 - xx - Unused
                                                //    7 - xx - Unused
                                                //  It has a corresponding mask, but that is hard
                                                //    coded, since all batteries have the same data.
//?  Battery_t                   battery[8];       // Room for the maximum number of batteries one
                                                //  Ocean Server Controller can access.
} BattControl_t;

typedef struct _OSEXTBATT_BUFFER_
{
  uint16_t                    baseAddr;           // EEPROM base address for task specific data.
  uint8_t                     pon_mask;           // Mask containing bit position for PON control.
  uint8_t                     pok_mask;           // Mask containing bit position for POK input.
  char                        bufferTx[256];
  char                        bufferRx[1024];
  BOOL                        sampling_active;    // When TRUE, sample data. When FALSE, turn off power
                                                  //  to thermistor chain.
  uint8_t                     sample_limit;       // Number of minutes between samples to be reported.
  int8_t                      sample_count;       // Counter for the above.
  char                        *oseb_tmp_str;      // Common use string dedicated to task located in
                                                  //  memory partition. Using this is faster than
                                                  //  allocating it for each use, but does tie up that
                                                  //  memory partition. At least it is thread safe.
  BattControl_t               batdat;             // Intermediate battery data collected here by the
                                                  //  task.
//  OSEXTBATT_DATA_REPORT       reportData;         // Report structure containing, among others, a
//                                                  //  complete collection set to be sent to shore.
  uint16_t                    databufsize;        // Amount of space used in the buffer below.
  char                        databuf[2048];      // A temporary buffer used when collecting data.
                                                  // (Can't use 'fileOut' for this purpose, since it
                                                  //  handles command I/O with the Float in parallel.)
                                                  // Careful that it fits within the generic size of
                                                  //  'taskBuffers'.
} OSEXTBATT_BUFFER, *POSEXTBATT_BUFFER;

typedef struct _OSEXTBATT_TASK_
{
  uint8_t                     taskID;
  uint8_t                     portNum;
  POSEXTBATT_BUFFER           pBuf;
} OSEXTBATT_TASK, *POSEXTBATT_TASK;

typedef struct _OSEXTBATT_DATA_LOG_FORMAT_
{                                               // Equivalent to Power Controller Status packet (DB3.5).
                                                // Not all fields will be used and are set to zero.
  union
  {
    uint8_t                   unused[60];       // Covers all unused fields.
    PacketHeader_t            hdr;              // In case some of those fields get used (later).
  };
  uint8_t                     batt_count;       // Number of batteries in system.
  Battery_t                   battery[NUM_OSEXTBATTERIES];  // Individual battery status.
} OSEXTBATT_DATA_LOG_FORMAT, *POSEXTBATT_DATA_LOG_FORMAT;

typedef struct _OSEXTBATT_DATA_REPORT_BLOCK_
{                                               // Battery pack sample, including overhead info.
  TYPE_90_91_DATA_HEADER      headerData;       // Lat., long., time.
  OSEXTBATT_DATA_LOG_FORMAT   data_sample;      // Battery pack data.
} OSEXTBATT_DATA_REPORT_BLOCK, *POSEXTBATT_DATA_REPORT_BLOCK;

typedef struct _OSEXTBATT_DATA_REPORT_
{                                               // Complete type 0x91 packet.
  TYPE_91_REPORT_HEADER       headerReport;
  OSEXTBATT_DATA_REPORT_BLOCK dataBlock;        // Single report per packet.
} OSEXTBATT_DATA_REPORT, *POSEXTBATT_DATA_REPORT;

typedef struct _OSEXTBATT_DATA_REPORT_XMIT_
{                                               // Binary data report packet.
  PIB_IRIDIUM_DATA_XMIT         comm;
  OSEXTBATT_DATA_REPORT         x91reportData;  // A single type 0x91 packet containing one set
                                                //  of data points.
} OSEXTBATT_DATA_REPORT_XMIT, *POSEXTBATT_DATA_REPORT_XMIT;

BOOL                    OS_ExtBatteries_PostTurnOn(POSEXTBATT_TASK pTaskInfo);
void                    OS_ExtBatteries_ExtractData(POSEXTBATT_TASK pTaskInfo);
void                    OS_ExtBatteries_DataSampleSubset(POSEXTBATT_TASK pTaskInfo);
void                    OS_ExtBatteries_Distill_Battery_Status(POSEXTBATT_TASK pTaskInfo, char *pline);
void                    OS_ExtBatteries_Apply_Battery_Status(POSEXTBATT_TASK pTaskInfo,
                                        uint8_t controller, uint8_t battery, uint8_t battreg,
                                        uint16_t battdata);
uint8_t                 OS_ExtBatteries_DataReportXmitType91(POSEXTBATT_TASK pTaskInfo);
uint8_t                 OS_ExtBatteries_ParseFile(POSEXTBATT_TASK pTaskInfo, uint32_t fileID);
uint8_t                 OS_ExtBatteries_QueueFile(POSEXTBATT_TASK pTaskInfo, uint32_t fileID);
uint8_t                 OS_ExtBatteries_GetString(char *pstring, uint8_t port_num, char term_char,
                                        uint8_t max_char, uint16_t time_out_ticks);
BOOL                    OS_ExtBatteries_ExtractHex(POSEXTBATT_TASK pTaskInfo);
BOOL                    OS_ExtBatteries_ExtractAscii(POSEXTBATT_TASK pTaskInfo);

static char*            ThreadSafeStrTok(char** context, char* sep);
static OS_EVENT         *slavetask_begin = NULL;
static OS_EVENT         *slavetask_done  = NULL;
uint8_t                 os_busy;
static OSEXTBATT_DATA_REPORT  reportData __attribute__((section(".xdata")));
                                                // Report structure containing, among others, a
                                                //  complete battery set sample to be sent to shore.


extern char             fileIn[NUM_TASK_FILES][MAX_FILE_SIZE_IN] __attribute__((section(".xdata")));
extern char             fileOut[NUM_TASK_FILES][MAX_FILE_SIZE_OUT] __attribute__((section(".xdata")));
extern uint16_t         fileOutSize[NUM_TASK_FILES];        // Want this to be initialized to zero:  __attribute__((section(".xdata")));
extern uint16_t         fileInSize[NUM_TASK_FILES];         // Want this to be initialized to zero:  __attribute__((section(".xdata")));
extern BOOL             fileAvailable[NUM_TASK_FILES];      // Want this to be initialized to zero:  __attribute__((section(".xdata")));
extern uint32_t         fileAvailableID[NUM_TASK_FILES];    // Want this to be initialized to zero:  __attribute__((section(".xdata")));
extern BOOL             transmitPending[NUM_TASK_FILES];    // Want this to be initialized to zero:  __attribute__((section(".xdata")));

extern OS_EVENT*        pMutexFloatQueue;
extern FLOAT_TX_BUFFER  txFloatData[MAX_FLOAT_BUFFERS] __attribute__((section(".xdata")));
extern uint8_t          headFloatTx;
extern uint8_t          tailFloatTx;

extern uint8_t          DotToPad(char* pad, char* dot);
extern uint16_t         mainTaskAddress;

extern int32_t          latitude;
extern int32_t          longitude;



// ................................................................................................
// Setup the I/O pin for Ocean Server Controller On/Off. Start up in the off position.
#define SET_OCEANSERVERON         {  PORTF &= ~_BV(PF2); }  // Low active.
#define SET_OCEANSERVEROFF        {  PORTF |=  _BV(PF2); }
#define IS_OCEANSERVERON          (!(PORTF  &  _BV(PF2)) )
#define IS_OCEANSERVEROFF         (  PORTF  &  _BV(PF2)  )
#define INIT_OCEANSERVERONOFF(void) \
  SET_OCEANSERVEROFF;               \
  DDRF  |=  _BV(PF2);

// ................................................................................................
// Setup the I/O pin for Charge Controller On/Off. Start up in the on position.
#define SET_CHARGECONTROLLEROFF   {  PORTH &= ~_BV(PH6); }  // High active.
#define SET_CHARGECONTROLLERON    {  PORTH |=  _BV(PH6); }
#define IS_CHARGECONTROLLERON     (!(PORTH  &  _BV(PH6)) )
#define IS_CHARGECONTROLLEROFF    (  PORTH  &  _BV(PH6)  )
#define INIT_CHARGECONTROLLERONOFF(void)  \
  SET_CHARGECONTROLLERON;                 \
  DDRH  |=  _BV(PH6);



// ................................................................................................
// Initialize the Ocean Server External Batteries master task.
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
// Returns:
//    uint8_t             - Effectivekly a boolean indicating initialization result.
//                          TRUE when initialization is completed successfully.
//                          FALSE if there was some kind of an issue.
//
uint8_t OS_ExtBatteries_Init_Master(POSEXTBATT_TASK pTaskInfo)
{
  uint8_t     retval = SUCCESS;
  uint16_t    temp;
  uint8_t     err;
  POSEXTBATT_BUFFER pBuf = pTaskInfo->pBuf;

  if(pTaskInfo == NULL || pBuf == NULL)
    return FALSE;                               // No pointers to task related data memory. Fail.

  memset(pBuf, 0, TASK_BUF_SIZE);               // Clear data before use (a Jim thing).

  if (pTaskInfo->portNum > MAX_PERSONALITY_MODULES)
    return FALSE;                               // Can't be outside the available personalities. Fail.

  // Set up memory partition for use exclusively by this task, typically used as temporary
  //  string storage. Do not use across routines without making sure there is no interference.
  // Will never get de-allocated unless task is halted.
  OSSemPend(mem_sem_ptr[INDEX_OSEB_TMP_STR], 0, &err);
  pTaskInfo->pBuf->oseb_tmp_str = OSMemGet(mem_part_ptr[INDEX_OSEB_TMP_STR], &err);

  pBuf->baseAddr = pgm_read_word(&(eeprom_pm_start[pTaskInfo->portNum])); // Determine where task related EEPROM is located.
                                                                          // PCOM power-on controls.
  pBuf->pon_mask = pgm_read_word(&(pm_pon[pTaskInfo->portNum]));          // The power on bit mask.
  pBuf->pok_mask = pgm_read_word(&(pm_pok[pTaskInfo->portNum]));          // The power ok bit mask.

  // Initialize port for communications to Ocean Server Battery controller.
  // This main task controls one directly, and controls the other via a supporting task.
  OSuartInitPort(pTaskInfo->portNum, 19200, UART_MODE_EIGHT_DATA_BITS, UART_MODE_ONE_STOP_BIT, UART_MODE_NO_PARITY);

  // Read sampling interval and collection size in use from EEPROM. Check for appropriate range.
  #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 1)
  EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                 (void*)&temp);
  temp &= 0xFF;
  #else
  #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 2)
  EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                 (void*)&temp);
  #else
  #warning "Not a valid data size for allowed operation."
  #endif
  #endif
  pBuf->sampling_active = temp ? TRUE : FALSE; // Set to defined TRUE or FALSE values.

  #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 1)
  EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                 (void*)&temp);
  temp &= 0xFF;
  #else
  #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 2)
  EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                 (void*)&temp);
  #else
  #warning "Not a valid data size for allowed operation."
  #endif
  #endif
  pBuf->sample_limit = temp;
  if (pBuf->sample_limit > MAX_SAMPLE_INTERVAL)
    pBuf->sample_limit = MAX_SAMPLE_INTERVAL;


  // This is not needed for the PCom, since we're not supplying power.
/*
  if (pTaskInfo->pBuf->sampling_active)         // Preset the power control bit.
    PORTL |= pBuf->pon_mask;                    // Fixed location. Turn power on.
  else
    PORTL &= ~pBuf->pon_mask;                   // Fixed location. Turn power off.
  DDRL |= pBuf->pon_mask;                       // Need an output here. Power On bit.
*/

  INIT_OCEANSERVERONOFF();                      // Fixed I/O bits for these on/off controls.
  INIT_CHARGECONTROLLERONOFF();

  OSTimeDlyHMSM(0,0,2,0);                       // Delay so that any start up string can be digested by
                                                //  the debug task.

  return retval;
} // OS_ExtBatteries_Init_Master

// ................................................................................................
// Initialize the Ocean Server External Batteries slave task.
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
// Returns:
//    uint8_t             - Effectivekly a boolean indicating initialization result.
//                          TRUE when initialization is completed successfully.
//                          FALSE if there was some kind of an issue.
//
uint8_t OS_ExtBatteries_Init_Slave(POSEXTBATT_TASK pTaskInfo)
{
  uint8_t     retval = SUCCESS;
  uint8_t     err;
  POSEXTBATT_BUFFER pBuf = pTaskInfo->pBuf;

  if(pTaskInfo == NULL || pBuf == NULL)
    return FALSE;                               // No pointers to task related data memory. Fail.

  memset(pBuf, 0, TASK_BUF_SIZE);               // Clear data before use (a Jim thing).

  if (pTaskInfo->portNum > MAX_PERSONALITY_MODULES)
    return FALSE;                               // Can't be outside the available personalities. Fail.

  // Set up memory partition for use exclusively by this task, typically used as temporary
  //  string storage. Do not use across routines without making sure there is no interference.
  // Will never get de-allocated unless task is halted.
  OSSemPend(mem_sem_ptr[INDEX_OSEB_TMP_STR], 0, &err);
  pTaskInfo->pBuf->oseb_tmp_str = OSMemGet(mem_part_ptr[INDEX_OSEB_TMP_STR], &err);

  pBuf->baseAddr = pgm_read_word(&(eeprom_pm_start[pTaskInfo->portNum])); // Determine where task related EEPROM is located.
                                                                          // PCOM power-on controls.
  pBuf->pon_mask = pgm_read_word(&(pm_pon[pTaskInfo->portNum]));          // The power on bit mask.
  pBuf->pok_mask = pgm_read_word(&(pm_pok[pTaskInfo->portNum]));          // The power ok bit mask.

  // Initialize port for communications to Ocean Server Battery controller.
  // This main task controls one directly, and controls the other via a supporting task.
  OSuartInitPort(pTaskInfo->portNum, 19200, UART_MODE_EIGHT_DATA_BITS, UART_MODE_ONE_STOP_BIT, UART_MODE_NO_PARITY);

  // This is not needed for the PCom, since we're not supplying power.
/*
  if (pTaskInfo->pBuf->sampling_active)         // Preset the power control bit.
    PORTL |= pBuf->pon_mask;                    // Fixed location. Turn power on.
  else
    PORTL &= ~pBuf->pon_mask;                   // Fixed location. Turn power off.
  DDRL |= pBuf->pon_mask;                       // Need an output here. Power On bit.
*/

  return retval;
} // OS_ExtBatteries_Init_Slave

// ================================================================================================
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv MASTER vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// ================================================================================================
// ................................................................................................
// Master Ocean Server External Batteries acquisition task.
//  Handle gathering of data and commands sent down from WGMS via a file transfer.
// Parameters:
//    param               - Pointer to task parameters.
// Returns:
//    Nothing.
//
static int32_t  next_sample_time;               // Time tracking.
void OS_ExtBatteries_Master(void* pParam)       // MASTER TASK MASTER TASK MASTER TASK MASTER TASK MASTER TASK
{
  OSEXTBATT_TASK    taskInfo;
  POSEXTBATT_TASK   pTaskInit = (POSEXTBATT_TASK)pParam;
  uint8_t           retval = FAIL;

  taskInfo.taskID   = pTaskInit->taskID;
  taskInfo.portNum  = pTaskInit->portNum;
  taskInfo.pBuf     = pTaskInit->pBuf;

  // Initialize the master task.
  retval = OS_ExtBatteries_Init_Master(&taskInfo);
  // Then initialize the mailboxes used to communicate to the slave task. This prevents the slave
  //  task from getting going until the master task has been set up. And the slave task will never
  //  get going if there is not enough memory for these mailboxes.
  slavetask_begin = OSMboxCreate((void*) 0);
  slavetask_done = OSMboxCreate((void*) 0);

  if(retval == SUCCESS)
  {
TADEBUG_STR("Ocean Server External Batteries master task start\r", 100, &retval);
    taskInfo.pBuf->sample_count  = 0;
    next_sample_time = OSTimeGet() - 1;         // Make sure we start sampling right away.
    for (;;)
    {
      // NOTE! For every packet or file received, it REQUIRES a response in kind!
      OSTimeDlyHMSM(0,0,0,500);
      if(taskInfo.pBuf->sampling_active == TRUE)
      {
        if ((next_sample_time - (int32_t)OSTimeGet()) <= 0)
        {
          // Got to the next minute.
          next_sample_time = OSTimeGet() + 60 * OS_TICKS_PER_SEC; // Fixed sample rate of once per minute.
          if (--taskInfo.pBuf->sample_count <= 0)
          {
            // Retrieve data from both Ocean Server controllers, but only if it is at the correct
            //  interval. If data is retrieved, send it back as an 0x91-type packet.
            os_busy = 1;                          // Tell Debug that we are busy.
            OS_ExtBatteries_DataSampleSubset(&taskInfo);
            os_busy = 0;                          // Tell Debug that we are done.
            taskInfo.pBuf->sample_count = taskInfo.pBuf->sample_limit;  // Reset the wait time (in minutes).
          }
        }
      }

      if(fileAvailable[pTaskInit->portNum] == TRUE)
      {
        // Communication from the Float has been received. See what it wants.
        os_busy = 1;                            // Tell Debug that we are busy.
        OS_ExtBatteries_ParseFile(&taskInfo, fileAvailableID[pTaskInit->portNum]);
        os_busy = 0;                            // Tell Debug that we are done.
        fileAvailableID[pTaskInit->portNum] = 0;
        fileAvailable[pTaskInit->portNum] = FALSE;
      }
    } // for
  }
  return;
} // OS_ExtBatteries_Master (Task)
// ================================================================================================
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ================================================================================================
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv SLAVE vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
// ================================================================================================
// ................................................................................................
// Slave Ocean Server External Batteries acquisition task.
//  Do what the master tells it, but on the second (smaller) controller.
// Parameters:
//    param               - Pointer to task parameters.
// Returns:
//    Nothing.
//
static POSEXTBATT_TASK    pSlaveTaskInfo;       // Place to get the slave task info buffer pointer.

void OS_ExtBatteries_Slave(void* pParam)        // SLAVE TASK SLAVE TASK SLAVE TASK SLAVE TASK SLAVE TASK
{
  OSEXTBATT_TASK    taskInfo;
  POSEXTBATT_TASK   pTaskInit = (POSEXTBATT_TASK)pParam;
  uint8_t           retval = FAIL;
  uint8_t           err;
  uint8_t           cmd;


  taskInfo.taskID   = pTaskInit->taskID;
  taskInfo.portNum  = pTaskInit->portNum;
  taskInfo.pBuf     = pTaskInit->pBuf;
  pSlaveTaskInfo    = &taskInfo;                // Allow some way to easily (kludgely) get data between tasks.
                                                // I know this is not the way to do it, but to do it right
                                                //  will mean rewriting too much and I don't have time for
                                                //  that. It would be better if this were to use OS allocated
                                                //  memory blocks...

  while ((slavetask_begin == NULL) || (slavetask_done == NULL))
    // Wait until the master creates these necessary mailboxes.
    OSTimeDly(10);

  retval = OS_ExtBatteries_Init_Slave(&taskInfo);

  if(retval == SUCCESS)
  {
TADEBUG_STR("Ocean Server External Batteries slave task start\r", 100, &err);
    for (;;)
    {
      // NOTE: This task expects the Ocean Server Battery controller to be awake when it is asked
      //        to perform a command. If not awake, it will eventually time out and return a fail
      //        condition.
      // Any of these commands may take quite some time, but typically less than a minute.
      cmd = (uint8_t)((int)OSMboxPend(slavetask_begin, 0, &err));
      switch (cmd)
      {
        case OSEB_SLAVECMD_BIN:
          // Parse a full hex dump from the Ocean Server controller to a record format that is
          //  compatible with the battery status packet. The slave task does not do any more
          //  with this data.

          break;
        case OSEB_SLAVECMD_X91:
          // Sort through the hex dump and gather battery data into structure.
          OS_ExtBatteries_ExtractData(&taskInfo);
          retval = OSEB_SLAVECMD_SUCCESS;
          break;
        case OSEB_SLAVECMD_HEX:
          // Send the Hex dump command to the Ocean Server controller.
          // Return the hex/text response in the "FileOut" buffer for this task.
          retval = OS_ExtBatteries_ExtractHex(&taskInfo);
          if (retval == SUCCESS)
            retval = OSEB_SLAVECMD_SUCCESS;
          else
            retval = OSEB_SLAVECMD_FAIL;
          break;
        case OSEB_SLAVECMD_ASCII:
          // Send the Battery Status command to the Ocean Server controller.
          // Return the ASCII/text response in the "FileOut" buffer for this task.
          retval = OS_ExtBatteries_ExtractAscii(&taskInfo);
          if (retval == SUCCESS)
            retval = OSEB_SLAVECMD_SUCCESS;
          else
            retval = OSEB_SLAVECMD_FAIL;
          break;
        default:
          //
          TADEBUG_STR("OS slave task: Unhandled command.\r", 100, &err);
          retval = OSEB_SLAVECMD_FAIL;
          break;
      } // switch
      err = OSMboxPost(slavetask_done, (void *)((uint16_t)retval));
    } // for
  }
  return;
} // OS_ExtBatteries_Slave (Task)
// ================================================================================================
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// ================================================================================================


// ................................................................................................
// Retrieve a sample from both controllers and distill the data into a battery status packet that is
//  sent to WGMS (Float C&C) in a X91-type packet.
//  Gather the information from both controllers in raw form in the file output buffers. Then parse
//    this data and pick out the items of interest. When done, send on to C&C.
//
//  NOTE: This routine should only be called from the master task!
//
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
// Returns:
//    Nothing.
//
void OS_ExtBatteries_DataSampleSubset(POSEXTBATT_TASK pTaskInfo)
{
  uint8_t   err;
  uint8_t   idx;

  // Clear compilation buffer where final distilled data will go.
  memset(&reportData.dataBlock.data_sample, 0, sizeof(OSEXTBATT_DATA_LOG_FORMAT));
  for (idx = 0; idx < NUM_OSEXTBATTERIES; idx++)
  {
    // Set each of the batteries' data to extreme so that one can infer easily when data has not
    //  been obtained.
    reportData.dataBlock.data_sample.battery[idx].status          = 0x80;   // Set to online, but not present.
    reportData.dataBlock.data_sample.battery[idx].temp            = 0x7F;   // 127C
    reportData.dataBlock.data_sample.battery[idx].power_remaining = 0xFFFF; // 65.535Ahr
    reportData.dataBlock.data_sample.battery[idx].current         = 0x7FFF; // 32.767A
    reportData.dataBlock.data_sample.battery[idx].voltage         = 0xFFFF; // 65.535V
  } // for

//?  pTaskInfo->pBuf->databufsize = 0;             // Clear intermediate data buffers.
//?  pSlaveTaskInfo->pBuf->databufsize = 0;
//?  memset(pTaskInfo->pBuf->databuf, 0, 2048);    // (Ugly!)
//?  memset(pSlaveTaskInfo->pBuf->databuf, 0, 2048);

  // Turn on Ocean Server controllers in preparation for data collection.
  SET_OCEANSERVERON;
  // Start the slave task data collection.
  err = OSMboxPost(slavetask_begin, (void *)OSEB_SLAVECMD_X91);
  // Start the master task data collection.
  OS_ExtBatteries_ExtractData(pTaskInfo);
//?  OS_ExtBatteries_Distill_Battery_Status(pTaskInfo, pTaskInfo);

  // Wait for the Slave task to be done.
  OSMboxPend(slavetask_done, 30 * OS_TICKS_PER_SEC, &err);
  // Turn the Ocean Server Controller off regardless.
  SET_OCEANSERVEROFF;

//?  port = cmd & 0xF;
//?  cmd &= 0xF0;
//?  // See if there is data to extract.
//?  if ((err == OS_ERR_NONE) && (cmd == OSEB_SLAVECMD_SUCCESS_HEX) && (pSlaveTaskInfo->pBuf->databufsize > 0))
//?    OS_ExtBatteries_Distill_Battery_Status(pTaskInfo, pSlaveTaskInfo);

  // Packet has been compiled. Send it to the Float C&C.
  //  Fill in data header. (lat, long, and time.)
  reportData.dataBlock.headerData.timestamp = (uint32_t)gettime();
  reportData.dataBlock.headerData.latitude  = latitude;
  reportData.dataBlock.headerData.longitude = longitude;
  // A fixed number of batteries.
  reportData.dataBlock.data_sample.batt_count = 12;
  // Put the 0x91 packet header together.
  reportData.headerReport.payloadType  = 0x000000C2;   // ?TBD Once defined, fixed forever.
  reportData.headerReport.boardID      = mainBoardID;
  reportData.headerReport.taskID       = pTaskInfo->taskID;
  reportData.headerReport.dataFormat   = 9;            // ?Not == 0. 0 is "reserved" for self identification.
  reportData.headerReport.parser       = 0;            // Binary.
  reportData.headerReport.priority     = 2;            // ?Not yet implemented, but probably correct for "data" type packet.
  reportData.headerReport.repeatCount  = 1;            // Only a single battery set per report.

  OS_ExtBatteries_DataReportXmitType91(pTaskInfo);  // Send it on its way.

  // Reset the buffer and count. All's gone.
  memset(&reportData, 0, sizeof(OSEXTBATT_DATA_REPORT));
TADEBUG_STR("Sent battery status to shore.\n", 100, &err);
} // OS_ExtBatteries_DataSampleSubset















// ................................................................................................
// Extract data coming in from the Ocean Server Battery Controller.
//  This is task specific and will place data into a common data area, but in their own sections.



//  The character buffer is expected to be one of the "fileout" buffers, and holds the raw hex data
//    from the Ocean Server Controller. Only controllers 0 and 1 are expected. Controller has room
//    for batteries 1 - 8, and controller 1 may only have batteries 1 - 4.
//  This routine will sort through the data and locate the specific fields required by the data_sample.
//  The destination buffer will not be cleared, and found data will overwrite any existing data.
//
//  NOTE: This routine should only be called from the master task!
//
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
// Returns:
//    Nothing.
//
void OS_ExtBatteries_ExtractData(POSEXTBATT_TASK pTaskInfo)
{
  BattControl_t   *pbc = &pTaskInfo->pBuf->batdat;
  char            *tmp_str;
  uint8_t         str_len;
  uint8_t         batbit;
  uint8_t         idx;
  BOOL            captured_all;

  uint8_t   err;
  OS_TMR    *Timer;

  if (!OS_ExtBatteries_PostTurnOn(pTaskInfo))
    return;

  // Setup for 20 second timer.
  Timer = OSTmrCreate(20 * OS_TMR_CFG_TICKS_PER_SEC + 1, 0,
              OS_TMR_OPT_ONE_SHOT,
              NULL, NULL,
              (uint8_t *)"OSEB_EH1",
              &err
             );
  OSTmrStart(Timer, &err);                      // Must manually start the timer.

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);
  // Initialize the battery data control.
  pbc->bmask      = 0xFF;                       // Won't know until first $C command. Presume until
                                                //  further notice that it will contain eight
                                                //  batteries. (Controller 0 holds 8; Controller 1
                                                //  holds 4 for this design.)
  pbc->bpresent   = 0xFF;                       // Presume that all batteries will be in circuit.
  memset(pbc->dcaptured, OSEB_DCAPT_UNUSED, MAX_OSCTLRBATTERIES);
                                                // None of the data has been captured yet.
                                                //  Set unused bits to one to simulate data captured.

  // Start the Battery Status command.
  OSuartPutCharWait(pTaskInfo->portNum, 'X', 100);

  while (OSTmrStateGet(Timer, &err) != OS_TMR_STATE_COMPLETED)  // Break out when appropriate, or time out.
  {
    // Read the next line from the OS Controller.
    str_len = OS_ExtBatteries_GetString(tmp_str,
                                        pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1,
                                        (uint16_t)(500L * OS_TICKS_PER_SEC / 1000));

/*
TADEBUG_CHR((pTaskInfo->portNum & 0x0F) | '0', 100);
TADEBUG_CHR('@', 100);
TADEBUG_STR(tmp_str, 100, &err);
*/

    if (str_len)
      OS_ExtBatteries_Distill_Battery_Status(pTaskInfo, tmp_str);

    // Each time through check to see if we've got all the data needed.
    captured_all = TRUE;                        // Be an optimist.
    for (batbit = 1, idx = 0; idx < MAX_OSCTLRBATTERIES; batbit <<= 1, idx++)
    {
      if (batbit & pbc->bmask & pbc->bpresent)
      {
        if (pbc->dcaptured[idx] != 0xFF)
          captured_all = FALSE;                 // Missing some data.
      }
    } // for
    if (captured_all)
      break;
  } // while
  // Stop data dump.
  OSuartPutCharWait(pTaskInfo->portNum, ' ', 100);


if (OSTmrStateGet(Timer, &err) == OS_TMR_STATE_COMPLETED)
{
TADEBUG_CHR((pTaskInfo->portNum & 0x0F) | '0', 100);
TADEBUG_STR("@TDto\n", 100, &err);
}
/*
else
{
TADEBUG_CHR((pTaskInfo->portNum & 0x0F) | '0', 100);
TADEBUG_STR("@TDdp\n", 100, &err);
}
*/

  OSTmrDel(Timer, &err);                        // Delete timer. (Improves performance some.)
  if (tmp_str)
  {
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }

} // OS_ExtBatteries_ExtractData





















// ................................................................................................
// Distill data in a character buffer into the battery data structure.
//  The character buffer is expected to be one line of raw hex data from the Ocean Server Controller.
//  Only controllers 0 and 1 are expected. Controller 0 has room for batteries 1 - 8, and controller 1
//  may only have batteries 1 - 4.
//  This routine will sort through the data and locate the specific fields required by the data_sample.
//  If a fields is found multiple times, the most recent data will overwrite any existing data.
//
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
//    pline               - Pointer to the string containing the input.
// Returns:
//    Nothing.
//
//? static char     linesep[]   = { '\r', '\n', '\0' };   // Line separators.
static char     fieldsep[]  = { ',',  '%',  '\0' };   // Field separators.

void OS_ExtBatteries_Distill_Battery_Status(POSEXTBATT_TASK pTaskInfo, char *pline)
{
  uint8_t   controller, battery;
  uint8_t   battreg;
  uint16_t  battdata;
  char      *pfield_reg, *pfield_data;

  if (pline[0] != '$')
    // All data lines start with a $. Ignore line if it doesn't.
    return;
  strupr(pline);                              // Desire all characters to be of the same (upper) case.
  switch (pline[1])
  {
    // Allow for various fields to be decoded.
    case 'B':
      // Battery data. Line contains register contents from a battery.
      // After the controller and battery identifier field, the fields appear in pairs until the
      //  checksum after the 5 (which we don't do anything with at this time). Each pair indicates
      //  the battery register number followed by its contents as a 16-bit value.
      controller  = pline[2] & 0xF;           // Separate out the controller and battery codes.
      pTaskInfo->pBuf->batdat.bmask = controller ? 0x0F : 0xFF;
      battery     = pline[3] & 0xF;
      pfield_reg  = ThreadSafeStrTok(&pline, fieldsep); // Skip over first field.
      do
      {
        // Get a pair of fields.
        pfield_reg  = ThreadSafeStrTok(&pline, fieldsep);
        pfield_data = ThreadSafeStrTok(&pline, fieldsep);
        if (*pfield_reg && *pfield_data)
        {
          // Decode only if the fields are not empty.
          battreg   =  (uint8_t)strtol(pfield_reg,  NULL, 16);  // Expect hexadecimal register number.
          battdata  = (uint16_t)strtol(pfield_data, NULL, 16);  // Expect hexadecimal register data value.
          OS_ExtBatteries_Apply_Battery_Status(pTaskInfo, controller, battery, battreg, battdata);
        }
      } while (*pfield_reg && *pfield_data);  // Continue as long as the fields contained something.
      break;
    case 'C':
      // Controller data -- interested in only the "battery present" data.
      controller  = pline[2] & 0xF;           // Separate out the controller code.
      pTaskInfo->pBuf->batdat.bmask = controller ? 0x0F : 0xFF;
      pfield_reg  = ThreadSafeStrTok(&pline, fieldsep); // Skip over first field.
      do
      {
        // Get a pair of fields.
        pfield_reg  = ThreadSafeStrTok(&pline, fieldsep);
        pfield_data = ThreadSafeStrTok(&pline, fieldsep);
        if (*pfield_reg && *pfield_data)
        {
          // Decode only if the fields are not empty.
          battreg   =  (uint8_t)strtol(pfield_reg,  NULL, 16);  // Expect hexadecimal register number.
          battdata  = (uint16_t)strtol(pfield_data, NULL, 16);  // Expect hexadecimal register data value.
          if (battreg == 1)
          {
            // Only send battery present status.
            pTaskInfo->pBuf->batdat.bpresent = battdata;
            for (battery = 1; battery <= ((controller == 0) ? 8 : 4); battery++)
              // Update status for each battery.
              OS_ExtBatteries_Apply_Battery_Status(pTaskInfo, controller, battery, 0x96, (battdata & (1 << (battery - 1))));
          }
        }
      } while (*pfield_reg && *pfield_data);  // Continue as long as the fields contained something.
      break;
    case 'S':
      // System data -- not interested at this time.

      break;
    default:
      // Any other type -- ignore for now.

      break;
  } // switch
} // OS_ExtBatteries_Distill_Battery_Status

// ................................................................................................
// Place data into the "dataBlock" per the given parameters.
//  The dataBlock structure is not a 1-for-1 match with the battery register set.
//  The parameters are checked for validity, and no data is stored if they do not match up with
//    the "dataBlock" structure.
//  The values that are stored are converted to units expected by the Float C&C for battery status.
//
//  NOTE: This routine should only be called from the master task!
//
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
//    node_idx            - Selected node index.
// Returns:
//    BOOL                - TRUE when data is retrieved successfully.
//                          FALSE if there was some kind of an issue.
//
void OS_ExtBatteries_Apply_Battery_Status(POSEXTBATT_TASK pTaskInfo,
                                          uint8_t controller, uint8_t battery,
                                          uint8_t battreg, uint16_t battdata)
{
  uint8_t         battidx;
  Battery_t       *pdsb;                        // Pointer to battery specific data sample.
  float           tmp_f;
  uint8_t         *pbc = &pTaskInfo->pBuf->batdat.dcaptured[battery - 1];

  battidx = controller * 8 + battery - 1;
  if (battidx >= NUM_OSEXTBATTERIES)
    // Something's wrong. Battery position out of range. Just exit.
    return;
  pdsb = &reportData.dataBlock.data_sample.battery[battidx];
  switch (battreg)
  {
    case 0x16:
      // Status.
      // In:  uint16 in status bits.
      //          0x8000 OVER_CHARGED_ALARM
      //          0x4000 TERMINATE_CHARGE_ALARM
      //          0x2000 Reserved
      //          0x1000 OVER_TEMP_ALARM
      //          0x0800 TERMINATE_DISCHARGE_ALARM
      //          0x0400 Reserved
      //          0x0200 REMAINING_CAPACITY_ALARM
      //          0x0100 REMAINING_TIME_ALARM
      //          * * * * * * Status Bits * * * * *
      //          0x0080 INITIALIZED
      //          0x0040 DISCHARGING
      //          0x0020 FULLY_CHARGED
      //          0x0010 FULLY_DISCHARGED
      // Out: uint8 in status bits.
      //          Bit 0:  1 = Battery is present                  - Not available from battery.
      //          Bit 1:  1 = Battery fully discharged
      //          Bit 2:  1 = Battery fully charged
      //          Bit 3:  1 = Battery is discharging
      //          Bit 4:  1 = Battery terminate discharge alarm
      //          Bit 5:  1 = Battery terminate charge alarm
      //          Bit 6:  1 = Battery over-temp alarm
      //          Bit 7:  1 = Battery is currently online         - Not available from battery.
      //pdsb->status |= (battdata & (1 << )) ? (1 << 0) : 0;
      pdsb->status |= (battdata & (1 <<  4)) ? (1 << 1) : 0;
      pdsb->status |= (battdata & (1 <<  5)) ? (1 << 2) : 0;
      pdsb->status |= (battdata & (1 <<  6)) ? (1 << 3) : 0;
      pdsb->status |= (battdata & (1 << 11)) ? (1 << 4) : 0;
      pdsb->status |= (battdata & (1 << 15)) ? (1 << 5) : 0;
      pdsb->status |= (battdata & (1 << 12)) ? (1 << 6) : 0;
      //pdsb->status |= (battdata & (1 << )) ? (1 << 7) : 0;
      *pbc |= OSEB_DCAPT_STATUS_OTHER;
      break;
    case 0x08:
      // Temperature.
      // In:  uint16 in units of 0.1K.
      // Out: int8 in units of 1C.
      tmp_f = ((float)battdata - 2731.5) / 10.0 + 0.5;
      pdsb->temp = (tmp_f > 127.0) ? 0x7F : ((tmp_f < -128.0) ? 0x80 : (int8_t)tmp_f);
      *pbc |= OSEB_DCAPT_TEMPERATURE;
      break;
    case 0x0F:
      // Power remaining (for specific battery).
      // In:  uint16 in units 1mAh.
      // Out: uint16 in units 10mWh. (@10.8V fixed as the conversion factor.)
      tmp_f = (float)battdata * 1.08 + 0.5;
      pdsb->power_remaining = (tmp_f < 65536.0) ? (uint16_t)tmp_f : 0xFFFF;
      *pbc |= OSEB_DCAPT_PWRREMAIN;
      break;
    case 0x0A:
      // Current.
      // In:  int16 in units 1mA.
      // Out: int16 in units 1mA.
      pdsb->current = battdata;
      *pbc |= OSEB_DCAPT_CURRENT;
      break;
    case 0x09:
      // Voltage.
      // In:  uint16 in units 1mV.
      // Out: uint16 in units 1mV.
      pdsb->voltage = battdata;
      *pbc |= OSEB_DCAPT_VOLTAGE;
      break;
    // The following are (is) not part of the standard Smart Battery register set.
    case 0x96:
      // Set battery present bit in status if battery data is TRUE.
      pdsb->status |= battdata ? (1 << 0) : 0;
      *pbc |= OSEB_DCAPT_STATUS_BP;
      break;
    default:
      // Any currently unhandled battery registers.
      //  Discard data.

      break;
  } // switch
} // OS_ExtBatteries_Apply_Battery_Status

// ................................................................................................
// Post a type 0x91 packet to the Float C&C queue for transmission to WGMS.
//  Packet is assumed to have been assembled within the task data block.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
// Returns:
//    Nothing.
//
uint8_t OS_ExtBatteries_DataReportXmitType91(POSEXTBATT_TASK pTaskInfo)
{
  uint8_t retval = 0;
  uint8_t err = 0;
  POSEXTBATT_DATA_REPORT_XMIT pReport = NULL;

  OSMutexPend(pMutexFloatQueue, INFINITE, &err);

  pReport = (POSEXTBATT_DATA_REPORT_XMIT)&(txFloatData[headFloatTx]);
  memset(pReport, 0, sizeof(FLOAT_TX_BUFFER));

  // Variable length report. Only send data, but not any empty blocks.
  pReport->comm.headers.length  = sizeof(OSEXTBATT_DATA_REPORT_XMIT) + CRC16_SIZE;
  pReport->comm.headers.destination = FLOAT_IRIDIUM_INTERFACE;

  pReport->comm.iridiumInfo.structureType = 0x91;
  pReport->comm.iridiumInfo.size = pReport->comm.headers.length - sizeof(PIB_IRIDIUM_DATA_XMIT) - CRC16_SIZE;

  // Copy the sample data, excluding the empty space.
  memcpy(&(pReport->x91reportData), &reportData, sizeof(OSEXTBATT_DATA_REPORT));

  (headFloatTx < MAX_FLOAT_BUFFERS - 1) ? (headFloatTx++) : (headFloatTx = 0);

  if(headFloatTx == tailFloatTx)
  {
    (tailFloatTx < MAX_FLOAT_BUFFERS - 1) ? (tailFloatTx++) : (tailFloatTx = 0);
  }

  OSMutexPost(pMutexFloatQueue);

// Debug display of raw data sent to shore.
uint8_t *pbyte;
char num_str[3];
pbyte = (uint8_t*)pReport;
for (uint16_t idx = 0; idx < pReport->comm.headers.length; idx++)
{
  snprintf_PSTR(num_str, 3, "%02X", *pbyte++);
  num_str[2] = 0; // Just in case.
  TADEBUG_STR(num_str, 100, &err);
} // for
TADEBUG_CHR('\n', 0);

  return retval;
} // OS_ExtBatteries_DataReportXmitType91

static char* ThreadSafeStrTok(char** context, char* sep)
{
  if (!context || !*context) return "";

  char* retval = &(*context)[strspn(*context, sep)];
  char* eos = &retval[strcspn(retval, sep)];

  if (*eos) *eos++ = '\0';

  *context = eos;
  return retval;
} // ThreadSafeStrTok


// ................................................................................................
// Sequence to run after turning on the Ocean Server controller.
//  Power must have been applied prior to entry. If not, it will wait until it has done so or times out.
//  Once it is turned on it will process the responses until we get to a prompt (->) and then exit.
//  It will fail if it takes too long.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
// Returns:
//    uint8_t             - Indicates SUCCESS of FAILure of interpretation.
//
BOOL OS_ExtBatteries_PostTurnOn(POSEXTBATT_TASK pTaskInfo)
{
  BOOL      found_prompt = FALSE;
  uint8_t   err;
  char      *tmp_str;
  OS_TMR    *Timer;

  OSTimeDlyHMSM(0,0,1,0);                       // Give it some time to allow the OS to wake up.
  if (IS_OCEANSERVEROFF)
  {
    // Did not turn on within a short time. Quit. This is a hardware problem in that the on/off
    //  control bit did not function.
    snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR, "Ocean Server Controller <%u> turn on failed.\n",
                  pTaskInfo->portNum);
    TADEBUG_STR(pTaskInfo->pBuf->oseb_tmp_str, 100, &err);
    return FAIL;
  }
  snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                "Power on checking. Task <%u>.\n",
                pTaskInfo->portNum);
  TADEBUG_STR(pTaskInfo->pBuf->oseb_tmp_str, 100, &err);
  OSTimeDlyHMSM(0,0,2,0);                       // Give it some more time to get the wake up strings out of the way.

  // Setup for 10 second timer.
  Timer = OSTmrCreate(10 * OS_TMR_CFG_TICKS_PER_SEC + 1, 0,
              OS_TMR_OPT_ONE_SHOT,
              NULL, NULL,
              (uint8_t *)"OSEB_PTO",
              &err
             );
  OSTmrStart(Timer, &err);                      // Must manually start the timer.

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);

  // Remove any extraneous data that might have fallen into the input buffer.
  // This includes the power up string(s) and possible menu or data dumps.
  OSuartFlushBuffers(pTaskInfo->portNum, UART_RX_BUFFER | UART_TX_BUFFER);
  // Send a space. This is the break character that stops any output (eventually) by the OS.
  OSuartPutCharWait(pTaskInfo->portNum, ' ', 100);

  // Sift through all the stuff before getting to the final prompt.
  // Limit how long we do this.
  while ((OSTmrStateGet(Timer, &err) != OS_TMR_STATE_COMPLETED))
  {
    OS_ExtBatteries_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (uint16_t)(500L * OS_TICKS_PER_SEC / 1000));


/*
snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR, "OSC%u|% 2u>%s\n", pTaskInfo->portNum, strlen(tmp_str), tmp_str);
TADEBUG_STR(pTaskInfo->pBuf->oseb_tmp_str, 100, &err);
*/


    if (strncmp(tmp_str, "->", 2) == 0)
    {
      // Got a prompt, but is it the last one?
      if (!OS_ExtBatteries_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (uint16_t)(500L * OS_TICKS_PER_SEC / 1000)))
        // Seems to be quiet. Expect this to be the time that it is waiting for the next command.
        found_prompt = TRUE;
        break;
    }
  }
  OSTmrDel(Timer, &err);                        // Delete timer. (Improves performance some.)
  if (tmp_str)
  {
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }
  if (!found_prompt)
  {
    // Did not find a prompt. See what happens from here.
    snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                  "Ocean Server Controller <%u> failed to find prompt.\nLast string <%s>\n",
                  pTaskInfo->portNum, tmp_str);
    TADEBUG_STR(pTaskInfo->pBuf->oseb_tmp_str, 100, &err);
  }

  return found_prompt;
//  return TRUE;
} // OS_ExtBatteries_PostTurnOn

// ................................................................................................
// Place the Hex dump from the Ocean Server controller into this task's data buffer buffer.
//  This routine requires that the Ocean Server Controller is awake and waiting at the prompt.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
// Returns:
//    BOOL                - Indicates SUCCESS (TRUE) of FAILure (FALSE) of interpretation.
//
BOOL OS_ExtBatteries_ExtractHex(POSEXTBATT_TASK pTaskInfo)
{
  uint16_t  fcidx         = 0;                  // File character index.
  uint8_t   err;
  BOOL      good_pkt      = FALSE;              // When TRUE, good/desired/full packet is being collected.
  uint8_t   str_len;
  char      *tmp_str;
  OS_TMR    *Timer;

  if (!OS_ExtBatteries_PostTurnOn(pTaskInfo))
    return FAIL;

  // Setup for 20 second timer.
  Timer = OSTmrCreate(20 * OS_TMR_CFG_TICKS_PER_SEC + 1, 0,
              OS_TMR_OPT_ONE_SHOT,
              NULL, NULL,
              (uint8_t *)"OSEB_EH1",
              &err
             );
  OSTmrStart(Timer, &err);                      // Must manually start the timer.

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);
  // Start the Battery Status command.
  OSuartPutCharWait(pTaskInfo->portNum, 'X', 100);

  while (OSTmrStateGet(Timer, &err) != OS_TMR_STATE_COMPLETED)  // Break out when appropriate, or time out.
  {
    // Read the next line from the OS Controller.
    str_len = OS_ExtBatteries_GetString(tmp_str,
                                        pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1,
                                        (uint16_t)(500L * OS_TICKS_PER_SEC / 1000));


/*
snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR, "OSCa%u|%02u|%c>%s\n",
              pTaskInfo->portNum, str_len,
              good_pkt      ? 'G' : '.',
              tmp_str);
TADEBUG_STR(pTaskInfo->pBuf->oseb_tmp_str, 100, &err);
*/


    if (str_len == 0)
    {
      // Timed out, since nothing was received.
      if (good_pkt)
        // Timed out and has a good packet. Must be done.
        break;
      else
        // Clear out the file buffer. Start over collecting data.
        fcidx = 0;
    }
    else
    {
      // Store just received line.
      if ((fcidx + strlen(tmp_str)) < 2048)
      {
        // Only add to buffer if there's room.
        memcpy(&pTaskInfo->pBuf->databuf[fcidx], tmp_str, str_len);
        fcidx += str_len;
      }
      if (!good_pkt && tmp_str[1] == 'B')
      {
        // This is a 'B' line of this packet.
        // Want it to contain the register, formatted: ,09,
        if (strstr(&tmp_str[4], ",09,") != NULL)
        {
          // This indicates a full (enough) packet, and not the abbreviated one.
          good_pkt = TRUE;
//          // Stop any further data dump after this packet has finished completely.
//          OSuartPutCharWait(pTaskInfo->portNum, ' ', 100);
        }
      }
    }
  } // while
  // Return whatever has been collected regardless of result.
  pTaskInfo->pBuf->databufsize = fcidx;         // Record the final file size.

  OSTmrDel(Timer, &err);                        // Delete timer. (Improves performance some.)
  if (tmp_str)
  {
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }

  return (!str_len && good_pkt);                // Fails if it did not finish getting the packet,
                                                //  or hadn't found a good packet yet.
} // OS_ExtBatteries_ExtractHex

// ................................................................................................
// Place the ASCII dump from the Ocean Server controller into this task's data buffer buffer.
//  This routine requires that the Ocean Server Controller is awake and waiting at the prompt.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
// Returns:
//    BOOL                - Indicates SUCCESS (TRUE) of FAILure (FALSE) of interpretation.
//
BOOL OS_ExtBatteries_ExtractAscii(POSEXTBATT_TASK pTaskInfo)
{
  uint16_t  fcidx = 0;                          // File character index.
  uint8_t   err;
  char      *tmp_str;
  OS_TMR    *Timer;

  if (!OS_ExtBatteries_PostTurnOn(pTaskInfo))
    return FAIL;

  // Setup for 20 second timer.
  Timer = OSTmrCreate(20 * OS_TMR_CFG_TICKS_PER_SEC + 1, 0,
              OS_TMR_OPT_ONE_SHOT,
              NULL, NULL,
              (uint8_t *)"OSEB_EA1",
              &err
             );
  OSTmrStart(Timer, &err);                      // Must manually start the timer.

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);
  // Start the Battery Status command.
  OSuartPutCharWait(pTaskInfo->portNum, 'B', 100);

  // Read the first line of response. This is the echo of the command. Don't need/want it, so throw it out.
  OS_ExtBatteries_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (uint16_t)(500L * OS_TICKS_PER_SEC / 1000));
  // Get the first result string.
  OS_ExtBatteries_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (uint16_t)(500L * OS_TICKS_PER_SEC / 1000));
  memcpy(&pTaskInfo->pBuf->databuf[fcidx], tmp_str, strlen(tmp_str));
  fcidx += strlen(tmp_str);
  // Send the break character so that the output will not repeat.
  OSuartPutCharWait(pTaskInfo->portNum, ' ', 100);
  while ((OSTmrStateGet(Timer, &err) != OS_TMR_STATE_COMPLETED) && (strncmp(tmp_str, "====", 4) != 0))
  {
    // Continue reading data from the OS until we get to the line with lots of '=' characters.
    OS_ExtBatteries_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (uint16_t)(500L * OS_TICKS_PER_SEC / 1000));

/*
TADEBUG_STR(tmp_str, 100, &err);
*/

    if ((fcidx + strlen(tmp_str)) > 2048)
      // Out of room. Truncate string. Not necessarily a failure, but a limitation.
      break;
    memcpy(&pTaskInfo->pBuf->databuf[fcidx], tmp_str, strlen(tmp_str));
    fcidx += strlen(tmp_str);
  }
  pTaskInfo->pBuf->databufsize = fcidx;         // Record the final file size.

  OSTmrDel(Timer, &err);                        // Delete timer. (Improves performance some.)
  if (tmp_str)
  {
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }
  OSTimeDly(2 * OS_TICKS_PER_SEC);              // Give it some time to let it output the pre-prompt
                                                //  data. Then flush that data.
  OSuartFlushBuffers(pTaskInfo->portNum, UART_RX_BUFFER | UART_TX_BUFFER);

  return SUCCESS;                               // Just return what it found so far.
} // OS_ExtBatteries_ExtractAscii

// ................................................................................................
// Place the Raw dump from the Ocean Server controller into this task's 'databuf'.
//  Run from the master task only. Will trigger the slave to read data from its controller.
//
//  Since there is not enough RAM to get the result from both controllers at one time, we must
//    specific which controller to interrogate and get the information from. But ultimately the
//    data must be returned in the this (master) task's 'databuf'.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
//    controller          - Controller number to get data from.
// Returns:
//    uint8_t             - Indicates SUCCESS of FAILure of interpretation.
//
uint8_t OS_ExtBatteries_RawDump(POSEXTBATT_TASK pTaskInfo, uint8_t type, uint8_t controller)
{
  uint8_t   retval = FAIL;
  uint8_t   err;
  uint8_t   cmd;

  if (type != OSEB_SLAVECMD_HEX && type != OSEB_SLAVECMD_ASCII)
  {
    // Not one of the raw data types.
    snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                  "Raw data dump type <%u> does not exist.\n", type);
    memcpy(&pTaskInfo->pBuf->databuf[0], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
    pTaskInfo->pBuf->databufsize = strlen(pTaskInfo->pBuf->oseb_tmp_str);
    return FAIL;
  }
  if (controller != 1 && controller != 2)
  {
    // Not one of the controllers.
    snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                  "Controller <%u> does not exist.\n", controller);
    memcpy(&pTaskInfo->pBuf->databuf[0], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
    pTaskInfo->pBuf->databufsize = strlen(pTaskInfo->pBuf->oseb_tmp_str);
    return FAIL;
  }

  SET_OCEANSERVERON;

  pTaskInfo->pBuf->databufsize = 0;             // Effectively clear the data buffers.
  pSlaveTaskInfo->pBuf->databufsize = 0;
  if (controller == 1)
  {
    // Extract the data attached to the Master task.
    if (type == OSEB_SLAVECMD_HEX)
      retval = OS_ExtBatteries_ExtractHex(pTaskInfo);
    if (type == OSEB_SLAVECMD_ASCII)
      retval = OS_ExtBatteries_ExtractAscii(pTaskInfo);
    // If there is any result, it will have been placed into the appropriate data buffer. No extra work here.
  }
  else if (controller == 2)
  {
    // Start the slave task.
    err = OSMboxPost(slavetask_begin, (void *)((uint16_t)type));
    // Wait for the Slave task to be done.
    cmd = (uint8_t)((int)OSMboxPend(slavetask_done, 30 * OS_TICKS_PER_SEC, &err));
    // See if there is data to merge.
    pTaskInfo->pBuf->databufsize = pSlaveTaskInfo->pBuf->databufsize;
    if ((err == OS_ERR_NONE) && (cmd == OSEB_SLAVECMD_SUCCESS) && (pTaskInfo->pBuf->databufsize > 0))
    {
      // Copy file data from slave task data buffer to master task file buffer.
      if (pTaskInfo->pBuf->databufsize)
        memcpy(pTaskInfo->pBuf->databuf, pSlaveTaskInfo->pBuf->databuf, pTaskInfo->pBuf->databufsize);
      retval = SUCCESS;
    }
    else
    {
      if (pTaskInfo->pBuf->databufsize >= 2048)
      {
        // In case there was a size issue, limit string to size of the buffer and force an end-of-string.
        pTaskInfo->pBuf->databufsize = 2048;
        pTaskInfo->pBuf->databuf[2047] = 0;
      }
      retval = FAIL;
    }
  }
  // Done with the controller. Put it back to sleep.
  SET_OCEANSERVEROFF;
  if (pTaskInfo->pBuf->databufsize == 0)
  {
    // No data returned from the controller.
    snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                  "Controller <%u> did not return data.\n", controller);
    memcpy(&pTaskInfo->pBuf->databuf[0], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
    pTaskInfo->pBuf->databufsize = strlen(pTaskInfo->pBuf->oseb_tmp_str);
    retval = FAIL;
  }

  return retval;                               // If there is nothing returned, it will deal with
                                                //  that later.
} // OS_ExtBatteries_RawDump

// ................................................................................................
// File contents parser.
//  Figure out what the shore wants from it and try to accommodate.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
//    fileID              - File pointer.
// Returns:
//    uint8_t             - Indicates SUCCESS of FAILure of interpretation.
//
#define CMD_NODE        (1<<0)                  // Main commands.
#define CMD_SAMPL       (1<<1)
#define CMD_SYST        (1<<2)
#define CMD_CMD         (1<<3)

#define SCMD_SET        (1<<0)                  // Sub-commands.
#define SCMD_GET        (1<<1)
#define SCMD_PROG       (1<<2)
#define SCMD_READ       (1<<3)
#define SCMD_NUM        (1<<4)

#define ACMD_0          (1<<0)                  // Alternate sub-functions specific to command.
#define ACMD_1          (1<<1)
#define ACMD_2          (1<<2)
#define ACMD_3          (1<<3)
#define ACMD_4          (1<<4)
#define ACMD_5          (1<<5)
#define ACMD_6          (1<<6)
#define ACMD_7          (1<<7)

uint8_t OS_ExtBatteries_ParseFile(POSEXTBATT_TASK pTaskInfo, uint32_t fileID)
{
  uint8_t     retval = FAIL;
  char        *pdatabuf = pTaskInfo->pBuf->databuf;
  uint16_t    *pdatabufsize = &pTaskInfo->pBuf->databufsize;
  char*       pBuf;
  uint8_t     err = 0;
  int16_t     tmp2_i;
  BOOL        tmp_b;
  uint8_t     cmd, scmd, acmd;                  // Command and sub-command flag bytes.
  uint16_t    scmd_num;

/*
  memset(&(fileOut[pTaskInfo->portNum][0]), 0, MAX_FILE_SIZE_OUT);
  fileOutSize[pTaskInfo->portNum] = 0;          // MUST output a file prior to exit!
*/
  memset(pdatabuf, 0, 2048);                    // Put all intermediate data into this buffer.
  *pdatabufsize = 0;                            // Clear it just because.

  // Convert all characters to lower case, since that's what the parser expects.
  strlwr(&fileIn[pTaskInfo->portNum][0]);
  pBuf = &fileIn[pTaskInfo->portNum][0];

  char* context = pBuf;
snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR, "%s\n", pBuf);
TADEBUG_STR(pTaskInfo->pBuf->oseb_tmp_str, 100, &err);
  char sep[] = { ' ', '\t', '\r', '\n', '\0' };
  char* cmdTok = ThreadSafeStrTok(&context, sep);

       if ((cmd  = (strncmp(cmdTok, "sampl", 5) == 0) ? CMD_SAMPL  : 0)) ;
  else if ((cmd  = (strncmp(cmdTok, "sys",   3) == 0) ? CMD_SYST   : 0)) ;
  else goto exit_TPF;

  scmd = 0;
  scmd_num = 0xFFFF;
  if (cmd == CMD_SAMPL)
  {
    cmdTok = ThreadSafeStrTok(&context, sep);
         if ((scmd   = (strcmp(cmdTok, "set"    ) == 0)       ? SCMD_SET  : 0)) ;
    else if ((scmd   = (strcmp(cmdTok, "get"    ) == 0)       ? SCMD_GET  : 0)) ;
    else if ((scmd   = (strcmp(cmdTok, "program") == 0)       ? SCMD_PROG : 0)) ;
    else if ((scmd   = (strcmp(cmdTok, "read"   ) == 0)       ? SCMD_READ : 0)) ;
    else if ((scmd   = (cmdTok[0] >= '0' && cmdTok[0] <= '9') ? SCMD_NUM  : 0))
      scmd_num = atoi(cmdTok);
  }

// SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE
  if (cmd == CMD_SAMPL)
  {
    // Service the 'sampl/e' command. Sampling related commands. Format is:
    //    sampl is
    //    sampl set|get|program|read on|off
    //    sampl set|get|program|read i|inter/val <t#>
    //    sampl <c#> rawhex|rawasc/ii
    //    sampl force
    //      where:  <t#> is the (approximate) loop time in minutes.
    //      and:    <c#> is the controller number from 1 to 2 (at this time).
    if (scmd != 0)
      // Previous token was taken by a sub-command. Not always the case.
      cmdTok = ThreadSafeStrTok(&context, sep);
    if (strcmp(cmdTok, "is") == 0)              // Report sampling information.
    {
      #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 1)
      EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                     (void*)&tmp2_i);
      tmp2_i &= 0xFF;
      #else
      #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 2)
      EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                     (void*)&tmp2_i);
      #else
      #warning "Not a valid data size for allowed operation."
      #endif
      #endif
      snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                    "Sampling:   Default = %s, Current = %s.\n",
                    tmp2_i                            ? "active" : "inactive",
                    pTaskInfo->pBuf->sampling_active  ? "active" : "inactive");
      memcpy(&pdatabuf[*pdatabufsize], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
      *pdatabufsize += strlen(pTaskInfo->pBuf->oseb_tmp_str);
      // --
      #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 1)
      EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                     (void*)&tmp2_i);
      tmp2_i &= 0xFF;
      #else
      #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 2)
      EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                     (void*)&tmp2_i);
      #else
      #warning "Not a valid data size for allowed operation."
      #endif
      #endif
      if (pTaskInfo->pBuf->sampling_active)
        snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                      "Interval:   Default =% 5d, Current =% 5d. Time to next sample: %d minutes.\n",
                      tmp2_i,
                      pTaskInfo->pBuf->sample_limit,
                      pTaskInfo->pBuf->sample_count);
      else
        snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                      "Interval:   Default =% 5d, Current =% 5d. Time to next sample: Not sampling.\n",
                      tmp2_i,
                      pTaskInfo->pBuf->sample_limit);
      memcpy(&pdatabuf[*pdatabufsize], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
      *pdatabufsize += strlen(pTaskInfo->pBuf->oseb_tmp_str);
      // --
      retval = SUCCESS;
    }
    else if ((acmd = (strcmp(cmdTok, "on" ) == 0) ? ACMD_1 : 0) ||
             (acmd = (strcmp(cmdTok, "off") == 0) ? ACMD_2 : 0))
    {
      if ((scmd == SCMD_GET) || (scmd == SCMD_READ))
      {
        if (scmd == SCMD_GET)
        {
          memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Active sampling state: "), 22);
          *pdatabufsize += 22;
          tmp_b = pTaskInfo->pBuf->sampling_active;
        }
        else
        {
          memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Startup sampling state: "), 23);
          *pdatabufsize += 23;
          #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 1)
          EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                         (void*)&tmp2_i);
          tmp2_i &= 0xFF;
          #else
          #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 2)
          EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                         (void*)&tmp2_i);
          #else
          #warning "Not a valid data size for allowed operation."
          #endif
          #endif
        }
        if (tmp2_i)
        {
          memcpy_P(&pdatabuf[*pdatabufsize], PSTR("sampling\n"), 9);
          *pdatabufsize += 9;
        }
        else
        {
          memcpy_P(&pdatabuf[*pdatabufsize], PSTR("idle\n"), 5);
          *pdatabufsize += 5;
        }
        retval = SUCCESS;
      }
      else if (scmd == SCMD_SET)
      {
        if (acmd == ACMD_1) // == "on"
        {
          pTaskInfo->pBuf->sampling_active = TRUE;
          PORTL |= pTaskInfo->pBuf->pon_mask;   // Power on.
          pTaskInfo->pBuf->sample_count  = 0;   // Create a sample immediately.
          next_sample_time = OSTimeGet() + 2 * OS_TICKS_PER_SEC;
                                                // Wait two seconds for any response from the device,
                                                //  which will be digested by the debug task, before
                                                //  initiating the first sample. Always sample as
                                                //  soon as practicable after power has been turned on.
        }
        else if (acmd == ACMD_2)  // == "off"
        {
          pTaskInfo->pBuf->sampling_active = FALSE;
          PORTL &= ~pTaskInfo->pBuf->pon_mask;  // Power off.
          retval = SUCCESS;
        }
//        goto exit_TPF;
      }
      else if (scmd == SCMD_PROG)
      {
        tmp_b = (acmd == ACMD_1);               // TRUE if "on".
        #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 1)
        EepromWriteByteUnSigned((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                                (uint8_t)tmp_b);
        #else
        #if (OSEXTBATT_EEPROM__SIZE__SAMPLINGACTIVE == 2)
        EepromWriteWordUnSigned((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLINGACTIVE),
                                (uint16_t)tmp_b);
        #else
        #warning "Not a valid data size for allowed operation."
        #endif
        #endif
        retval = SUCCESS;
      }
//      else
//        goto exit_TPF;
    }
    else if ((strcmp( cmdTok, "i" )       == 0) ||
             (strncmp(cmdTok, "inter", 5) == 0))
    {
      if ((scmd == SCMD_GET) || (scmd == SCMD_READ))
      {
        if (scmd == SCMD_GET)
        {
          memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Active"), 6);
          *pdatabufsize = 6;
          tmp2_i = pTaskInfo->pBuf->sample_limit;
        }
        else
        {
          memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Startup"), 7);
          *pdatabufsize = 7;
          #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 1)
          EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                         (void*)&tmp2_i);
          tmp2_i &= 0xFF;
          #else
          #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 2)
          EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                         (void*)&tmp2_i);
          #else
          #warning "Not a valid data size for allowed operation."
          #endif
          #endif
        }
        snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                      " sample interval: %d minutes.\n", tmp2_i);
        memcpy(&pdatabuf[*pdatabufsize], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
        *pdatabufsize += strlen(pTaskInfo->pBuf->oseb_tmp_str);
        retval = SUCCESS;
      }
      else if ((scmd == SCMD_SET) || (scmd == SCMD_PROG))
      {
        cmdTok = ThreadSafeStrTok(&context, sep);
        tmp2_i = atoi(cmdTok);
        if (tmp2_i)
        {
          // Not allowed to set to zero. It's not.
          if (scmd == SCMD_SET)
          {
            pTaskInfo->pBuf->sample_limit = tmp2_i;
            pTaskInfo->pBuf->sample_count = 0;  // Write next sample, regardless of where we are. Then start new cycle.
          }
          else
          {
            #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 1)
            EepromWriteByteUnSigned((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                                    (uint8_t)tmp2_i);
            #else
            #if (OSEXTBATT_EEPROM__SIZE__SAMPLEINTERVAL == 2)
            EepromWriteWordUnSigned((void*)(pTaskInfo->pBuf->baseAddr + OSEXTBATT_EEPROM_OFFSET_SAMPLEINTERVAL),
                                    (uint16_t)tmp2_i);
            #else
            #warning "Not a valid data size for allowed operation."
            #endif
            #endif
          }
          retval = SUCCESS;
        }
      }
    }
    else if (strcmp(cmdTok, "rawhex") == 0)     // Retrieve the raw data in hex format data from
                                                //  Ocean Server controller.
    {
      if (scmd_num == 0xFFFF)
      {
        memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Missing Controller ID. (1 or 2)\n"), 32);
        *pdatabufsize += 32;
      }
      else
        // Expect this routine to place its data into 'databuf' and 'databufsize'. And then return
        //  the appropriate result value.
        retval = OS_ExtBatteries_RawDump(pTaskInfo, OSEB_SLAVECMD_HEX, scmd_num);
    }
    else if (strncmp(cmdTok, "rawasc", 6) == 0) // Retrieve the raw data in ASCII format data from
                                                //  Ocean Server controller.
    {
      if (scmd_num == 0xFFFF)
      {
        memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Missing Controller ID. (1 or 2)\n"), 32);
        *pdatabufsize += 32;
      }
      else
        // Expect this routine to place its data into 'databuf' and 'databufsize'. And then return
        //  the appropriate result value.
        retval = OS_ExtBatteries_RawDump(pTaskInfo, OSEB_SLAVECMD_ASCII, scmd_num);
    }
    else if (strcmp(cmdTok, "force") == 0)      // Force a(n additional) sample to go out.
    {
      // Expect this routine to use 'databuf' and 'databufsize' for its own purposes.
      OS_ExtBatteries_DataSampleSubset(pTaskInfo);
      *pdatabufsize = 0;                        // Nothing specific to report.
      retval = SUCCESS;                         // Allow the generic success string to be reported.
    }
  } // if (sampl)
// SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS SYS
  else if (cmd == CMD_SYST)
  {
    // Service the 'sys/tem' command. Various higher level commands. Format is:
    //    sys ver
    //    sys ...???!!!
      cmdTok = ThreadSafeStrTok(&context, sep);
    if (strncmp(cmdTok, "ver", 3) == 0)              // Report sampling information.
    {
      memcpy_P(&pdatabuf[*pdatabufsize], PSTR("Personality: Ocean Server Battery Controller\n"), 45);
      *pdatabufsize += 45;
      tmp2_i = pgm_read_word(&swversion);
      snprintf_PSTR(pTaskInfo->pBuf->oseb_tmp_str, SIZEOF_OSEB_TMP_STR,
                    "Version: %d.%d Build: %d (%d-%03d).\n",
                    MAJOR, MINOR, tmp2_i, tmp2_i >> 8, tmp2_i & 0xFF);
      memcpy(&pdatabuf[*pdatabufsize], pTaskInfo->pBuf->oseb_tmp_str, strlen(pTaskInfo->pBuf->oseb_tmp_str));
      *pdatabufsize += strlen(pTaskInfo->pBuf->oseb_tmp_str);
      retval = SUCCESS;
    }
  } // if (system)

exit_TPF:
  if (*pdatabufsize == 0)
  {
    // No result given. Supply a generic succes or failure string.
    if(retval == FAIL)
    {
      // For any failure, return a message indicating such.
      memcpy_P(&pdatabuf[*pdatabufsize], PSTR("OS Cmd: failed"), 14);
      *pdatabufsize = 14;
    }
    else
    {
      // Generic succes for comand.
      memcpy_P(&pdatabuf[*pdatabufsize], PSTR("OS Cmd: OK"), 10);
      *pdatabufsize = 10;
    }
  }
  // Copy everything into the file out buffer.
  memcpy(&fileOut[pTaskInfo->portNum][0], pdatabuf, *pdatabufsize);
  fileOutSize[pTaskInfo->portNum] = *pdatabufsize;
  // Send response back up the chain.
  OS_ExtBatteries_QueueFile(pTaskInfo, fileID);
TADEBUG_STR(&fileOut[pTaskInfo->portNum][0], 100, &err);
  return SUCCESS;
} // OS_ExtBatteries_ParseFile


// ................................................................................................
// Put a buffer onto the file out queue for transfer back to WGMS.
//  This may be done in response to a file command, or asynchronously. Regardless, the buffer must
//    have been transferred before it can be used again. Therefore it cannot respond to a command
//    if an asynchronous buffer is being uploaded, until that transfer is complete.
//
// Parameters:
//    pTaskInfo           - Pointer to associated task block.
//    fileID              - File pointer.
// Returns:
//    uint8_t             - Indicates SUCCESS of FAILure of transaction.
//                          It does NOT indicate when the file transfer is complete!
//
uint8_t OS_ExtBatteries_QueueFile(POSEXTBATT_TASK pTaskInfo, uint32_t fileID)
{
  uint8_t retval = FAIL;
  PFS_REPORT pReport;
  uint8_t err = 0;
  tm* dateTime;
  time_t time;

  OSMutexPend(pMutexFloatQueue, INFINITE, &err);

  pReport = (PFS_REPORT)&(txFloatData[headFloatTx]);

  memset(pReport, 0, sizeof(FS_REPORT));

  pReport->headers.length  = sizeof(FS_REPORT) + CRC16_SIZE;
  pReport->headers.destination = FLOAT_FS_INTERFACE;
  pReport->type = NEW_FILE_AVAILABLE;

  time=gettime();

  //Need to fill in these values appropriately
  pReport->newFile.fileSize = fileOutSize[pTaskInfo->portNum];
  dateTime = gmtime(&time);
  pReport->newFile.creationDate.day = dateTime->tm_mday;
  pReport->newFile.creationDate.hour = dateTime->tm_hour;
  pReport->newFile.creationDate.minute = dateTime->tm_min;
  pReport->newFile.creationDate.month = dateTime->tm_mon + 1;
  pReport->newFile.creationDate.second = dateTime->tm_sec;
  pReport->newFile.creationDate.year = (uint8_t)(dateTime->tm_year - 100);
  dateTime = gmtime(&time);
  pReport->newFile.modifiedDate.day = dateTime->tm_mday;
  pReport->newFile.modifiedDate.hour = dateTime->tm_hour;
  pReport->newFile.modifiedDate.minute = dateTime->tm_min;
  pReport->newFile.modifiedDate.month = dateTime->tm_mon + 1;
  pReport->newFile.modifiedDate.second = dateTime->tm_sec;
  pReport->newFile.modifiedDate.year = (uint8_t)(dateTime->tm_year - 100);

  pReport->newFile.fileAttributes = 0;
  memset(&pReport->newFile.calculatedMD5, 0, sizeof(MD5));
  memset(&pReport->newFile.providedMD5, 0, sizeof(MD5));
  pReport->newFile.calculatedCRC = 0;
  pReport->newFile.providedCRC = 0;
  pReport->newFile.storageControllerAddress = mainTaskAddress | pTaskInfo->portNum;

  char fileName[13];
  snprintf_PSTR(fileName, 13, "%.8lX.txt", fileID);

  DotToPad(pReport->newFile.fileName, fileName);

  (headFloatTx < (MAX_FLOAT_BUFFERS - 1)) ? (headFloatTx++) : (headFloatTx = 0);

  if(headFloatTx == tailFloatTx)
  {
    (tailFloatTx < (MAX_FLOAT_BUFFERS - 1)) ? (tailFloatTx++) : (tailFloatTx = 0);
  }

  OSMutexPost(pMutexFloatQueue);

  return retval;
} // OS_ExtBatteries_QueueFile


// ................................................................................................
// Obtain a character string from the attached device.
//  This routine will be captive for the task calling it.
//  Read characters until one of the following conditions occurs:
//    - 'term_char' or '\0' character is read;
//    - 'max_char' is reached;
//    - 'time_out_tick' is exceeded.
//  The termination character is placed into the string, plus a NUL character after that to make
//    sure that there is a string terminator.
//
// Parameters:
//    pstring             - Pointer to destination string.
//    term_char           - String termination character.
//    max_char            - Maximum number of characters allowed in string.
//    time_out_ticks      - Maximum time to wait for termination character.
// Returns:
//    uint8_t             - Count of characters placed into the string.
//
uint8_t OS_ExtBatteries_GetString(char *pstring, uint8_t port_num, char term_char, uint8_t max_char, uint16_t time_out_ticks)
{
  uint8_t err = 0;
  uint8_t achar;
  uint8_t idx = 1;
  int32_t timeout = (int32_t)(OSTimeGet() + time_out_ticks);

  do
  {
    achar = OSuartGetCharWait(port_num, 1, &err);
    if (err == OS_ERR_NONE)
    {
      *pstring++ = achar;
      idx++;
      // Look for response termination.
      if ((achar == 0) || (achar == term_char))
        break;
    }
  } while (((timeout - (int32_t)OSTimeGet()) >= 0) && (idx < max_char));
  *pstring++ = 0;                               // Always terminate string.
  return idx - 1;
} // OS_ExtBatteries_GetString
