/**
  ******************************************************************************
  * @file    RSTi_ThermArray.c
  * @author  C. Otten
  * @version V0.0.0
  * @date    03/25/2011
  * @brief   RSTi Thermistor Array
  ******************************************************************************
  *
  * COPYRIGHT 2011 Liquid Robotics, Inc.
  **/

#include "includes.h"
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/power.h>
//#include "swversion.h"

#include "ff.h"
#include "mmc_uspi_conf.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 "RSTi_thermarray.h"
#include "TASDCard.h"
#include "TADebug.h"

#define RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE   0x0000
#define RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE   0x0001

#define RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE   0x0002
#define RTSiTA_EEPROM__SIZE__COLLECTIONSIZE   0x0001

#define RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL   0x0004
#define RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL   0x0001

#define RTSiTA_EEPROM_OFFSET_NODE_SN          0x0020
#define RTSiTA_EEPROM__SIZE__NODE_SN          0x02

#define MAX_COLLECTION_SIZE                   MAX_RSTI_THERMARRAY_SAMPLES
#define MAX_COLLECTION_SAMPLE_INTERVAL        60    // In number of base sample time. (Currently set to one hour.)

#define MAX_PERSONALITY_MODULES				3

typedef struct _RSTI_THERMARRAY_DATA_REPORT_XMIT_
{                                               // Binary data report packet.
  PIB_IRIDIUM_DATA_XMIT         comm;
  RSTI_THERMARRAY_DATA_REPORT   reportData;     // A single type 0x91 packet containing from one to
                                                //  many (10) sets of data points.
}RSTI_THERMARRAY_DATA_REPORT_XMIT, *PRSTI_THERMARRAY_DATA_REPORT_XMIT;

BOOL                    RSTi_Thermarray_GetNodeData(PRSTI_THERMARRAY_TASK pTaskInfo, uint8_t node_idx);
void                    RSTi_Thermarray_GetDataSample(PRSTI_THERMARRAY_TASK pTaskInfo);
void                    RSTi_Add2Collection(PRSTI_THERMARRAY_TASK pTaskInfo, BOOL force);
void                    RSTi_SendCollection(PRSTI_THERMARRAY_TASK pTaskInfo, BOOL force);
uint8_t                 RSTi_Thermarray_DataReportXmitType91(PRSTI_THERMARRAY_TASK pTaskInfo);
uint8_t                 RSTi_Thermarray_ParseFile(PRSTI_THERMARRAY_TASK pTaskInfo, uint32_t fileID);
uint8_t                 RSTi_Thermarray_QueueFile(PRSTI_THERMARRAY_TASK pTaskInfo, uint32_t fileID);
uint8_t                 RSTi_GetString(char *pstring, uint8_t port_num, char term_char, uint8_t max_char, uint16_t time_out_ticks);

static char*            ThreadSafeStrTok(char** context, char* sep);

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];
extern uint16_t         fileInSize[NUM_TASK_FILES];
extern BOOL             fileAvailable[NUM_TASK_FILES];
extern uint32_t         fileAvailableID[NUM_TASK_FILES];
extern BOOL             transmitPending[NUM_TASK_FILES];

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;

// ................................................................................................
// Initialize the Thermarray 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 RSTi_Thermarray_Init(PRSTI_THERMARRAY_TASK pTaskInfo)
{
  uint8_t     retval = SUCCESS;
  uint16_t    offset;
  uint16_t    temp;
  uint8_t     err;
  PRSTI_THERMARRAY_BUFFER pBuf = pTaskInfo->pBuf;
  TA_Node_s   *pNode;

  if(pTaskInfo == NULL || pBuf == NULL)
    return FALSE;                               // No pointers to task related data memory. Fail.

  USPI_Device_Disable(0);                       // Make sure power is off before starting. The SD Card
  USPI_Device_Disable(1);                       // Sometimes has trouble restarting if the power was
                                                // not completely drained.
  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_RSTI_TMP_STR], 0, &err);
  pTaskInfo->pBuf->rsti_tmp_str = OSMemGet(mem_part_ptr[INDEX_RSTI_TMP_STR], &err);

  pBuf->baseAddr = pgm_read_word(&(eeprom_pm_start[pTaskInfo->portNum])); // Determine where task related EEPROM is located.
  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.

  OSuartInitPort(pTaskInfo->portNum, 115200, 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 (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 1)
  EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                 (void*)&temp);
  temp &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 2)
  EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_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 (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 1)
  EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                 (void*)&temp);
  temp &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 2)
  EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                 (void*)&temp);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
  pBuf->collection_size = temp;
  if (pBuf->collection_size > MAX_COLLECTION_SIZE)
    pBuf->collection_size = MAX_COLLECTION_SIZE;

#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 1)
  EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                 (void*)&temp);
  temp &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 2)
  EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                 (void*)&temp);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
  pBuf->collection_sample_interval = temp;
  if (pBuf->collection_sample_interval > MAX_COLLECTION_SAMPLE_INTERVAL)
    pBuf->collection_sample_interval = MAX_COLLECTION_SAMPLE_INTERVAL;

  offset = RTSiTA_EEPROM_OFFSET_NODE_SN;
  for (uint8_t idx = 0; idx < NUM_TA_NODES; idx++)
  {
    pNode = &(pBuf->node[idx]);
    // Read the serial number for each node.
#if (RTSiTA_EEPROM__SIZE__NODE_SN == 1)
    EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + offset), (void*)&temp);
    temp &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__NODE_SN == 2)
    EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + offset), (void*)&temp);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif

    // Recorded serial numbers 0 & -1 are not valid, and indicate that this node has not been
    //  defined.
    pNode->active = !((temp == 0xFFFF) || (temp == 0x0000));
    pNode->serialnum = temp;
    pNode->valid = FALSE;                       // Haven't read any data yet.
    offset += RTSiTA_EEPROM__SIZE__NODE_SN;
  }

  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.

  OSTimeDlyHMSM(0,0,2,0);                       // Drain power from SD Card before initialization.
  SDCard_Init(pTaskInfo, 0);                    // Mount and create data file on SD Card.
  SDCard_Init(pTaskInfo, 1);                    // Its result status will be reported in the device variables.
  OSTimeDlyHMSM(0,0,2,0);                       // Delay so that any start up string can be digested by
                                                //  the debug task.

  return retval;
} // RSTi_Thermarray_Init

// ================================================================================================
// ================================================================================================
// ................................................................................................
// Master RSTi Thermistor Array sensor acquisition task.
//  Handle gathering of data and commands sent down from WGMS via a file transfer (not SD Card file).
// Parameters:
//    param               - Pointer to task parameters.
// Returns:
//    Nothing.
//
extern uint8_t  ta_busy;
static int32_t  next_sample_time;               // Time tracking.
void RSTi_Thermarray(void* pParam)              // TASK TASK TASK TASK TASK TASK TASK TASK TASK TASK
{
  RSTI_THERMARRAY_TASK  taskInfo;
  PRSTI_THERMARRAY_TASK pTaskInit = (PRSTI_THERMARRAY_TASK)pParam;
  uint8_t               retval = FAIL;

  taskInfo.taskID   = pTaskInit->taskID;
  taskInfo.portNum  = pTaskInit->portNum;
  taskInfo.pBuf     = pTaskInit->pBuf;

  retval = RSTi_Thermarray_Init(&taskInfo);

  if(retval == SUCCESS)
  {
TADEBUG_STR("Therm. Array task start\r", 100, &retval);
    taskInfo.pBuf->sample_count = 0;            // Haven't collected any data yet.
    taskInfo.pBuf->sample_time  = 0;            // First sample will be part of the collection, regardless.
    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)
        {
          next_sample_time = OSTimeGet() + 60 * OS_TICKS_PER_SEC; // Fixed sample rate of once per minute.
          RSTi_Thermarray_GetDataSample(&taskInfo);

          SDCard_Save(&taskInfo, 0);              // Save every sample to SD Card.
          SDCard_Save(&taskInfo, 1);              // Save every sample to the other SD Card.
          RSTi_Add2Collection(&taskInfo, FALSE);  // Only some samples get added to collection.
          RSTi_SendCollection(&taskInfo, FALSE);  // When collection is full, send to shore.
        }

      if(fileAvailable[pTaskInit->portNum] == TRUE)
      {
        // Communication from the Float has been received. See what it wants.
        RSTi_Thermarray_ParseFile(&taskInfo, fileAvailableID[pTaskInit->portNum]);
        fileAvailableID[pTaskInit->portNum] = 0;
        fileAvailable[pTaskInit->portNum] = FALSE;
      }
    } // for
  }
  return;
} // RSTi_Thermarray (Task)
// ================================================================================================
// ================================================================================================

// ................................................................................................
// Retrieve a sample from a (one) node.
//  Allow it enough time, and retry a few times if the data seems to be slow in returning.
// 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.
//
BOOL RSTi_Thermarray_GetNodeData(PRSTI_THERMARRAY_TASK pTaskInfo, uint8_t node_idx)
{
  TA_Node_s   *pNode = &pTaskInfo->pBuf->node[node_idx];
  uint8_t     err = 0;
  int         tmp_i;
  char        *tmp_str;
  char        *context;
  char        *cmdTok;
  char        sep[] = {' ', ','};
  BOOL        result = FALSE;

SETTO1_12;
  tmp_str = 0;                                  // Show that no memory partition was allocated.
  if (pNode->active == 0)
    goto exit_GND;                              // Not an active node. Nothing to get.

  OSSemPend(mem_sem_ptr[MEM_PART_100], 0, &err);
  tmp_str = OSMemGet(mem_part_ptr[MEM_PART_100], &err);

  // Flush pipeline in case of any strays. Don't like, but there is no completely documented protocol.
  //  If it doesn't respond correctly, there is no way telling how that response will look.
  OSuartFlushBuffers(pTaskInfo->portNum, UART_RX_BUFFER | UART_TX_BUFFER);

//Send string: TTT <devaddr><\r>
  snprintf_PSTR(tmp_str, MEM_PART_SIZE_100, "TTT %d\r", pNode->serialnum);
  OSuartPutStrWait(pTaskInfo->portNum, tmp_str, 100, &err);
if (err != OS_ERR_NONE)
result = FALSE;

//Read what is returned--s/b what was just sent. (Don't care for now.)
  RSTi_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (200L * OS_TICKS_PER_SEC / 1000));

//wait > ~400ms
  OSTimeDly((uint16_t)(400L * OS_TICKS_PER_SEC / 1000));

//Send string: GTT <devaddr><\r>
  snprintf_PSTR(tmp_str, MEM_PART_SIZE_100, "GTT %d\r", pNode->serialnum);
  OSuartPutStrWait(pTaskInfo->portNum, tmp_str, 100, &err);

//do
//  Read reply, which should be: GTT <devaddr>, <temperature>, <resistance>, <AD reading><\r>
//until a) Returned string does not start with a <?>
//      b) ??Numbers don't make sense?? Whatever that means.
  RSTi_GetString(tmp_str, pTaskInfo->portNum, 0x0A, MEM_PART_SIZE_100-1, (uint16_t)(200L * OS_TICKS_PER_SEC / 1000));
  context = tmp_str;
  cmdTok = ThreadSafeStrTok(&context, sep);     // Expect "GTT"
  if (strcmp(cmdTok, "GTT") != 0)
    goto exit_GND;
  cmdTok = ThreadSafeStrTok(&context, sep);     // Expect node's serial number.
  tmp_i = atoi(cmdTok);
  if (tmp_i != pNode->serialnum)
    goto exit_GND;
  cmdTok = ThreadSafeStrTok(&context, sep);     // Temperature.
  tmp_i = (int16_t)(strtol(cmdTok, &cmdTok, 10) * 100);         // Degrees (signed).
  cmdTok++;
  tmp_i += (int16_t)((strtol(cmdTok, &cmdTok, 10) + 5) / 10);   // Fractional degrees.
  pNode->temp = tmp_i;                          // Read temperature as is.
  cmdTok = ThreadSafeStrTok(&context, sep);     // Resistance.
  tmp_i = atoi(cmdTok);                         // Ignoring the value after the decimal point!
  if (tmp_i == 0)                               // Zero means data not ready.
    goto exit_GND;
  cmdTok = ThreadSafeStrTok(&context, sep);     // AD reading.
  tmp_i = atoi(cmdTok);
  if (tmp_i == 0)                               // Zero means data not ready.
    goto exit_GND;
  result = TRUE;                                // Returned data reflects something reasonable.

exit_GND:
  pNode->valid = result;
  if (pNode->active == 0)
    pNode->temp = 0x7FFF;
  else if (pNode->valid == 0)
    pNode->temp = 0x8000;
  if (tmp_str)
  {
    OSMemPut(mem_part_ptr[MEM_PART_100], tmp_str);
    OSSemPost(mem_sem_ptr[MEM_PART_100]);
  }
SETTO0_12;
  return result;
} // RSTi_Thermarray_GetNodeData

// ................................................................................................
// Obtain a set of data from the Thermistor Array.
//  This routine will collect a temperature sample for each node and store it in the 'node' array.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
// Returns:
//    Nothing.
//
void RSTi_Thermarray_GetDataSample(PRSTI_THERMARRAY_TASK pTaskInfo)
{
  uint8_t idx;
#ifdef  _DEBUG_PORT
  uint8_t err;
#endif // _DEBUG_PORT

  ta_busy = 1;                                  // Tell Debug that we are talking to TA module,
                                                //  and not to butt in.
  //Clear data for all nodes.
  for (idx = 0; idx < NUM_TA_NODES; idx++)
  {
    pTaskInfo->pBuf->node[idx].valid = FALSE;
    pTaskInfo->pBuf->node[idx].temp  = 0;
  }

  //For each node, get data.
  for (idx = 0; idx < NUM_TA_NODES; idx++)
    RSTi_Thermarray_GetNodeData(pTaskInfo, idx);
  pTaskInfo->pBuf->timestamp = (uint32_t)gettime();
  pTaskInfo->pBuf->latitude  = latitude;
  pTaskInfo->pBuf->longitude = longitude;

#if NUM_TA_NODES >= 1                           // Report sample via debug port.
TADEBUG_STR("\n", 100, &err); // For lining up nicer.
snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
              "time: % 11ld, lat: % 11ld, long: % 11ld, ",
              pTaskInfo->pBuf->timestamp,
              pTaskInfo->pBuf->latitude,
              pTaskInfo->pBuf->longitude);
TADEBUG_STR(pTaskInfo->pBuf->rsti_tmp_str, 100, &err);
for (idx = 0; idx < NUM_TA_NODES; idx++)
{
  snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                "t%02d:% 7d ", idx, pTaskInfo->pBuf->node[idx].temp);
  TADEBUG_STR(pTaskInfo->pBuf->rsti_tmp_str, 100, &err);
}
TADEBUG_STR("\n", 100, &err);

/*
// Output in hex to ease verification with WGMS.
snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
              "time:    %08lx, lat:    %08lx, long:    %08lx, ",
              pTaskInfo->pBuf->timestamp,
              pTaskInfo->pBuf->latitude,
              pTaskInfo->pBuf->longitude);
TADEBUG_STR(pTaskInfo->pBuf->rsti_tmp_str, 100, &err);
for (idx = 0; idx < NUM_TA_NODES; idx++)
{
  snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                "t%02d:   %04x ", idx, pTaskInfo->pBuf->node[idx].temp);
  TADEBUG_STR(pTaskInfo->pBuf->rsti_tmp_str, 100, &err);
}
TADEBUG_STR("\n", 100, &err);
*/
#endif

  ta_busy = 0;                           // Tell Debug that we are done talking to PL module.
} // RSTi_Thermarray_GetDataSample

// ................................................................................................
// Add current node data set to the sample collection when it is time.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
//    force               - If TRUE, force this sample into the collection, but do not change interval
//                            count.
//                          Else, add the sample only when it is time.
// Returns:
//    Nothing.
//
void RSTi_Add2Collection(PRSTI_THERMARRAY_TASK pTaskInfo, BOOL force)
{
  uint8_t idx, c_idx;

  if (!force)
    // Only update if not forced.
    --pTaskInfo->pBuf->sample_time;
  if (force || (pTaskInfo->pBuf->sample_time <= 0))
  {
    c_idx = pTaskInfo->pBuf->sample_count;
    //  Fill in data header. (lat, long, and time.)
    pTaskInfo->pBuf->reportData.dataBlock[c_idx].headerData.timestamp = pTaskInfo->pBuf->timestamp;
    pTaskInfo->pBuf->reportData.dataBlock[c_idx].headerData.latitude  = pTaskInfo->pBuf->latitude;
    pTaskInfo->pBuf->reportData.dataBlock[c_idx].headerData.longitude = pTaskInfo->pBuf->longitude;

    //  Copy sample data.
    for (idx = 0; idx < NUM_TA_NODES; idx++)
      // Node data already reflects inactive and invalid conditions.
      pTaskInfo->pBuf->reportData.dataBlock[c_idx].data_sample.degreesx100[idx] = pTaskInfo->pBuf->node[idx].temp;
    pTaskInfo->pBuf->sample_count++;
    pTaskInfo->pBuf->sample_time = pTaskInfo->pBuf->collection_sample_interval;
TADEBUG_STR("Added sample to collection.\n", 100, &idx);
  }
} // RSTi_Add2Collection

// ................................................................................................
// Send the collection to shore when it is time.
//
// Parameters:
//    pTaskInfo           - Pointer to task specific data.
//    force               - If TRUE, force this collection to shore, but only if it contains data.
//                          Else, send to shore only when it is full.
// Returns:
//    Nothing.
//
void RSTi_SendCollection(PRSTI_THERMARRAY_TASK pTaskInfo, BOOL force)
{
#ifdef  _DEBUG_PORT
  uint8_t err;
#endif // _DEBUG_PORT

  if ((force && (pTaskInfo->pBuf->sample_count > 0)) || (pTaskInfo->pBuf->sample_count >= pTaskInfo->pBuf->collection_size))
  {
    if (pTaskInfo->pBuf->sample_count > pTaskInfo->pBuf->collection_size)
    {
      // Got a problem here, but can't do much about it. Limit its effect by setting an upper limit.
      TADEBUG_STR("SERIOUS PROBLEM HERE! Sample count too large.\n", 100, &err);
      pTaskInfo->pBuf->sample_count = pTaskInfo->pBuf->collection_size;
    }
    // Put the 0x91 packet header together.
    pTaskInfo->pBuf->reportData.headerReport.payloadType  = 0x000000C0;   // ?TBD Once defined, fixed forever.
    pTaskInfo->pBuf->reportData.headerReport.boardID      = mainBoardID;
    pTaskInfo->pBuf->reportData.headerReport.taskID       = pTaskInfo->taskID;
    pTaskInfo->pBuf->reportData.headerReport.dataFormat   = 9;            // ?Not == 0. 0 is "reserved" for self identification.
    pTaskInfo->pBuf->reportData.headerReport.parser       = 0;            // Binary.
    pTaskInfo->pBuf->reportData.headerReport.priority     = 2;            // ?Not yet implemented, but probably correct for "data" type packet.
    pTaskInfo->pBuf->reportData.headerReport.repeatCount  = pTaskInfo->pBuf->sample_count;

    RSTi_Thermarray_DataReportXmitType91(pTaskInfo);  // Send it on its way.

    // Reset the buffer and count. All's gone.
    memset(&(pTaskInfo->pBuf->reportData), 0, sizeof(RSTI_THERMARRAY_DATA_REPORT));
    pTaskInfo->pBuf->sample_count = 0;
TADEBUG_STR("Sent collection to shore.\n", 100, &err);
  }
} // RSTi_SendCollection

// ................................................................................................
// 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 RSTi_Thermarray_DataReportXmitType91(PRSTI_THERMARRAY_TASK pTaskInfo)
{
  uint8_t retval = 0;
  uint8_t err = 0;
  PRSTI_THERMARRAY_DATA_REPORT_XMIT pReport = NULL;

  OSMutexPend(pMutexFloatQueue, INFINITE, &err);

  pReport = (PRSTI_THERMARRAY_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(RSTI_THERMARRAY_DATA_REPORT_XMIT) + CRC16_SIZE -
                                  (sizeof(RSTI_THERMARRAY_DATA_REPORT_BLOCK) *
                                    (MAX_RSTI_THERMARRAY_SAMPLES - pTaskInfo->pBuf->reportData.headerReport.repeatCount));
  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->reportData), &(pTaskInfo->pBuf->reportData), sizeof(RSTI_THERMARRAY_DATA_REPORT) -
                                  (sizeof(RSTI_THERMARRAY_DATA_REPORT_BLOCK) *
                                    (MAX_RSTI_THERMARRAY_SAMPLES - pTaskInfo->pBuf->reportData.headerReport.repeatCount)));

  (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 tmp_b, *pbyte;
pbyte = (uint8_t*)pReport;
for (uint16_t idx = 0; idx < pReport->comm.headers.length; idx++)
{
  tmp_b = (*pbyte >> 4) & 0x0F;
  if (tmp_b >= 0 && tmp_b <= 9) tmp_b |= 0x30;
  else tmp_b += (0x41 - 10);
  TADEBUG_CHR(tmp_b, 0);
  tmp_b = *pbyte++ & 0x0F;
  if (tmp_b >= 0 && tmp_b <= 9) tmp_b |= 0x30;
  else tmp_b += (0x41 - 10);
  TADEBUG_CHR(tmp_b, 0);
} // for
TADEBUG_CHR('\n', 0);

  return retval;
} // RSTi_Thermarray_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;
}

// ................................................................................................
// 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 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)

#define SUCCESSCMD  (SUCCESS + 10)
uint8_t ta_busy;
uint8_t RSTi_Thermarray_ParseFile(PRSTI_THERMARRAY_TASK pTaskInfo, uint32_t fileID)
{
  uint8_t     retval = FAIL;
  char*       pBuf;
  uint8_t     err = 0;
  uint8_t     idx;
  uint8_t     nodenum_min, nodenum_max;
  uint16_t    offset;
  int16_t     tmp_i;
  int16_t     tmp2_i;
  BOOL        tmp_b;
  uint8_t     cmd, scmd, acmd;                  // Command and sub-command flag bytes.

  ta_busy = 1;                                  // Tell Debug that we are busy.
  memset(&(fileOut[pTaskInfo->portNum][0]), 0, MAX_FILE_SIZE_OUT);
  fileOutSize[pTaskInfo->portNum] = 0;          // MUST output a file prior to exit!

  // Convert all characters to lower case, since that's what the PowerLoad expects.
  strlwr(&fileIn[pTaskInfo->portNum][0]);
  pBuf = &fileIn[pTaskInfo->portNum][0];

  char* context = pBuf;
snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR, "%s\n", pBuf);
TADEBUG_STR(pTaskInfo->pBuf->rsti_tmp_str, 100, &err);
  char sep[] = { ' ', '\t', '\r', '\n', '\0' };
  char* cmdTok = ThreadSafeStrTok(&context, sep);

       if ((cmd  = (strncmp(cmdTok, "node",  4) == 0) ? CMD_NODE   : 0)) ;
  else if ((cmd  = (strncmp(cmdTok, "sampl", 5) == 0) ? CMD_SAMPL  : 0)) ;
  else if ((cmd  = (strncmp(cmdTok, "sys",   3) == 0) ? CMD_SYST   : 0)) ;
  else if ((cmd  = (strcmp( cmdTok, "cmd:"    ) == 0) ? CMD_CMD    : 0)) ;
  else goto exit_TPF;

  if (cmd == CMD_NODE)
  {
    cmdTok = ThreadSafeStrTok(&context, sep);
    if (strcmp(cmdTok, "all") == 0)             // Look for node number.
    {
      nodenum_min = 0;                          // Span across all nodes.
      nodenum_max = NUM_TA_NODES-1;
    }
    else
    {
      nodenum_min = atoi(cmdTok);               // Whatever is specified, it must be one that is
      nodenum_max = nodenum_min;                //  allowed by the code.
      if (nodenum_max >= NUM_TA_NODES)
        goto exit_TPF;
    }
  }

  scmd = 0;
  if (cmd == CMD_NODE || 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)) ;
  }

// NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE NODE
  if (cmd == CMD_NODE)
  {
    // Service the 'node' command. This command may be used to set and read individual or grouped
    //  node data, such as serial number, state, and last read value. Format is:
    //    node <n#>|all set|get|program|read sn|serial [<s#> [<s#>..<s#>]]
    //    node <n#>|all set|get              state     [a|i|active|inactive[ a|i|active|inactive...]]
    //    node <n#>|all get oldtemp
    //    node <n#>|all get newtemp
    //      where:  <n#> is the node number, from 0 to number of nodes -1.
    //              <s#> is the decimal value of the 16-bit node serial number.
    if (scmd != 0)
    // Previous token was taken by a sub-command. Not always the case.
      cmdTok = ThreadSafeStrTok(&context, sep);
    if ((strcmp( cmdTok, "sn"       ) == 0) ||
        (strncmp(cmdTok, "serial", 6) == 0))
    {
      if ((scmd == SCMD_PROG) || (scmd == SCMD_READ))
      {
        if (pTaskInfo->pBuf->baseAddr == 0xFFFF)
          goto exit_TPF;                        // No place in EEPROM specified.
        offset = RTSiTA_EEPROM_OFFSET_NODE_SN + RTSiTA_EEPROM__SIZE__NODE_SN * nodenum_min;
      }

      if ((scmd == SCMD_GET) || (scmd == SCMD_READ))
      {
        if (scmd == SCMD_GET)
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("In use serial number(s):"), 24);
          tmp_i = 24;
        }
        else
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Stored serial number(s):"), 24);
          tmp_i = 24;
        }
        for (idx = nodenum_min; idx <= nodenum_max; idx++)
        {
          if (scmd == SCMD_GET)
            tmp2_i = pTaskInfo->pBuf->node[idx].serialnum;
          else
          {
#if (RTSiTA_EEPROM__SIZE__NODE_SN == 1)
            EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + offset), (void*)&tmp2_i);
            tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__NODE_SN == 2)
            EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + offset), (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
            offset += RTSiTA_EEPROM__SIZE__NODE_SN;
          }
          snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                        " n%d=%d", idx, tmp2_i);
          memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
          tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
        }
        fileOutSize[pTaskInfo->portNum] = tmp_i;
        retval = SUCCESS;
      }
      else if ((scmd == SCMD_SET) || (scmd == SCMD_PROG))
      {
        for (idx = nodenum_min; idx <= nodenum_max; idx++)
        {
          cmdTok = ThreadSafeStrTok(&context, sep);
          if (cmdTok[0] != 0)
          {
            // Change only those that have a non-empty string. Could still set it to 0x0000 or
            //  0xFFFF. But that's a check for some other time. Local value update only.
            tmp_i = atoi(cmdTok);
            if (scmd == SCMD_SET)
              pTaskInfo->pBuf->node[idx].serialnum = tmp_i;
            else
            {
              EepromWriteWordSigned((void*)(pTaskInfo->pBuf->baseAddr + offset), tmp_i);
              offset += RTSiTA_EEPROM__SIZE__NODE_SN;
            }
          }
        } // for
        retval = SUCCESSCMD;
      }
    } // if (serial)
    else if (strncmp(cmdTok, "st", 2) == 0)     // state
    {
      if (scmd == SCMD_GET)
      {
        memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Node use state:"), 15);
        tmp_i = 15;
        for (idx = nodenum_min; idx <= nodenum_max; idx++)
        {
          snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                        " n%d=%s", idx, pTaskInfo->pBuf->node[idx].active ? "active" : "inactive");
          memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
          tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
        }
        fileOutSize[pTaskInfo->portNum] = tmp_i;
        retval = SUCCESS;
      }
      else if (scmd == SCMD_SET)
      {
        for (idx = nodenum_min; idx <= nodenum_max; idx++)
        {
          cmdTok = ThreadSafeStrTok(&context, sep);
          if ((acmd = (strcmp( cmdTok, "a"       ) == 0) ? ACMD_1 : 0) ||
              (acmd = (strncmp(cmdTok, "acti",  4) == 0) ? ACMD_1 : 0) ||
              (acmd = (strcmp( cmdTok, "i"       ) == 0) ? ACMD_2 : 0) ||
              (acmd = (strncmp(cmdTok, "inact", 5) == 0) ? ACMD_2 : 0))
          {
            // Only change those that specifically specify active or inactive.
            pTaskInfo->pBuf->node[idx].active = (acmd == ACMD_1);
          }
        }
        retval = SUCCESSCMD;
      }
//      else
//        goto exit_TPF;
    } // if (state)
    else if (((acmd = (strncmp(cmdTok, "old", 3) == 0) ? ACMD_1 : 0) ||
              (acmd = (strncmp(cmdTok, "new", 3) == 0) ? ACMD_2 : 0)) &&
               (scmd == SCMD_GET))
    {
      if (acmd == ACMD_2)
      {
        memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Node's temperature:"), 19);
        tmp_i = 19;
      }
      else
      {
        memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Node's last read temperature:"), 29);
        tmp_i = 29;
      }
      for (idx = nodenum_min; idx <= nodenum_max; idx++)
      {
        if (pTaskInfo->pBuf->node[idx].active == FALSE)
          snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                        " n%d=inactive", idx);
        else
        {
          if (acmd == ACMD_2)
            // Require new value. Go get it.
            RSTi_Thermarray_GetNodeData(pTaskInfo, idx);
          if (pTaskInfo->pBuf->node[idx].valid)
            snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                          " n%d=%d", idx, pTaskInfo->pBuf->node[idx].temp);
          else
            snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                          " n%d=not valid", idx);
        }
        memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
        tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
      }
      fileOutSize[pTaskInfo->portNum] = tmp_i;
      retval = SUCCESS;
    } // if (old|new/sample|temp)
  } // if (node)

// SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE SAMPLE
  else 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 <#>
    //    sampl set|get|program|read c|cs|collec/tionsize <#>
    //    sampl force
    //      where:  <t#> is the (approximate) loop time in seconds.
    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.
    {
      tmp_i = 0;
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 1)
      EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                     (void*)&tmp2_i);
      tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 2)
      EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                     (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
      snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                    "Sampling:   Default = %s, Current = %s.\n",
                    tmp2_i                            ? "active" : "inactive",
                    pTaskInfo->pBuf->sampling_active  ? "active" : "inactive");
      memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
      tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
      // --
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 1)
      EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                     (void*)&tmp2_i);
      tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 2)
      EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                     (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
      snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                    "Interval:   Default =% 5d, Current =% 5d. Time to next collection sample: %d minutes.\n",
                    tmp2_i,
                    pTaskInfo->pBuf->collection_sample_interval,
                    pTaskInfo->pBuf->sample_time);
      memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
      tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
      // --
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 1)
      EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                     (void*)&tmp2_i);
      tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 2)
      EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                     (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
      snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                    "Collection: Default =% 5d, Current =% 5d. Entries so far: %d.\n",
                    tmp2_i,
                    pTaskInfo->pBuf->collection_size,
                    pTaskInfo->pBuf->sample_count);
      memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
      tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
      // --
      fileOutSize[pTaskInfo->portNum] = tmp_i;
      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(&fileOut[pTaskInfo->portNum][0], PSTR("Active sampling state: "), 22);
          tmp_i = 22;
          tmp_b = pTaskInfo->pBuf->sampling_active;
        }
        else
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Startup sampling state: "), 23);
          tmp_i = 23;
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 1)
          EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                         (void*)&tmp2_i);
          tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 2)
          EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                         (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
        }
        if (tmp2_i)
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][tmp_i], PSTR("sampling\n"), 9);
          tmp_i += 9;
        }
        else
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][tmp_i], PSTR("idle\n"), 5);
          tmp_i += 5;
        }
        fileOutSize[pTaskInfo->portNum] = tmp_i;
        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;    // Haven't collected any data yet.
          pTaskInfo->pBuf->sample_time  = 0;    // First sample will be part of the collection, regardless.
          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.
          RSTi_SendCollection(pTaskInfo, TRUE); // Send collection to shore.
          retval = SUCCESSCMD;
        }
//        goto exit_TPF;
      }
      else if (scmd == SCMD_PROG)
      {
        tmp_b = (acmd == ACMD_1);               // TRUE if "on".
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 1)
        EepromWriteByteUnSigned((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                                (uint8_t)tmp_b);
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLINGACTIVE == 2)
        EepromWriteWordUnSigned((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLINGACTIVE),
                                (uint16_t)tmp_b);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
        retval = SUCCESSCMD;
      }
//      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(&fileOut[pTaskInfo->portNum][0], PSTR("Active"), 6);
          tmp_i = 6;
          tmp2_i = pTaskInfo->pBuf->collection_sample_interval;
        }
        else
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Startup"), 7);
          tmp_i = 7;
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 1)
          EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                         (void*)&tmp2_i);
          tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 2)
          EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                         (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
        }
        snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                      " collection interval: %d minutes.\n", tmp2_i);
        memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
        tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
        fileOutSize[pTaskInfo->portNum] = tmp_i;
        retval = SUCCESS;
      }
      else if ((scmd == SCMD_SET) || (scmd == SCMD_PROG))
      {
        cmdTok = ThreadSafeStrTok(&context, sep);
        tmp_i = atoi(cmdTok);
        if (tmp_i)
        {
          // Not allowed to set to zero. It's not.
          if (scmd == SCMD_SET)
          {
            pTaskInfo->pBuf->collection_sample_interval = tmp_i;
            pTaskInfo->pBuf->sample_time = 0;   // Write next sample, regardless of where we are. Then start new cycle.
          }
          else
          {
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 1)
            EepromWriteByteUnSigned((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                                    (uint8_t)tmp_i);
#else
#if (RTSiTA_EEPROM__SIZE__SAMPLEINTERVAL == 2)
            EepromWriteWordUnSigned((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_SAMPLEINTERVAL),
                                    (uint16_t)tmp_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
          }
          retval = SUCCESSCMD;
        }
      }
    }
    else if ((strcmp( cmdTok, "c" )         == 0) ||
             (strcmp( cmdTok, "cs")         == 0) ||
             (strncmp(cmdTok, "collec", 6)  == 0))
    {
      if ((scmd == SCMD_GET) || (scmd == SCMD_READ))
      {
        if (scmd == SCMD_GET)
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Active"), 6);
          tmp_i = 6;
          tmp2_i = pTaskInfo->pBuf->collection_size;
        }
        else
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Startup"), 7);
          tmp_i = 7;
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 1)
          EepromReadByte((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                         (void*)&tmp2_i);
          tmp2_i &= 0xFF;
#else
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 2)
          EepromReadWord((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                         (void*)&tmp2_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
        }
        if (tmp2_i == 1)
        {
          memcpy_P(&fileOut[pTaskInfo->portNum][tmp_i], PSTR(" collection size: 1 entry.\n"), 27);
          tmp_i += 27;
        }
        else
        {
          snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                        " collection size: %d entries.\n", tmp2_i);
          memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
          tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
        }
        fileOutSize[pTaskInfo->portNum] = tmp_i;
        retval = SUCCESS;
      }
      else if ((scmd == SCMD_SET) || (scmd == SCMD_PROG))
      {
        cmdTok = ThreadSafeStrTok(&context, sep);
        tmp_i = atoi(cmdTok);
        if (tmp_i)
        {
          // Not allowed to set to zero. It's not.
          if (scmd == SCMD_SET)
          {
            // Force collection to be sent to WGMS.
            pTaskInfo->pBuf->sample_count = pTaskInfo->pBuf->collection_size;
            RSTi_SendCollection(pTaskInfo, FALSE);
            pTaskInfo->pBuf->collection_size = tmp_i;
          }
          else
          {
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 1)
            EepromWriteByteUnSigned((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                                    (uint8_t)tmp_i);
#else
#if (RTSiTA_EEPROM__SIZE__COLLECTIONSIZE == 2)
            EepromWriteWordUnSigned((void*)(pTaskInfo->pBuf->baseAddr + RTSiTA_EEPROM_OFFSET_COLLECTIONSIZE),
                                    (uint16_t)tmp_i);
#else
#warning "Not a valid data size for allowed operation."
#endif
#endif
          }
          retval = SUCCESSCMD;
        }
      }
    }
    else if (strcmp(cmdTok, "force") == 0)      // Force a(n additional) sample to go out.
    {
      RSTi_Thermarray_GetDataSample(pTaskInfo);
      RSTi_Add2Collection(pTaskInfo, TRUE);     // Add this sample to collection.
      RSTi_SendCollection(pTaskInfo, FALSE);    // When collection is full, send to shore.
      retval = SUCCESSCMD;
    }
  } // 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(&fileOut[pTaskInfo->portNum][0], PSTR("Personality: Thermistor Array\n"), 30);
      tmp_i = 30;
      tmp2_i = pgm_read_word(&swversion);
      snprintf_PSTR(pTaskInfo->pBuf->rsti_tmp_str, SIZEOF_RSTI_TMP_STR,
                    "Version: %d.%d Build: %d (%d-%03d).\n",
                    MAJOR, MINOR, tmp2_i, tmp2_i >> 8, tmp2_i & 0xFF);
      memcpy(&fileOut[pTaskInfo->portNum][tmp_i], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
      tmp_i += strlen(pTaskInfo->pBuf->rsti_tmp_str);
      fileOutSize[pTaskInfo->portNum] = tmp_i;
      retval = SUCCESS;
    }
  } // if (system)
// CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD: CMD:
  else if (cmd == CMD_CMD)
  {
    // Service the 'cmd:' command. Allows one to send a command directly to the thermistor array.
    //  Format is:
    //    cmd: <string>
    //      where:  the contents of <string> gets sent to the thermistor array. Any response up to
    //              the first <CR> and within ten seconds and 99 characters, whichever comes first,
    //              will be returned.
    OSuartPutStrWait(pTaskInfo->portNum, context, 100, &err);

    memset(pTaskInfo->pBuf->rsti_tmp_str, 0, SIZEOF_RSTI_TMP_STR);
    if (RSTi_GetString(pTaskInfo->pBuf->rsti_tmp_str, pTaskInfo->portNum, 0x0A,
                       SIZEOF_RSTI_TMP_STR-1, (uint16_t)(10 * OS_TICKS_PER_SEC)))
    {
      // Got at least some characters.
      memcpy(&fileOut[pTaskInfo->portNum][0], pTaskInfo->pBuf->rsti_tmp_str, strlen(pTaskInfo->pBuf->rsti_tmp_str));
      fileOutSize[pTaskInfo->portNum] = strlen(pTaskInfo->pBuf->rsti_tmp_str);
    }
    else
    {
      memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("Cmd: No response to 'cmd:'!"), 27);
      fileOutSize[pTaskInfo->portNum] = 27;
    }
    retval = SUCCESS;
  } // if (cmd:)

exit_TPF:
  ta_busy = 0;                           // Tell Debug that we are done talking to PL module.
  if(retval == FAIL)
  {
    // For any failure, return a message indicating such.
    memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("TA Cmd: failed"), 14);
    fileOutSize[pTaskInfo->portNum] = 14;
  }
  else if(retval == SUCCESSCMD)
  {
    // Generic succes for comand.
    memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("TA Cmd: OK"), 10);
    fileOutSize[pTaskInfo->portNum] = 10;
  }
  if (fileOutSize[pTaskInfo->portNum] == 0)
  {
    // Handled something, but without a specific response.
    memcpy_P(&fileOut[pTaskInfo->portNum][0], PSTR("TA Cmd: Something, but what, I don't know!"), 42);
    fileOutSize[pTaskInfo->portNum] = 42;
  }
  // Send response back up the chain.
  RSTi_Thermarray_QueueFile(pTaskInfo, fileID);
TADEBUG_STR(&fileOut[pTaskInfo->portNum][0], 100, &err);
  return SUCCESS;
} // RSTi_Thermarray_ParseFile


uint8_t RSTi_Thermarray_QueueFile(PRSTI_THERMARRAY_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;
} // RSTi_Thermarray_QueueFile


// ................................................................................................
// Obtain a character string from the Thermistor Array.
//  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.
//
// 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 RSTi_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 = 0;
  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));
  return idx;
} // RSTi_GetString
