//---------------------------------------------------------------------------
// Copyright (C) 2000 Dallas Semiconductor Corporation, All Rights Reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL DALLAS SEMICONDUCTOR BE LIABLE FOR ANY CLAIM, DAMAGES
// OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//---------------------------------------------------------------------------
//
//  thermo21.c - Thermochron iButton utility functions
//
//  Version: 2.00
//
//    History:
//           1.03 -> 2.00  Reorganization of Public Domain Kit
//                         Convert to global CRC utility functions
//                         Y2K fix.

#include "ownet.h"
#include "thermo21.h"
#include <time.h>
#include <stdio.h>

// Include files for the Palm and Visor
#ifdef __MC68K__
#include <DateTime.h>
#include <TimeMgr.h>
#else
#include <string.h>
#endif

// Local Function Prototypes
static int ThermoStep(int, ThermoStateType *, ThermoScript *, int *, int *, int *, char *);
static int ReadPages(int, int, int, int *, uchar *);
static int WriteScratch(int, uchar *, int, int);
static int CopyScratch(int, int, int);
static int WriteMemory(int, uchar *, int, int);

// global state information
static int current_speed[MAX_PORTNUM];

// download steps
static ThermoScript Download[] = {{ST_READ_STATUS, "Setup to read the mission status"},
                                  {ST_READ_PAGES, "Read the status page"},
                                  {ST_READ_ALARM, "Setup to read alarm pages"},
                                  {ST_READ_PAGES, "Read the alarm pages"},
                                  {ST_READ_HIST, "Setup to read histogram pages"},
                                  {ST_READ_PAGES, "Read the histogram pages"},
                                  {ST_READ_LOG, "Setup to read log pages"},
                                  {ST_READ_PAGES, "Read the log pages"},
                                  {ST_FINISH, "Finished"}};

// read status only steps
static ThermoScript GetStatus[] = {
    {ST_READ_STATUS, "Setup to read the mission status"}, {ST_READ_PAGES, "Read the status page"}, {ST_FINISH, "Finished"}};

// mission steps (assume already did StatusThermo)
static ThermoScript Mission[] = {{ST_CLEAR_SETUP, "Setup clear memory"},
                                 {ST_WRITE_MEM, "Write clear memory bit"},
                                 {ST_CLEAR_MEM, "Clear the memory"},
                                 {ST_READ_STATUS, "Setup to read the mission status"},
                                 {ST_READ_PAGES, "Read the status page"},
                                 {ST_CLEAR_VERIFY, "Verify memory is clear"},
                                 {ST_WRITE_TIME, "Setup to write the real time clock"},
                                 {ST_WRITE_MEM, "Write the real time clock"},
                                 {ST_WRITE_CONTROL, "Setup to write the control"},
                                 {ST_WRITE_MEM, "Write the control"},
                                 {ST_WRITE_RATE, "Setup to write the sample rate to start mission"},
                                 {ST_WRITE_MEM, "Write the sample rate"},
                                 {ST_READ_STATUS, "Read the new mission status"},
                                 {ST_FINISH, "Finished"}};

//--------------------------------------------------------------------------
// The 'DownloadThermo' downloads the specified Thermochron in 'SerialNum'
// and puts the data in the state variable 'ThermoState'.  Progress output
// is printed to the specified file 'fp'.
//
// 'portnum'     - number 0 to MAX_PORTNUM-1.  This number is provided to
//                 indicate the symbolic port number.
// 'SerialNum'   - Device serial number to download
// 'ThermoState' - pointer to a structure type that holds the raw and
//                 translated Thermochron data.
// 'fp'          - file pointer to print status information to
//
// Returns:   TRUE (1) : Thermochron download with raw data in ThermoState
//            FALSE (0): not downloaded.  Abort due to repeated errors
//                       or user keypress.
//
int DownloadThermo(int portnum, uchar *SerialNum, ThermoStateType *ThermoState, FILE *fp) {
  // set the serial num
  owSerialNum(portnum, SerialNum, FALSE);

  // run the script and download thermochron
  return RunThermoScript(portnum, ThermoState, Download, fp);
}

//--------------------------------------------------------------------------
// The 'ReadThermoStatus' reads the Thermochron status in 'SerialNum'
// and puts the data in the state variable 'ThermoState'.  Progress output
// is printed to the specified file 'fp'.
//
// 'portnum'     - number 0 to MAX_PORTNUM-1.  This number is provided to
//                 indicate the symbolic port number.
// 'SerialNum'   - Device serial number to download
// 'ThermoState' - pointer to a structure type that holds the raw and
//                 translated Thermochron data.
// 'fp'          - file pointer to print status information to
//
// Returns:   TRUE (1) : Thermochron status read with raw data in ThermoState
//            FALSE (0): status not read.  Abort due to repeated errors
//                       or user keypress.
//
int ReadThermoStatus(int portnum, uchar *SerialNum, ThermoStateType *ThermoState, FILE *fp) {

  // set the serial num
  owSerialNum(portnum, SerialNum, FALSE);

  // run the script and read status of thermochron
  return RunThermoScript(portnum, ThermoState, GetStatus, fp);
}

//--------------------------------------------------------------------------
// The 'MissionThermo' starts a new Thermochron mission on 'SerialNum'
// from the state information provided in 'ThermoState'. Progress output
// is printed to the specified file 'fp'.
//
// 'portnum'     - number 0 to MAX_PORTNUM-1.  This number is provided to
//                 indicate the symbolic port number.
// 'SerialNum'   - Device serial number to download
// 'ThermoState' - pointer to a structure type that holds the raw and
//                 translated Thermochron data.
// 'fp'          - file pointer to print status information to
//
// Returns:   TRUE (1) : Thermochron missioned
//            FALSE (0): not missioned.  Abort due to repeated errors
//                       or user keypress.
//
int MissionThermo(int portnum, uchar *SerialNum, ThermoStateType *ThermoState, FILE *fp) {
  // set the serial num
  owSerialNum(portnum, SerialNum, FALSE);

  // run the script and mission thermochron
  return RunThermoScript(portnum, ThermoState, Mission, fp);
}

//--------------------------------------------------------------------------
// Run the specified script.  Return TRUE if all steps completed else FALSE.
// Status is printed to file 'fp'.
//
int RunThermoScript(int portnum, ThermoStateType *ThermoState, ThermoScript script[], FILE *fp) {
  char msg[256], LastDescription[256], LastMsg[256];
  int StepCount, SubStep, ErrorCount, Status;
  int last_clear_step = 0;

  // reset the step to the begining
  StepCount = 0;
  SubStep = 0;
  ErrorCount = 0;
  Status = STATUS_INPROGRESS;

  LastDescription[0] = 0;
  LastMsg[0] = 0;

  // loop to perform all of the steps to download the Thermochron
  do {
    // switch on the status of the last step done
    switch (Status) {
    // step complete so go to the next
    case STATUS_STEP_COMPLETE:
      StepCount++;
      SubStep = 0;
      ErrorCount = 0;
      Status = STATUS_INPROGRESS;
      LastDescription[0] = 0;
      LastMsg[0] = 0;
      break;
    // in progress so call again
    case STATUS_INPROGRESS:
      // record the step position of the last memory clear
      // this is in case we need to attempt a clear again
      if (script[StepCount].Step == ST_CLEAR_SETUP)
        last_clear_step = StepCount;

      // print step description if different
      if (strcmp(LastDescription, script[StepCount].StepDescription) != 0) {
        fprintf(fp, "%s --> ", script[StepCount].StepDescription);
        sprintf(LastDescription, "%s", script[StepCount].StepDescription);
      }

      // perform a step in the job
      Status = ThermoStep(portnum, ThermoState, &script[StepCount], &SubStep, &Status, &ErrorCount, msg);

      // print results if different
      if (strcmp(LastMsg, msg) != 0) {
        fprintf(fp, "%s\n", msg);
        sprintf(LastMsg, "%s", msg);
      } else
        fprintf(fp, ".");

      break;
    // encountered a transient error
    case STATUS_ERROR_TRANSIENT:
      // check if transient error is a memory clear
      if (script[StepCount].Step == ST_CLEAR_VERIFY) {
        // put back to starting clear over again
        StepCount = last_clear_step;
        SubStep = 0;
        ErrorCount = 0;
        Status = STATUS_INPROGRESS;
        break;
      }
      // if 20 tansient errors in a row then abort
      if (ErrorCount > 20)
        Status = STATUS_ERROR_HALT;
      else
        Status = STATUS_INPROGRESS;
      break;
    // all steps complete
    case STATUS_COMPLETE:
      fprintf(fp, "End script normally\n");
      return TRUE;
    // non-recoverable error
    case STATUS_ERROR_HALT:
      fprintf(fp, "Aborting script due to non-recoverable error\n");
      return FALSE;
    }
  } while (!key_abort());

  // key abort
  fprintf(fp, "Aborting script due to key press\n");
  return FALSE;
}

//----------------------------------------------------------------------
//  Use the script to perform a step and return.
//
int ThermoStep(int portnum, ThermoStateType *ThermoState, ThermoScript *StateScript, int *SubStep, int *Status, int *ErrorCount,
               char *msg) {
  short rslt;
  static int read_page_num, read_pages, write_addr, write_len;
  static uchar *read_buf, *write_buf;
  static uchar tbuf[5];

  // do the current step
  switch (StateScript->Step) {
  // the operation is complete
  case ST_FINISH:
    sprintf(msg, "Operation complete");
    *Status = STATUS_COMPLETE;
    break;

  // read the mission status page
  case ST_READ_STATUS:
    read_page_num = STATUS_PAGE;
    read_pages = 1;
    read_buf = ThermoState->MissStat.status_raw;
    sprintf(msg, "Ready to read status page %d", read_page_num);
    *Status = STATUS_STEP_COMPLETE;
    break;

  // set up to read the alarm registers
  case ST_READ_ALARM:
    read_page_num = 17;
    read_pages = 3;
    read_buf = ThermoState->AlarmData.alarm_raw;
    sprintf(msg, "Ready to read alarm pages %d to %d", read_page_num, read_page_num + read_pages - 1);
    *Status = STATUS_STEP_COMPLETE;
    break;

  // set up to read the histogram data
  case ST_READ_HIST:
    read_page_num = 64;
    read_pages = 4;
    read_buf = ThermoState->HistData.hist_raw;
    sprintf(msg, "Ready to read histogram pages %d to %d", read_page_num, read_page_num + read_pages - 1);
    *Status = STATUS_STEP_COMPLETE;
    break;

  // set up to read the log data
  case ST_READ_LOG:
    read_page_num = 128;
    read_pages = 64;
    read_buf = ThermoState->LogData.log_raw;
    sprintf(msg, "Ready to read log pages %d to %d", read_page_num, read_page_num + read_pages - 1);
    *Status = STATUS_STEP_COMPLETE;
    break;

  // read the specified pages
  case ST_READ_PAGES:
    // check for last page
    if (*SubStep == 0)
      // set the sub-step to the current page being read
      *SubStep = read_page_num;
    // read the status page
    rslt = ReadPages(portnum, read_page_num, read_pages, SubStep, read_buf);
    if (rslt == FALSE) {
      sprintf(msg, "Thermochron not on 1-Wire Net");
#ifdef __MC68K__
      *Status = STATUS_ERROR_HALT;
#else
      *Status = STATUS_INPROGRESS;
#endif
    } else {
      sprintf(msg, "Pages read from Thermochron");
      *Status = STATUS_STEP_COMPLETE;
    }
    break;

  // setup the clear memory
  case ST_CLEAR_SETUP:
    // create a small buff to write to start the clear memory
    tbuf[0] = 0x40;
    write_buf = &tbuf[0];
    write_len = 1;
    write_addr = 0x20E;
    sprintf(msg, "Write to setup clear memory");
    *Status = STATUS_STEP_COMPLETE;
    break;

  // clear the memory
  case ST_CLEAR_MEM:
    // set the clear memory command (not check return because verify)
    if (!owAccess(portnum)) {
      OWERROR(OWERROR_ACCESS_FAILED);
    }
    if (!owWriteByte(portnum, 0x3C)) {
      OWERROR(OWERROR_WRITE_BYTE_FAILED);
    }
    msDelay(3);
    if (!owTouchReset(portnum)) {
      OWERROR(OWERROR_RESET_FAILED);
    }
    sprintf(msg, "Clear memory command sent");
    *Status = STATUS_STEP_COMPLETE;
    break;

  // clear the memory
  case ST_CLEAR_VERIFY:
    // look at the memory clear bit
    if ((ThermoState->MissStat.status_raw[0x14] & 0x40) == 0x40) {
      sprintf(msg, "Memory is clear");
      *Status = STATUS_STEP_COMPLETE;
    } else {
      sprintf(msg, "Memory did NOT clear");
      *Status = STATUS_ERROR_TRANSIENT;
    }
    break;

  // setup write time, clock alarm, control, trips
  case ST_WRITE_TIME:
    // create the write buffer
    FormatMission(&ThermoState->MissStat);
    write_buf = &ThermoState->MissStat.status_raw[0x00];
    write_len = 13;
    write_addr = 0x200;
    sprintf(msg, "Write time, clock alarm, and trips setup");
    *Status = STATUS_STEP_COMPLETE;
    break;

  // write the control, mission delay and clear flags
  case ST_WRITE_CONTROL:
    write_buf = &ThermoState->MissStat.status_raw[0x0E];
    write_len = 7;
    write_addr = 0x20E;
    sprintf(msg, "Write control, mission delay, clear flags setup");
    *Status = STATUS_STEP_COMPLETE;
    break;

  case ST_WRITE_RATE:
    write_buf = &ThermoState->MissStat.status_raw[0x0D];
    write_len = 1;
    write_addr = 0x20D;
    sprintf(msg, "Write sample rate setup");
    *Status = STATUS_STEP_COMPLETE;
    break;

  // write the specified memory location
  case ST_WRITE_MEM:
    if (WriteMemory(portnum, write_buf, write_len, write_addr)) {
      sprintf(msg, "Memory written to Thermochron");
      *Status = STATUS_STEP_COMPLETE;
    } else {
      sprintf(msg, "Thermochron not on 1-Wire Net");
      *Status = STATUS_INPROGRESS;
    }

  default:
    break;
  }

  return *Status;
}

//----------------------------------------------------------------------
//  Read a specified number of pages in overdrive
//
// 'portnum'  - number 0 to MAX_PORTNUM-1.  This number is provided to
//              indicate the symbolic port number.
//
int ReadPages(int portnum, int start_pg, int num_pgs, int *last_pg, uchar *finalbuf) {
  int skip_overaccess = 0, skip_access = 0;
  uchar pkt[60];
  int len, i;
  uchar SerialNumber[8];
  ushort lastcrc16;

  // read the rom number
  owSerialNum(portnum, SerialNumber, TRUE);

#ifndef __MC68K__
  // verify device is in overdrive
  if (current_speed[portnum] == MODE_OVERDRIVE) {
    if (owVerify(portnum, FALSE))
      skip_overaccess = 1;
  }

  if (!skip_overaccess) {
    if (owOverdriveAccess(portnum))
      current_speed[portnum] = MODE_OVERDRIVE;
    else
      current_speed[portnum] = MODE_NORMAL;
  }
#endif

  // loop while there is pages to read
  do {
    // create a packet to read a page
    len = 0;
    setcrc16(portnum, 0);
    // optional skip access on subsequent pages
    if (!skip_access) {
      // match
      pkt[len++] = 0x55;
      // rom number
      for (i = 0; i < 8; i++)
        pkt[len++] = SerialNumber[i];
      // read memory with crc command
      pkt[len] = 0xA5;
      lastcrc16 = docrc16(portnum, pkt[len++]);
      // address
      pkt[len] = (uchar)((*last_pg << 5) & 0xFF);
      lastcrc16 = docrc16(portnum, pkt[len++]);
      pkt[len] = (uchar)(*last_pg >> 3);
      lastcrc16 = docrc16(portnum, pkt[len++]);
    }

    // set 32 reads for data and 2 for crc
    for (i = 0; i < 34; i++)
      pkt[len++] = 0xFF;

    // send the bytes
    if (owBlock(portnum, !skip_access, pkt, len)) {
      // calucate the CRC over the last 34 bytes
      for (i = 0; i < 34; i++)
        lastcrc16 = docrc16(portnum, pkt[len - 34 + i]);

      // check crc
      if (lastcrc16 == 0xB001) {
        // copy the data into the buffer
        for (i = 0; i < 32; i++)
          finalbuf[i + (*last_pg - start_pg) * 32] = pkt[len - 34 + i];

        // change number of pages
        *last_pg = *last_pg + 1;

        // now skip access
        skip_access = TRUE;
      } else
        return FALSE;

    } else
      return FALSE;

  } while ((*last_pg - start_pg) < num_pgs);

  return TRUE;
}

//----------------------------------------------------------------------------}
// Write a memory location. Data must all be on the same page
//
// 'portnum'  - number 0 to MAX_PORTNUM-1.  This number is provided to
//              indicate the symbolic port number.
//
int WriteMemory(int portnum, uchar *Buf, int ln, int adr) {
  // write to scratch and then copy
  if (WriteScratch(portnum, Buf, ln, adr))
    return CopyScratch(portnum, ln, adr);

  return FALSE;
}

//----------------------------------------------------------------------------}
// Write the scratch pad
//
// 'portnum'  - number 0 to MAX_PORTNUM-1.  This number is provided to
//              indicate the symbolic port number.
//
int WriteScratch(int portnum, uchar *Buf, int ln, int adr) {
  int i;
  uchar pbuf[80];

  // check for alarm indicator
  if (owAccess(portnum)) {
    // construct a packet to send
    pbuf[0] = 0x0F;                // write scratch command
    pbuf[1] = (adr & 0xFF);        // address 1
    pbuf[2] = ((adr >> 8) & 0xFF); // address 2

    // the write bytes
    for (i = 0; i < ln; i++)
      pbuf[3 + i] = (uchar)(Buf[i]); // data

    // perform the block
    if (!owBlock(portnum, FALSE, pbuf, ln + 3))
      return FALSE;

    // Now read back the scratch
    if (owAccess(portnum)) {
      // construct a packet to send
      pbuf[0] = 0xAA; // read scratch command
      pbuf[1] = 0xFF; // address 1
      pbuf[2] = 0xFF; // address 2
      pbuf[3] = 0xFF; // offset

      // the write bytes
      for (i = 0; i < ln; i++)
        pbuf[4 + i] = 0xFF; // data

      // perform the block
      if (!owBlock(portnum, FALSE, pbuf, ln + 4))
        return FALSE;

      // read address 1
      if (pbuf[1] != (adr & 0xFF))
        return FALSE;
      // read address 2
      if (pbuf[2] != ((adr >> 8) & 0xFF))
        return FALSE;
      // read the offset
      if (pbuf[3] != ((adr + ln - 1) & 0x1F))
        return FALSE;
      // read and compare the contents
      for (i = 0; i < ln; i++) {
        if (pbuf[4 + i] != Buf[i])
          return FALSE;
      }
      // success
      return TRUE;
    }
  }

  OWERROR(OWERROR_ACCESS_FAILED);
  return FALSE;
}

//----------------------------------------------------------------------------}
// Copy the scratch pad
//
// 'portnum'  - number 0 to MAX_PORTNUM-1.  This number is provided to
//              indicate the symbolic port number.
//
int CopyScratch(int portnum, int ln, int adr) {
  int i;
  uchar pbuf[50];

  // check for alarm indicator
  if (owAccess(portnum)) {
    // construct a packet to send
    pbuf[0] = 0x55;                  // copy scratch command
    pbuf[1] = (adr & 0xFF);          // address 1
    pbuf[2] = ((adr >> 8) & 0xFF);   // address 2
    pbuf[3] = (adr + ln - 1) & 0x1F; // offset
    for (i = 0; i <= 9; i++)
      pbuf[4 + i] = 0xFF; // result of copy

    // perform the block
    if (owBlock(portnum, FALSE, pbuf, 14)) {
      if ((pbuf[13] == 0x55) || (pbuf[13] == 0xAA))
        return TRUE;
    }
  }

  OWERROR(OWERROR_ACCESS_FAILED);
  return FALSE;
}

//----------------------------------------------------------------------
//  Interpret the Status by looking at the 'raw' portion of the
//  mission status structure.
//
void InterpretStatus(MissionStatus *mstatus) {
  timedate td, tdtmp;
  int offset;
  uint tmtmp;

#ifndef __MC68K__
  time_t tint;
  struct tm *tstruct;
#endif

  // mission in progress flag
  mstatus->mission_in_progress = (0x20 & mstatus->status_raw[0x14]) >> 5;

  // sample rate
  mstatus->sample_rate = mstatus->status_raw[0x0D];

  // rollover enabled
  mstatus->rollover_enable = (0x08 & mstatus->status_raw[0x0E]) >> 3;

  // startdelay
  mstatus->start_delay = (mstatus->status_raw[0x13] << 8) | mstatus->status_raw[0x12];

  // number of samples in this mission
#ifdef __MC68K__
  mstatus->mission_samples = (((uint)mstatus->status_raw[0x1C]) * 65536) + (((uint)mstatus->status_raw[0x1B]) * 256) +
                             ((uint)mstatus->status_raw[0x1A]);
#else
  mstatus->mission_samples = (mstatus->status_raw[0x1C] << 16) | (mstatus->status_raw[0x1B] << 8) | mstatus->status_raw[0x1A];
#endif

  // total number of samples
#ifdef __MC68K__
  mstatus->samples_total = (((uint)mstatus->status_raw[0x1F]) * 65536) + (((uint)mstatus->status_raw[0x1E]) * 256) +
                           ((uint)mstatus->status_raw[0x1D]);
#else
  mstatus->samples_total = (mstatus->status_raw[0x1F] << 16) | (mstatus->status_raw[0x1E] << 8) | mstatus->status_raw[0x1D];
#endif

  // temperature thresholds
  mstatus->high_threshold = mstatus->status_raw[0x0C];
  mstatus->low_threshold = mstatus->status_raw[0x0B];

  // rollover occurred
  if ((mstatus->mission_samples > 2048) && mstatus->rollover_enable)
    mstatus->rollover_occurred = 1;
  else
    mstatus->rollover_occurred = 0;

  // current real-time clock value
  offset = 0x00;
  td.second = BCDToBin((uchar)(mstatus->status_raw[offset] & 0x7F));
  td.minute = BCDToBin((uchar)(mstatus->status_raw[offset + 1] & 0x7F));
  // check for 12 hour mode
  if (mstatus->status_raw[offset + 2] & 0x40) {
    td.hour = BCDToBin((uchar)(mstatus->status_raw[offset + 2] & 0x1F));
    // check for PM
    if (mstatus->status_raw[offset + 2] & 0x20)
      td.hour += 12;
  } else
    td.hour = BCDToBin((uchar)(mstatus->status_raw[offset + 2] & 0x3F));
  td.day = BCDToBin((uchar)(mstatus->status_raw[offset + 4] & 0x3F));
  td.month = BCDToBin((uchar)(mstatus->status_raw[offset + 5] & 0x1F));
  td.year = BCDToBin(mstatus->status_raw[offset + 6]) + 1900;
  // check for century bit
  if (mstatus->status_raw[offset + 5] & 0x80)
    td.year = BCDToBin(mstatus->status_raw[offset + 6]) + 2000; // (2.00)
  // convert to seconds since 1970
  mstatus->current_time = DateToSeconds(&td);

  // date/time when mission started
  offset = 0x15;
  td.second = (uchar)0;
  td.minute = BCDToBin((uchar)(mstatus->status_raw[offset] & 0x7F));
  // check for 12 hour mode
  if (mstatus->status_raw[offset + 1] & 0x40) {
    td.hour = BCDToBin((uchar)(mstatus->status_raw[offset + 1] & 0x1F));
    // check for PM
    if (mstatus->status_raw[offset + 1] & 0x20)
      td.hour += 12;
  } else
    td.hour = BCDToBin((uchar)(mstatus->status_raw[offset + 1] & 0x3F));
  td.day = BCDToBin((uchar)(mstatus->status_raw[offset + 2] & 0x3F));
  td.month = BCDToBin((uchar)(mstatus->status_raw[offset + 3] & 0x1F));
  td.year = BCDToBin((uchar)(mstatus->status_raw[offset + 4])); // (2.00)
  // (2.00) logic to decide on century of mission stamp
  // check if century bit set in mission stamp
  if (mstatus->status_raw[offset + 3] & 0x80)
    td.year += 2000;
  // check in mission in progress
  else if (mstatus->mission_in_progress) {
    // calculate the mission start year back from real time clock
    tmtmp = mstatus->current_time - (mstatus->sample_rate * mstatus->mission_samples * 60);
    SecondsToDate(&tdtmp, tmtmp);
    td.year = tdtmp.year;
  } else {
    // mission stopped so get century by year window
    if (td.year <= 70)
      td.year += 2000;
    else
      td.year += 1900;
  }
  // convert to seconds since 1970
  if ((td.month == 0) || (td.day == 0))
    mstatus->mission_start_time = 0;
  else
    mstatus->mission_start_time = DateToSeconds(&td);

    // download stations time of reading
#ifdef __MC68K__
  mstatus->download_time = TimGetSeconds();
#else
  time(&tint);
  tstruct = localtime(&tint);
  td.day = tstruct->tm_mday;
  td.month = tstruct->tm_mon + 1; // (1.01)
  td.year = tstruct->tm_year + 1900;
  td.hour = tstruct->tm_hour;
  td.minute = tstruct->tm_min;
  td.second = tstruct->tm_sec;
  mstatus->download_time = DateToSeconds(&td);
#endif

  // skip alarm modes and status for now
}

//--------------------------------------------------------------------------
// Take the Mission Status structure and create new raw data to start
// a new mission.
//
void FormatMission(MissionStatus *mstatus) {
  int i;
  time_t tint;
#ifndef __MC68K__
  struct tm *tstruct;
#else
  DateTimePtr tstruct = NULL;
#endif
  // clear the buffer
  for (i = 0; i < 32; i++)
    mstatus->status_raw[i] = 0;

    // Real Time Clock
#ifdef __MC68K__
  tint = TimGetSeconds();
#else
  time(&tint);
#endif
  tint++; // add 1 second

#ifdef __MC68K__
  TimSecondsToDateTime(tint, tstruct);
#else
  tstruct = localtime(&tint);
#endif

  // convert to BCD
#ifdef __MC68K__
  mstatus->status_raw[0x00] = ToBCD((short)tstruct->second);
  mstatus->status_raw[0x01] = ToBCD((short)tstruct->minute);
  mstatus->status_raw[0x02] = ToBCD((short)tstruct->hour);
  mstatus->status_raw[0x03] = ToBCD((short)(tstruct->weekDay + 1));
  mstatus->status_raw[0x04] = ToBCD((short)tstruct->day);
  mstatus->status_raw[0x05] = ToBCD((short)(tstruct->month + 1));
  if (tstruct->year >= 100)
    mstatus->status_raw[0x05] |= 0x80;
  mstatus->status_raw[0x06] = ToBCD((short)(tstruct->year % 100));
#else
  mstatus->status_raw[0x00] = ToBCD((short)tstruct->tm_sec);
  mstatus->status_raw[0x01] = ToBCD((short)tstruct->tm_min);
  mstatus->status_raw[0x02] = ToBCD((short)tstruct->tm_hour);
  mstatus->status_raw[0x03] = ToBCD((short)(tstruct->tm_wday + 1));
  mstatus->status_raw[0x04] = ToBCD((short)tstruct->tm_mday);
  mstatus->status_raw[0x05] = ToBCD((short)(tstruct->tm_mon + 1));
  if (tstruct->tm_year >= 100)
    mstatus->status_raw[0x05] |= 0x80;
  mstatus->status_raw[0x06] = ToBCD((short)(tstruct->tm_year % 100));
#endif

  // Real Time clock Alarm (leave 0's)
  // Low temp alarm
  mstatus->status_raw[0x0B] = mstatus->low_threshold;
  // High temp alarm
  mstatus->status_raw[0x0C] = mstatus->high_threshold;
  // sample rate
  mstatus->status_raw[0x0D] = mstatus->sample_rate;
  // control
  mstatus->status_raw[0x0E] = 0x40;
  if (mstatus->rollover_enable)
    mstatus->status_raw[0x0E] |= 0x08;
  // mission start delay
  mstatus->status_raw[0x12] = mstatus->start_delay & 0xFF;
  mstatus->status_raw[0x13] = (mstatus->start_delay >> 8) & 0xFF;
}

//--------------------------------------------------------------------------
// Convert an integer to a 1 Byte BCD number (99 max)
//
uchar ToBCD(short num) {
  uchar rtbyte;

  rtbyte = (num - ((num / 10) * 10)) & 0x0F;
  rtbyte = rtbyte | ((num / 10) << 4);

  return rtbyte;
}

//--------------------------------------------------------------------------
// Take the Mission Status structure and convert to string format
//
void MissionStatusToString(MissionStatus *mstatus, int ConvertToF, char *str) {
  int cnt = 0, i;
  timedate td;
#ifndef __MC68K__
  time_t tint;
  struct tm *tstruct;
#else
  DateTimePtr tstruct = NULL;
#endif

  // title
#ifdef __MC68K__
  cnt += sprintf(&str[cnt], "Stat For DS1921:");
#else
  cnt += sprintf(&str[cnt], "Mission State\n-------------\n");
  cnt += sprintf(&str[cnt], "Serial Number of DS1921: ");
#endif

  // serial number
  for (i = 7; i >= 0; i--)
    cnt += sprintf(&str[cnt], "%02X", mstatus->serial_num[i]);

  // mission state
  if (mstatus->mission_in_progress)
    cnt += sprintf(&str[cnt], "\nMission is in progress\n");
  else
    cnt += sprintf(&str[cnt], "\nMission is ended\n");

  // sample rate
  cnt += sprintf(&str[cnt], "Sample rate: %d minute(s)\n", mstatus->sample_rate);

  // rollover
  cnt += sprintf(&str[cnt], "Roll-Over Enabled: ");
  if (mstatus->rollover_enable)
    cnt += sprintf(&str[cnt], "yes\n");
  else
    cnt += sprintf(&str[cnt], "no\n");

  cnt += sprintf(&str[cnt], "Roll-Over Occurred: ");
  if (mstatus->rollover_occurred)
    cnt += sprintf(&str[cnt], "yes\n");
  else
    cnt += sprintf(&str[cnt], "no\n");

  // mission start time
  if (mstatus->start_delay == 0) {
    SecondsToDate(&td, mstatus->mission_start_time);
    if (mstatus->mission_start_time == 0)
      cnt += sprintf(&str[cnt], "Mission Start time: not started yet\n");
    else
      cnt += sprintf(&str[cnt], "Mission Start: %02d/%02d/%04d  %02d:%02d:%02d\n", td.month, td.day, td.year, td.hour,
                     td.minute, td.second);

  } else
    cnt += sprintf(&str[cnt], "Mission Start time: na\n");

  // mission start delay
  cnt += sprintf(&str[cnt], "Mission Start delay: %d minute(s)\n", mstatus->start_delay);

  // mission samples
  cnt += sprintf(&str[cnt], "Mission Samples: %d\n", mstatus->mission_samples);

  // device total samples
  cnt += sprintf(&str[cnt], "Device total samples: %d\n", mstatus->samples_total);

  // temperature display mode
  cnt += sprintf(&str[cnt], "Temp displayed in: ");
  if (ConvertToF)
    cnt += sprintf(&str[cnt], "(Fahrenheit)\n");
  else
    cnt += sprintf(&str[cnt], "(Celsius)\n");

  // thresholds
  cnt += sprintf(&str[cnt], "High Threshold: %6.1f\n", TempToFloat(mstatus->high_threshold, ConvertToF));

  cnt += sprintf(&str[cnt], "Low Threshold: %6.1f\n", TempToFloat(mstatus->low_threshold, ConvertToF));

  // time from D1921
  SecondsToDate(&td, mstatus->current_time);
  cnt += sprintf(&str[cnt], "Current Real-Time Clock from DS1921: %02d/%02d/%04d  %02d:%02d:%02d\n", td.month, td.day, td.year,
                 td.hour, td.minute, td.second);

#ifndef __MC68K__
  // current PC time
  time(&tint);
  tstruct = localtime(&tint);

  cnt += sprintf(&str[cnt], "Current PC Time: %02d/%02d/%04d  %02d:%02d:%02d\n", tstruct->tm_mon + 1, tstruct->tm_mday,
                 tstruct->tm_year + 1900, tstruct->tm_hour, tstruct->tm_min, tstruct->tm_sec);
#endif

  // zero terminate string
  str[cnt] = 0;
}

//----------------------------------------------------------------------
//  Interpret the Histogram by looking at the 'raw' portion of the
//  Histogram structure.  Store the temperature range values in Celsius.
//
void InterpretHistogram(Histogram *hist) {
  int i;

  // loop through each bin value
  for (i = 0; i < 126; i += 2) { // (2.00)
    // get the bin value
    hist->bin_count[i / 2] = hist->hist_raw[i] | (hist->hist_raw[i + 1] << 8);

    // start value for this bin
    hist->start_range[i / 2] = TempToFloat((uchar)((i / 2) << 2), FALSE);

    // end value for this bin
    hist->end_range[i / 2] = TempToFloat((uchar)(((i / 2) << 2) | 0x03), FALSE);
  }
}

//--------------------------------------------------------------------------
// Take the Histogram structure and convert to string format
//
void HistogramToString(Histogram *hist, int ConvertToF, char *str) {
  int cnt = 0, i;

  // title
  cnt += sprintf(&str[cnt], "Temperature Histogram\n---------------------\n"
                            "Format: [Temp Range, Count] ");

  if (ConvertToF)
    cnt += sprintf(&str[cnt], "(Fahrenheit)\n");
  else
    cnt += sprintf(&str[cnt], "(Celsius)\n");

  // loop through bins
  for (i = 0; i < 63; i++) { // (2.00)
    cnt += sprintf(&str[cnt], "%6.1f to %6.1f, %d\n", (ConvertToF) ? CToF(hist->start_range[i]) : hist->start_range[i],
                   (ConvertToF) ? CToF(hist->end_range[i]) : hist->end_range[i], hist->bin_count[i]);
  }

  // zero terminate string
  str[cnt] = 0;
}

//----------------------------------------------------------------------
//  Interpret the Temperature Alarm Event data by looking at the 'raw'
//  portion of the TempAlarmEvents structure.  Mission Status is needed
//  to interpret the events.
//
void InterpretAlarms(TempAlarmEvents *alarm, MissionStatus *mstatus) {
  int i;
  uint event_mission_count;
  uchar duration;

  // low events
  alarm->num_low = 0;
  for (i = 0; i < 48; i += 4) {
    // get the mission start count of this event
    event_mission_count = (alarm->alarm_raw[i + 2] << 16) | (alarm->alarm_raw[i + 1] << 8) | alarm->alarm_raw[i];

    // check if done with low events
    if (!event_mission_count)
      break;

    // get the duration
    duration = alarm->alarm_raw[i + 3];

    // calculate the start time
    alarm->low_start_time[alarm->num_low] =
        mstatus->mission_start_time + (event_mission_count - 1) * (mstatus->sample_rate * 60);

    // calculate the end time
    alarm->low_end_time[alarm->num_low] = alarm->low_start_time[alarm->num_low] + (duration - 1) * (mstatus->sample_rate * 60);

    // increment number of low events
    alarm->num_low++;
  }

  // high events
  alarm->num_high = 0;
  for (i = 48; i < 96; i += 4) {
    // get the mission start count of this event
    event_mission_count = (alarm->alarm_raw[i + 2] << 16) | (alarm->alarm_raw[i + 1] << 8) | alarm->alarm_raw[i];

    // check if done with low events
    if (!event_mission_count)
      break;

    // get the duration
    duration = alarm->alarm_raw[i + 3];

    // calculate the start time
    alarm->high_start_time[alarm->num_high] =
        mstatus->mission_start_time + (event_mission_count - 1) * (mstatus->sample_rate * 60);

    // calculate the end time
    alarm->high_end_time[alarm->num_high] =
        alarm->high_start_time[alarm->num_high] + (duration - 1) * (mstatus->sample_rate * 60);

    // increment number of low events
    alarm->num_high++;
  }
}

//--------------------------------------------------------------------------
// Take the Temperature Alarms Events structure and convert to string
// format
//
void AlarmsToString(TempAlarmEvents *alarm, char *str) {
  int i, cnt = 0;
  timedate td;

  // title
  cnt += sprintf(&str[cnt], "Temperature Alarms\n------------------\n"
                            "Format: [(HIGH/LOW), Time/Date Range]\n");

  // loop through each low alarm
  for (i = 0; i < alarm->num_low; i++) {
    cnt += sprintf(&str[cnt], "LOW  , ");
    // start time
    SecondsToDate(&td, alarm->low_start_time[i]);
    cnt += sprintf(&str[cnt], " %02d/%02d/%04d  %02d:%02d  to  ", td.month, td.day, td.year, td.hour, td.minute);
    // end time
    SecondsToDate(&td, alarm->low_end_time[i]);
    cnt += sprintf(&str[cnt], " %02d/%02d/%04d  %02d:%02d\n", td.month, td.day, td.year, td.hour, td.minute);
  }

  // loop through each high alarm
  for (i = 0; i < alarm->num_high; i++) {
    cnt += sprintf(&str[cnt], "HIGH , ");
    // start time
    SecondsToDate(&td, alarm->high_start_time[i]);
    cnt += sprintf(&str[cnt], " %02d/%02d/%04d  %02d:%02d  to  ", td.month, td.day, td.year, td.hour, td.minute);
    // end time
    SecondsToDate(&td, alarm->high_end_time[i]);
    cnt += sprintf(&str[cnt], " %02d/%02d/%04d  %02d:%02d\n", td.month, td.day, td.year, td.hour, td.minute);
  }

  // zero terminate string
  str[cnt] = 0;
}

//----------------------------------------------------------------------
//  Interpret the Log data by looking at the 'raw'
//  portion of the Log structure.  Mission Status is needed
//  to interpret when the logs occurred.
//
void InterpretLog(Log *log, MissionStatus *mstatus) {
  uint loops = 0, overlap = 0, lastlog = 2048, i;
  int logcnt = 0;

  // check if wrap occurred
  if (mstatus->rollover_occurred) {
    // calculate the number loops
    loops = (mstatus->mission_samples / 2048) - 1;
    // calculate the number of overlap
    overlap = mstatus->mission_samples % 2048;
    log->num_log = 2048;
  } else {
    log->start_time = mstatus->mission_start_time;
    if (mstatus->mission_samples > 2048) // (1.02)
      lastlog = 2048;
    else
      lastlog = mstatus->mission_samples;
    log->num_log = (int)lastlog;
  }

  // set the interval
  log->interval = mstatus->sample_rate * 60;

  // caluclate the start time of the first log value
  log->start_time = mstatus->mission_start_time + loops * 2048 * log->interval + overlap * log->interval;

  // loop to fill in the remainder first
  for (i = overlap; i < lastlog; i++)
    log->temp[logcnt++] = TempToFloat(log->log_raw[i], FALSE);

  // loop to get the overlap
  for (i = 0; i < overlap; i++)
    log->temp[logcnt++] = TempToFloat(log->log_raw[i], FALSE);
}

//--------------------------------------------------------------------------
// Take the Log structure and convert to string
// format
//
void LogToString(Log *log, int ConvertToF, char *str) {
  int i, cnt = 0;
  uint logtime;
  timedate td;

  // title
  cnt += sprintf(&str[cnt], "Log Data\n--------\n"
                            "Format: [Time/Date , Temperature] ");
  if (ConvertToF)
    cnt += sprintf(&str[cnt], "(Fahrenheit)\n");
  else
    cnt += sprintf(&str[cnt], "(Celsius)\n");

  // loop through the logs
  logtime = log->start_time;
  for (i = 0; i < log->num_log; i++) {
    // time
    SecondsToDate(&td, logtime);
    cnt += sprintf(&str[cnt], "%04d-%02d-%02d  %02d:%02d ,", td.year, td.month, td.day, td.hour, td.minute);
    // temp
    cnt += sprintf(&str[cnt], "%6.1f\n", (ConvertToF) ? CToF(log->temp[i]) : log->temp[i]);

    // increment the time
    logtime += log->interval;
  }

  // zero terminate string
  str[cnt] = 0;
}

//--------------------------------------------------------------------------
// Convert the raw debug data to a string
//
void DebugToString(MissionStatus *mstatus, TempAlarmEvents *alarm, Histogram *hist, Log *log, char *str) {
  int i, cnt = 0;

  // title
  cnt += sprintf(&str[cnt], "Debug Dump\n----------\nRegister Page:\n");

  // reg
  for (i = 0; i < 32; i++) {
    cnt += sprintf(&str[cnt], "%02X ", mstatus->status_raw[i]);
    if (i && (((i + 1) % 16) == 0))
      cnt += sprintf(&str[cnt], "\n");
  }

  // alarms
  cnt += sprintf(&str[cnt], "Alarms:\n");
  for (i = 0; i < 96; i++) {
    cnt += sprintf(&str[cnt], "%02X ", alarm->alarm_raw[i]);
    if (i && (((i + 1) % 16) == 0))
      cnt += sprintf(&str[cnt], "\n");
  }

  // histogram
  cnt += sprintf(&str[cnt], "Histogram:\n");
  for (i = 0; i < 128; i++) {
    cnt += sprintf(&str[cnt], "%02X ", hist->hist_raw[i]);
    if (i && (((i + 1) % 16) == 0))
      cnt += sprintf(&str[cnt], "\n");
  }

  // log
  cnt += sprintf(&str[cnt], "Log:\n");
  for (i = 0; i < ((log->num_log > 2048) ? 2048 : log->num_log); i++) {
    cnt += sprintf(&str[cnt], "%02X ", log->log_raw[i]);
    if (i && (((i + 1) % 16) == 0))
      cnt += sprintf(&str[cnt], "\n");
  }

  // zero terminate string
  str[cnt] = 0;
}

//--------------------------------------------------------------------------
// Take one byte BCD value and return binary value
//
uchar BCDToBin(uchar bcd) { return (((bcd & 0xF0) >> 4) * 10) + (bcd & 0x0F); }

//--------------------------------------------------------------------------
// Take a 4 byte int string and convert it into a timedata structure.
//
static int dm[] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};

void SecondsToDate(timedate *td, uint x) {
  short tmp, i, j;
  uint y;

  // check to make sure date is not over 2070 (sanity check)
  if (x > 0xBBF81E00L)
    x = 0;

  y = x / 60;
  td->second = (ushort)(x - 60 * y);
  x = y / 60;
  td->minute = (ushort)(y - 60 * x);
  y = x / 24;
  td->hour = (ushort)(x - 24 * y);
  x = 4 * (y + 731);
  td->year = (ushort)(x / 1461);
  i = (int)((x - 1461 * (uint)(td->year)) / 4);
  td->month = 13;

  do {
    td->month -= 1;
    tmp = (td->month > 2) && ((td->year & 3) == 0) ? 1 : 0;
    j = dm[td->month] + tmp;

  } while (i < j);

  td->day = i - j + 1;

  // slight adjustment to algorithm
  if (td->day == 0)
    td->day = 1;

  td->year = (td->year < 32) ? td->year + 68 + 1900 : td->year - 32 + 2000;
}

//--------------------------------------------------------------------------
// DateToSeconds takes a time/date structure and converts it into the
// number of seconds since 1970
//
uint DateToSeconds(timedate *td) {
  uint Sv, Bv, Xv;

  // convert the date/time values into the 5 byte format used in the touch
  if (td->year >= 2000)
    Sv = td->year + 32 - 2000;
  else
    Sv = td->year - 68 - 1900;

  if ((td->month > 2) && ((Sv & 3) == 0))
    Bv = 1;
  else
    Bv = 0;

  Xv = 365 * (Sv - 2) + (Sv - 1) / 4 + dm[td->month] + td->day + Bv - 1;

  Xv = 86400 * Xv + (uint)(td->second) + 60 * ((uint)(td->minute) + 60 * (uint)(td->hour));

  return Xv;
}

//--------------------------------------------------------------------------
// Convert from DS1921 termature format to a float
//
//
float TempToFloat(uchar tmp, int ConvertToF) {
  float tfloat;

  tfloat = (float)((tmp / 2.0) - 40.0);

  if (ConvertToF)
    return (float)(tfloat * 9.0 / 5.0 + 32.0);
  else
    return tfloat;
}

//--------------------------------------------------------------------------
// Convert from Celsius to Fahrenheit
//
float CToF(float CVal) { return (float)(CVal * 9.0 / 5.0 + 32.0); }
