#ifndef CONTROL_H
#define CONTROL_H (0x01f00U)

/*========================================================================*/
/* Design notes for float control firmware                                */
/*========================================================================*/
/**
   The APFx firmware design makes fundamental use of the concept of
   "sequence points" for controlling the flow of the profile cycle.  A
   sequence point is defined to be a point where one phase of the mission
   cycle transitions to the next phase.  Most of the sequence points are
   based on time but there are several sequence points that are event-based.
   Given a properly functioning APFx controller, the firmware guarantees the
   phase transition at each sequence point regardless of the health of any
   other float component.

   The schematics below illustrate four different parts of the mission
   cycle:

      \begin{verbatim}
      1. The pressure-activation phase.
      2. The mission prelude.
      3. A profile from the park level.
      4. A deep profile.
      \end{verbatim}

   Pressure-activation phase
   -------------------------

   The pressure-activation feature is an optional phase of the mission.  It
   was designed to accomodate requests from ship's crew to be able to deploy
   the float without being required to start it with a magnet.  One
   event-based sequence points is implemented that activates the mission
   prelude if the pressure exceeds the activation threshold.
       
   \begin{verbatim}
     |--------------------------- A -----------------------------------|
    -+----------------+-------------+----------------------------------+- Time
    P|                 .           .                                    .    
    r|                   .        .       Sequence Points                 .   
    e|                      .    .        -----------------------           .  
    s|                      B   .         B = Pressure-activation              .
    s|                                    
    u|
    r|
    e|
   \end{verbatim}

   
   Mission prelude                                                        
   ---------------

   The mission prelude is the time period between mission activation and the
   first descent.  The sequence point 'L' is time-based and is the
   transition between the mission prelude and the first descent.  The period
   of the mission prelude is user-defined.
   
   \begin{verbatim}
     |--------------------------- L -----------------------------------|
    -+-----------------------------------------------------------------+- Time
    P|                                                                  .    
    r|                                                 Sequence Points   .   
    e|                                                 ---------------    .  
    s|                                                 L = Prelude          .
    s|                                                                       
    u|
    r|
    e|
   \end{verbatim}
   


   Profile from park depth
   -----------------------

   The profile cycle for a shallow profile consists of four phases.
   
   \begin{verbatim}
     |--------------------------------------------------- C ----------------|     
     |------------------------------ P ---------------|                     |     
     |----------------- K -------------|              |                     |     
     |--- F -----|                     |           S  |                T    |     
    -+-----------+---------------------+-----------+-------------------+----+- Time
    P|.          |                     |         .                      .    
    r| .         |                     |       .       Sequence Points   .   
    e|  .        |                     |     .         ---------------    .  
    s|    .      |                     |   .           F = Descent          .
    s|       .   |                     | .             K = Park             
    u|           . . . . . . . . . . . .               S = SurfaceDetect
    r|                                                 P = Profile           
    e|                                                 T = Telemetry
     |                                                 C = Cycle
   \end{verbatim}                    
   


   Deep profile
   ------------
   
   Test for deep profile:
   (PnpCycleLength<254 && ((!(PrfId%PnpCycleLength)) || PrfId==1) ? Yes : No;

   The profile cycle for a deep profile consists of five phases.
   
   \begin{verbatim}
     |------------------------------------------------------ C -------------|     
     |--------------------------------- P ------------|                     |     
     |----------------- D --------|                   |                     |     
     |------- K -------------|    |                   |                     |     
     |--- F -----|           |    |                S  |                T    |     
    -+-----------+-----------+----+----------------+-------------------+------- Time
    P|.          |           |    |              .                      .    
    r| .         |           |    |            .       Sequence Points   .   
    e|  .        |           |    |          .         ---------------    .  
    s|    .      |           |    |        .           F = Descent          .
    s|       .   |           |    |      .             K = Park                        
    u|           . . . . . . .    |    .               Q = DeepProfile 
    r|                            |  .                 D = GoDeep
    e|                        .   |.                   S = SurfaceDetect
     |                           .                     P = Profile
     |                         .                       T = Telemetry 
     |                         Q                       C = Cycle                             
   \end{verbatim}


   The following schematic shows 3 important piston extensions.
     
   \begin{verbatim}
                    +---Full Extension (227 counts)    Full Retraction (9 counts)---+
                    |          Ballast piston position (19 counts)--+               |
                    |                                               |               |
                    V                                               V               V
     ---------------------------------------------------------------------------------------+
                                                                                            |
     -------------+---+-------------------------------------------+---+-----------+---+-----+
          +-------|   |-------------------------------------------|   |-----------|   |-----------+
         /|       |   |                                           |   |           |   |           |
     ---+ |       |   |                                           |   |           |   |           |
        | |       |   |                                           |   |           |   |           |
        | |     +-|   |-------------------------------------------|   |-----------|   |-+         |
        | |    (  |. .|                                           |. .|           |. .|  )        |
        | |     +-|   |-------------------------------------------|   |-----------|   |-+         |
        | |       +---+                                           +---+           +---+           |
     ---+ |       .   .                                               .               .           |
         \|       |   |                                               |               |           |
          +-------+---+-----------------------------------------------+---------------+-----------+
          .       |   |                                               |               |           .
       -->| 44mm  |<- |<------------------- 318mm ----------------------------------->|<- 56mm -->|
                                        (Total scope)                 |<----- 70mm -------------->|
   \end{verbatim}
*/

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * $Id: control.c,v 1.51 2011/02/07 15:54:14 swift Exp $
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Copyright University of Washington.   Written by Dana Swift.
 *
 * This software was developed at the University of Washington using funds
 * generously provided by the US Office of Naval Research, the National
 * Science Foundation, and NOAA.
 *  
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
/** RCS log of revisions to the C source code.
 *
 * \begin{verbatim}
 * $Log: control.c,v $
 * Revision 1.51  2011/02/07 15:54:14  swift
 * Added missing newline at the end of the file.
 *
 * Revision 1.50  2010/12/26 15:27:33  swift
 * Minor formatting changes.
 *
 * Revision 1.49  2010/12/04 12:40:36  swift
 * Minor formatting change in SelfTest() output of the ICCID.
 *
 * Revision 1.48  2010/12/02 13:55:38  swift
 * Iridium has finally implemented an AT command in the LBT9522 that reads the
 * ICCID from the sim card.  The kludge that used the SIM card's phonebook to
 * store and allow retrieval of the ICCID/MSISDN has been replaced by the native
 * AT command that retrieves the ICCID.
 *
 * Revision 1.47  2010/11/08 23:57:42  swift
 * Updated expected LBT firmware revision.
 *
 * Revision 1.46  2010/04/20 18:58:07  swift
 * Fixed a minor bug in determination of minimum descent period.
 *
 * Revision 1.45  2010/03/14 15:25:55  swift
 * Changed the bitmask for Sbe41cpActive to remove a potential conflict.
 *
 * Revision 1.44  2010/02/10 18:55:45  swift
 * Moved inRange() and inCRange() to labrary functions.
 *
 * Revision 1.43  2010/02/05 19:34:05  swift
 * Reduced the minimum descent period and park period from 1 hour to 5 minutes
 * for the profile-after-prelude feature.
 *
 * Revision 1.42  2009/12/24 21:52:13  swift
 * Modified the LBT model and firmware revision for the 9522B.
 *
 * Revision 1.41  2009/10/16 16:39:42  swift
 * Define diagnostic bit-masks for Sbe41cp.
 *
 * Revision 1.40  2009/07/23 13:32:39  swift
 * Added 6 cycles of 4-sec on, 4-sec off 20mA squawk for successful mission launch.
 *
 * Revision 1.39  2009/07/16 14:25:21  swift
 * Re-implement pressure activation to be nonmodal.  Implement ability to escape
 * from the SelfTest().
 *
 * Revision 1.38  2008/07/14 16:53:30  swift
 * Increase length of status words from 16-bits to 32-bits.  Update
 * Sbe41cp firmware revision for new SBE controller.
 *
 * Revision 1.37  2007/10/05 22:31:41  swift
 * Change telemetry retry period to be a simple pause between cycles.
 *
 * Revision 1.36  2007/05/09 19:11:29  swift
 * Minor modifications to the code that handles the TimeOfDay feature.
 *
 * Revision 1.35  2007/05/08 18:10:16  swift
 * Added TimeOfDay feature to implement ability to schedule profiles to start
 * at a specified time of day.  Added attribution just below the copyright in
 * the main comment section.
 *
 * Revision 1.34  2007/05/07 21:04:08  swift
 * Rearranged the order in SelfTest() of GPS operations and LBT operations in
 * order to work around a 60-sec latency in SIM operations.
 *
 * Revision 1.33  2007/03/26 21:39:37  swift
 * Implemented retrieval of ICCID/MSISDN numbers from SIM card in SelfTest().
 *
 * Revision 1.32  2007/01/15 19:19:01  swift
 * Changed handling of air-valve solenoid signals to reduce bladder inflation
 * time.
 *
 * Revision 1.31  2006/12/27 16:02:53  swift
 * Changed expected firmware revision during SelfTest() of the Sbe41cp.
 *
 * Revision 1.30  2006/12/06 17:05:49  swift
 * Changed expected LBT firmware revision from ISO6001 to ISO6004.
 *
 * Revision 1.29  2006/11/25 00:10:05  swift
 * Added LogEntry() for undefined or uncaught state.
 *
 * Revision 1.28  2006/10/23 16:49:46  swift
 * Added missing include directive for flashio.h file.
 *
 * Revision 1.27  2006/10/13 18:54:06  swift
 * Added code in SelfTest() to reformat the flash file system.
 *
 * Revision 1.26  2006/10/11 20:59:24  swift
 * Integrated the new flashio file system and implemented the module-wise
 * debugging facility.
 *
 * Revision 1.25  2006/08/17 21:17:59  swift
 * Modifications to allow better logging control.
 *
 * Revision 1.24  2006/05/19 19:27:36  swift
 * Modifications to account for upgraded LBT9522A firmware.
 *
 * Revision 1.23  2006/05/15 18:55:34  swift
 * Changed SelfTest() to reflect new version of SBE41CP firmware 1.1c.
 *
 * Revision 1.22  2006/04/21 13:45:42  swift
 * Changed copyright attribute.
 *
 * Revision 1.21  2006/02/22 21:55:53  swift
 * Added biographical checks of the LBT to the self test.
 *
 * Revision 1.20  2006/01/13 20:40:18  swift
 * Modified expected version of SBE41CP firmware from 1.0c to 1.1a.
 *
 * Revision 1.19  2006/01/06 23:24:10  swift
 * Added GPS configuration to the SelfTest() function.
 *
 * Revision 1.18  2005/10/12 20:11:46  swift
 * Modifications to account for changes to SBE41CP API.
 *
 * Revision 1.17  2005/10/11 20:44:44  swift
 * Implemented pressure-activation of iridium floats.
 *
 * Revision 1.16  2005/09/02 22:20:26  swift
 * Added status bit-mask and logentries for shallow-water trap.
 *
 * Revision 1.15  2005/08/06 21:42:19  swift
 * Modifications to properly handle recover mode during the mission prelude as
 * well as during the mission.
 *
 * Revision 1.14  2005/07/05 22:00:14  swift
 * Added modem configuration to the self-test.
 *
 * Revision 1.13  2005/05/01 22:04:02  swift
 * Implement a feature to execute a deep profile immediately after the mission
 * prelude.
 *
 * Revision 1.12  2005/05/01 14:29:58  swift
 * Added two steps to the prelaunch self-test: Sbe41cp configuration and
 * Sbe41cp firmware validation.
 *
 * Revision 1.11  2005/02/22 21:03:06  swift
 * Minor changes to alarm times to account for HeartBeat.
 *
 * Revision 1.10  2005/01/06 00:54:04  swift
 * Modifications to prelude and telemetry phases.
 *
 * Revision 1.9  2004/12/29 23:11:27  swift
 * Modified LogEntry() to use strings stored in the CODE segment.  This saves
 * lots of space in the DATA segment and significantly speeds code start-up.
 *
 * Revision 1.8  2004/04/26 15:54:46  swift
 * Documentation correction for criteria used to determine a deep profile.
 *
 * Revision 1.7  2004/04/15 23:19:13  swift
 * Added mag-switch reset to LaunchMission().
 *
 * Revision 1.6  2004/04/14 20:43:02  swift
 * Modified the default mission parameters.
 *
 * Revision 1.5  2003/11/20 18:59:47  swift
 * Added GNU Lesser General Public License to source file.
 *
 * Revision 1.4  2003/11/12 22:40:58  swift
 * Added some diagnostic output.
 *
 * Revision 1.3  2003/09/13 14:42:58  swift
 * Added DescentTerminate() and GoDeepTerminate() to more cleanly implement the
 * mission sequencing model.
 *
 * Revision 1.2  2003/09/11 23:14:56  swift
 * Documentation added.
 *
 * Revision 1.1  2003/08/26 20:17:47  swift
 * Initial revision
 * \end{verbatim}
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
#define ControlChangeLog "$RCSfile: control.c,v $  $Revision: 1.51 $   $Date: 2011/02/07 15:54:14 $"

#include <time.h>
#include <profile.h>
#include <config.h>

/*------------------------------------------------------------------------*/
/* structure to contain engineering data for a single profile             */
/*------------------------------------------------------------------------*/
struct EngineeringData
{
   #define       ParkDescentPMax 9
   unsigned char ActiveBallastAdjustments;
   unsigned char AirBladderPressure;
   unsigned char AirPumpAmps;
   unsigned char AirPumpVolts;
   time_t        BuoyancyPumpOnTime;
   unsigned char BuoyancyPumpAmps;
   unsigned char BuoyancyPumpVolts;
   unsigned char ConnectionAttempts;
   unsigned char Connections;
   time_t        GpsFixTime;
   unsigned char ParkDescentP[ParkDescentPMax];
   unsigned char ParkDescentPCnt;
   char          ParkPOutOfBand;
   struct   Obs  ParkObs;
   unsigned int  ObsIndex;
   unsigned char QuiescentAmps;
   unsigned char QuiescentVolts;
   time_t        RtcSkew;
   unsigned char Sbe41cpAmps;
   unsigned long Sbe41cpStatus;
   unsigned char Sbe41cpVolts;
   unsigned long status;
   unsigned char SurfacePistonPosition;
   float         SurfacePressure;
   unsigned char Vacuum;
};

/* definition of the 'status' bits in the engineering data above  */
#define DeepPrf            0x00000001UL
#define ShallowWaterTrap   0x00000002UL
#define Obs25Min           0x00000004UL
#define PistonFullExt      0x00000008UL
#define AscentTimeOut      0x00000010UL
#define DownLoadCfg        0x00000020UL
#define BadSeqPnt          0x00000040UL
#define Sbe41cpPFail       0x00000200UL
#define Sbe41cpPtFail      0x00000400UL
#define Sbe41cpPtsFail     0x00000800UL
#define Sbe41cpPUnreliable 0x00001000UL
#define AirSysLimit        0x00002000UL
#define WatchDogAlarm      0x00004000UL
#define PrfIdOverflow      0x00008000UL

/* definition of the Sbe41cpStatus bits in the engineering data above */
#define Sbe41cpPPedanticExceptn    0x00000001UL
#define Sbe41cpPPedanticFail       0x00000002UL
#define Sbe41cpPRegexFail          0x00000004UL
#define Sbe41cpPNullArg            0x00000008UL
#define Sbe41cpPRegExceptn         0x00000010UL
#define Sbe41cpPNoResponse         0x00000020UL
#define Sbe41cpPUncaughtExceptn    0x00000040UL
#define Sbe41cpPDivPts             0x00000080UL
#define Sbe41cpPtPedanticExceptn   0x00000100UL
#define Sbe41cpPtPedanticFail      0x00000200UL
#define Sbe41cpPtRegexFail         0x00000400UL
#define Sbe41cpPtNullArg           0x00000800UL
#define Sbe41cpPtRegExceptn        0x00001000UL
#define Sbe41cpPtNoResponse        0x00002000UL
#define Sbe41cpPtUncaughtExceptn   0x00004000UL
#define Sbe41cpPtsPedanticExceptn  0x00008000UL
#define Sbe41cpPtsPedanticFail     0x00010000UL
#define Sbe41cpPtsRegexFail        0x00020000UL
#define Sbe41cpPtsNullArg          0x00040000UL
#define Sbe41cpPtsRegExceptn       0x00080000UL
#define Sbe41cpPtsNoResponse       0x00100000UL
#define Sbe41cpPtsUncaughtExceptn  0x00200000UL
#define Sbe41cpCpActive            0x10000000UL

/* define the various states that a float can be in */
enum State {UNDEFINED=-3, PACTIVATE, RECOVERY, INACTIVE, PRELUDE, DESCENT,
            PARK, GODEEP, PROFILE, TELEMETRY, EOS};

/* function prototypes */
int    DeepProfile(void);
void   InitVitals(void);
void   MissionControlAgent(void);
int    MissionLaunch(void);
int    MissionKill(void);
int    MissionValidate(void);
void   PowerOff(time_t AlarmSec);
int    SelfTest(void);
void   SequencePointsDisplay(void);
int    SequencePointsValidate(void);
time_t ToD(time_t Tref,time_t TimeDown, time_t DayMin);

/* store engineering data in persistent far ram */
extern persistent far struct EngineeringData vitals;

#endif /* CONTROL_H */

#include <eeprom.h>
#include <logger.h>
#include <crc16bit.h>
#include <ds2404.h>
#include <apf9.h>
#include <apf9icom.h>
#include <garmin.h>
#include <lbt9522.h>
#include <lt1598ad.h>
#include <engine.h>
#include <inrange.h>
#include <unistd.h>
#include <snprintf.h>
#include <sbe41cp.h>
#include <nan.h>
#include <prelude.h>
#include <pactivat.h>
#include <descent.h>
#include <godeep.h>
#include <park.h>
#include <profile.h>
#include <telemetr.h>
#include <pactivat.h>
#include <recovery.h>
#include <string.h>
#include <tc58v64.h>
#include <flashio.h>
#include <stdlib.h>

/* declare functions with external linkage */
int KbdEscRequest(void);

/*------------------------------------------------------------------------*/
/* structure to contain the time-based sequence points                    */
/*------------------------------------------------------------------------*/
persistent static struct
{
      time_t Descent;
      time_t Park;
      time_t GoDeep;
      time_t Profile;
      time_t Telemetry;
} SeqPoint;

/* define structure to contain engineering data */
persistent far struct EngineeringData vitals;

/*------------------------------------------------------------------------*/
/* function to test if the current profile is a deep profile              */
/*------------------------------------------------------------------------*/
/**
   This function tests for a deep profile based on the current profile
   number.   The PnP mode is enabled only if the cycle length is in the
   closed interval [1,253].  Set PnpCycleLength=254 in order to disable PnP
   mode.  If PnP mode is enabled then this function returns a positive value
   if the current profile is a deep profile; otherwise zero is returned.
   The first profile is a deep profile as well as all profile ids that are
   evenly divisible by PnpCycleLength.
*/
int DeepProfile(void)
{
   /* initialize return status */
   int status=0;

   /* get the current profile id */
   int PrfId=PrfIdGet();

   /* check if PnP mode is enabled */
   if (mission.PnpCycleLength>0 && mission.PnpCycleLength<254) 
   {
      /* evaluate criteria for a deep profile */
      status=((!(PrfId%mission.PnpCycleLength)) || PrfId==1) ? 1 : 0;
   }
   
   return status;
}

/*------------------------------------------------------------------------*/
/* function to initialize the engineering data                            */
/*------------------------------------------------------------------------*/
void InitVitals(void)
{
   unsigned int i;
   far unsigned char *b;
    
   /* initialize the engineering data to zeros */
   for (b=(far unsigned char *)&vitals, i=0; i<sizeof(vitals); i++) {b[i]=0;}

   /* initialize the error counters in the flash file system */
   Tc58v64ErrorsInit();
}

/*------------------------------------------------------------------------*/
/* main control loop and sequence point enforcement                       */
/*------------------------------------------------------------------------*/
/**
   This function constitutes the main control loop for detecting and
   enforcing phase transitions at sequence points.
*/
void MissionControlAgent(void)
{ 
   /* define the logging signature */
   static cc FuncName[] = "MissionControlAgent()";

   /* get the sequence time */
   time_t SeqTime = itimer();
   
   /* define the default alarm time */
   time_t alarm=itimer()+HeartBeat;

   /* get the current state of the mission */
   enum State state = StateGet();

   /* check if the watch-dog alarm indicates a power-down exception */
   if (Apf9WakeUpByRtc()&Ds2404RtfMask) vitals.status|=WatchDogAlarm;

   /* apply criteria for sequence point at the end of the mission prelude */
   if (state==PRELUDE && SeqTime>=mission.TimePrelude)
   {
      /* initiate final telemetry before the mission starts */
      PreludeTerminate(); DescentInit();

      /* arrange for a profile immediately after the mission prelude */
      SeqPoint.Descent   = (mission.TimeParkDescent>5*Min) ? mission.TimeParkDescent : 5*Min;
      SeqPoint.Park      = SeqPoint.Descent + 5*Min;
      SeqPoint.GoDeep    = SeqPoint.Park + ((DeepProfile()>0) ? mission.TimeDeepProfileDescent : 0); 
      SeqPoint.Profile   = SeqPoint.GoDeep + mission.TimeOutAscent;
      SeqPoint.Telemetry = SeqPoint.GoDeep + mission.TimeUp;
   }
   
   /* apply criteria for sequence point at the end of the descent phase */
   else if (state==DESCENT && SeqTime>=SeqPoint.Descent)
   {
      /* terminate the descent phase */
      DescentTerminate();

      /* initialize the park phase */
      ParkInit();
   }

   /* apply criteria for sequence point at the end of the park phase */
   else if (state==PARK && SeqTime>=SeqPoint.Park)
   {
      /* execute the tasks at the end of the park phase */
      ParkTerminate();

      /* sequence point depends whether this is a deep profile */
      (DeepProfile()>0) ? GoDeepInit() : ProfileInit();
   }
   
   /* apply criteria for sequence point at the end of the deep-descent phase */
   else if (state==GODEEP && SeqTime>=SeqPoint.GoDeep)
   {
      /* terminate the deep-descent phase */
      GoDeepTerminate(RETRACT);
      
      /* transition to the profile phase */
      ProfileInit();
   }
   
   /* apply criteria for sequence point at the end of the profile phase */
   else if (state==PROFILE && SeqTime>=SeqPoint.Profile)
   {
      /* create the message */
      static cc msg[]="Ascent time-out exceeded; aborting profile.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);

      /* record that the ascent period timed out */
      vitals.status|=AscentTimeOut;

      /* record the piston position */
      vitals.SurfacePistonPosition=PistonPosition();

      /* terminate the profile */
      ProfileTerminate(); 
      
      /* move the piston to full extension */
      PistonMoveAbs(mission.PistonFullExtension);

      /* transition to the telemetry phase */
      TelemetryInit();
   }
   
   /* apply criteria for sequence point at the end of the telemetry phase */
   else if (state==TELEMETRY && SeqTime>=SeqPoint.Telemetry)
   {
      /* terminate the telemetry phase and initiate the descent phase */
      TelemetryTerminate(); DescentInit();
   }

   
   /* mission state might have changed; read from EEPROM */
   switch (StateGet())
   {
      /* sleep for six hours at time in hibernate mode */
      case INACTIVE:  {alarm=Apf9WakeTime()+6*Hour; break;}

      case PACTIVATE:
      {
         /* execute the pressure-activation monitor */
         if (PActivate()>0) alarm = itimer()+HeartBeat;

         else
         {
            /* get the current mission time */
            SeqTime=itimer();
         
            /* compute the next regularly scheduled alarm-time */
            alarm = SeqTime - SeqTime%(2*Hour) + (2*Hour);
         }
         
         break;
      }
  
      case PRELUDE:
      {
         /* execute the regularly scheduled tasks for the mission prelude */
         Prelude(); SeqTime=itimer()+HeartBeat;
         
         /* compute the next regularly scheduled alarm-time */
         alarm = SeqTime + mission.TimeTelemetryRetry;

         /* ensure regularly scheduled alarm-time doesn't exceed the sequence point */
         if (alarm>mission.TimePrelude) alarm=mission.TimePrelude + HeartBeat;
         
         break;
      }

      case DESCENT:
      {
         /* execute the regularly scheduled tasks for the descent phase */
         Descent(); SeqTime=itimer();
         
         /* compute the next regularly scheduled alarm-time */
         alarm = SeqTime - SeqTime%Hour + Hour;

         /* ensure regularly scheduled alarm-time doesn't exceed the sequence point */
         if (alarm>SeqPoint.Descent) alarm=SeqPoint.Descent + HeartBeat;

         break;
      }

      case PARK:
      {
         /* execute the regularly scheduled tasks for the park phase */
         Park(); SeqTime=itimer();
           
         /* compute the next regularly scheduled alarm-time */
         alarm = SeqTime - SeqTime%Hour + Hour;
         
         /* ensure regularly scheduled alarm-time doesn't exceed the sequence point */
         if (alarm>SeqPoint.Park) alarm=SeqPoint.Park + HeartBeat;

         break;
      }

      case GODEEP:
      {
         /* execute the regularly scheduled tasks for the deep-descent phase */
         if (GoDeep()>0) {SeqTime=itimer()+HeartBeat; alarm = SeqTime - SeqTime%(5*Min) + (5*Min);}

         /* transition to profile phase was detected; start profile in one heartbeat */
         else alarm = (itimer()+HeartBeat);
         
         /* ensure regularly scheduled alarm-time doesn't exceed the sequence point */
         if (alarm>SeqPoint.GoDeep) alarm=SeqPoint.GoDeep + HeartBeat;

         break;
      }
      
      /* execute the regularly scheduled tasks for the profile phase */
      case PROFILE:
      {
         /* execute the profile monitor */
         time_t sleep=Profile();

         /* set the next alarm time based on Profile()'s recommended sleep period */
         alarm = itimer() + ((sleep>=HeartBeat && sleep<=15*Min) ? sleep : HeartBeat);

         /* ensure regularly scheduled alarm-time doesn't exceed the sequence point */
         if (alarm>SeqPoint.Profile) alarm=SeqPoint.Profile + HeartBeat;
         
         break;
      }

      case TELEMETRY:
      {
         /* successful completion of telemetry is an event-based sequence point */
         if (Telemetry()>0) {TelemetryTerminate(); DescentInit(); alarm=itimer()+HeartBeat;}
         
         /* execute the regularly scheduled tasks for the telemetry phase */
         else
         {
            /* get the sequence time */ 
            SeqTime=itimer()+HeartBeat;
            
            /* compute the next regularly scheduled alarm-time */
            alarm = SeqTime + mission.TimeTelemetryRetry;
                          
            /* ensure regularly scheduled alarm-time doesn't exceed the sequence point */
            if (alarm>SeqPoint.Telemetry) alarm=SeqPoint.Telemetry + HeartBeat;
         }
         
         break;
      }

      case RECOVERY:
      {
         /* execute the regularly scheduled tasks for the recovery phase */
         Recovery();

         /* check if mission configuration was successfully downloaded */
         if (!(vitals.status&DownLoadCfg))
         {
            /* create the message */
            static cc msg[]="Terminating recovery mode.\n";
            
            /* process the configuration file */
            configure(&mission,config_path);

            /* terminate the recovery mode */
            LogEntry(FuncName,msg); RecoveryTerminate(); alarm=itimer()+HeartBeat;
         }
         else
         {
            /* get the sequence time */ 
            SeqTime=itimer()+HeartBeat; 
             
            /* compute the next regularly scheduled alarm-time */
            alarm = SeqTime + mission.TimeTelemetryRetry;
         }

         break;
      }

      /* confused state; restart the mission */
      case UNDEFINED: default:
      {
         /* create a logentry */
         static cc msg[]="Logic error - uncaught state[%d].  Restarting mission.\n";
         LogEntry(FuncName,msg,StateGet());

         /* restart mission */
         PreludeInit(); alarm=itimer()+HeartBeat;
      }
   }

   /* set the alarm and go to sleep */
   PowerOff(alarm);
}

/*------------------------------------------------------------------------*/
/* function to launch a new mission                                       */
/*------------------------------------------------------------------------*/
/**
   This function launches the float on a new mission.
*/
int MissionLaunch(void)
{
   /* define the logging signature */
   static cc FuncName[] = "MissionLaunch()";

   int i,status=0;
   unsigned char VCnt,ACnt;
   
   /* reset the magnetic reset-switch */
   MagSwitchReset();

   /* open the air-valve so that self-test measures internal vacuum */
   AirValveOpen();
   
   /* run the air pump for 1 second as an audible startup signal */
   AirPumpRun(1,&VCnt,&ACnt); 

   /* save the air pump current and volts */
   vitals.AirPumpVolts=VCnt; vitals.AirPumpAmps=ACnt;

   /* open the air-valve again and wait a few seconds for bladder to deflate */
   AirValveOpen(); sleep(10);
      
   /* create a log file name */
   snprintf(log_path,sizeof(log_path),"%04d.000.log",mission.FloatId);

   /* format the file system and open the log file */
   LogClose(); fformat(); LogOpen(log_path,'w');

   /* run the self-test before the mission */
   if (SelfTest()<=0)
   {
      /* create the message */
      static cc msg[]="Self-test failed - aborting mission.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);

      /* kill the mission */
      MissionKill();

      /* signal that the self-test failed */
      if (ConioActive()) Apf9Squawk20mA(10000);
   }

   else
   {
      /* create the message */
      static cc msg[]="Self-test passed - initiating six 4-second squawks & mission prelude.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);

      /* signal that the self-test passed */
      if (ConioActive()) {for (i=0; i<6; i++) {Apf9Squawk20mA(4000); Wait(4000);}}

      /* initialize the mission prelude */
      status=PreludeInit();
   }
   
   return status;
}

/*------------------------------------------------------------------------*/
/* function to kill the current mission                                   */
/*------------------------------------------------------------------------*/
/**
   This function kills the current mission.
*/
int MissionKill(void)
{
   StateSet(PACTIVATE);

   /* execute a command to stop any active SBE41cp profile */
   if (vitals.Sbe41cpStatus&Sbe41cpCpActive) Sbe41cpStopCP();
   
   /* read the mission parameters from EEPROM */
   if (!MissionParametersRead(&mission))
   {
      /* copy the default mission to the active mission */
      mission=DefaultMission;
   }

   /* make sure the air valve is open */
   AirValveOpen();

   return 1;
}

/*------------------------------------------------------------------------*/
/* compute the mission signature to validate the mission stored in RAM    */
/*------------------------------------------------------------------------*/
/**
   This function computes the mission signature and compares it to the
   mission stored in RAM in order to validate the mission configuration.
   If the computed signature matches that stored in RAM then the mission is
   valid.  If the match fails then the mission is read from EEPROM.  If the
   mission stored in EEPROM is invalid then a default mission is assigned.

   This function returns a positive number on success and zero otherwise.
*/
int MissionValidate(void)
{
   /* define the logging signature */
   static cc FuncName[] = "MissionValidate()";

   int status=1;

   /* compute the mission signature */
   unsigned int crc = Crc16Bit((unsigned char *)(&mission), sizeof(mission)-sizeof(mission.crc));

   /* check if the computed signature matches the signature stored in the mission object */
   if (crc!=mission.crc)
   {
      /* create the message */
      static cc msg[]="Reading mission program from EEPROM.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);

      /* read mission parameters from EEPROM */
      if (!MissionParametersRead(&mission))
      {
         /* create the message */
         static cc msg[]="Failed --- no mission stored in EEPROM.\n";

         /* make the logentry */
         LogEntry(FuncName,msg);

         /* indicate that the mission could not be read from EEPROM */
         status=0;
      }
   }
   
   return status;
}

/*------------------------------------------------------------------------*/
/* function to properly power-down the APF9 controller                    */
/*------------------------------------------------------------------------*/
void PowerOff(time_t AlarmSec)
{
   /* check if the piston movement was aborted with a magent swipe */
   if (MagSwitchToggled())
   {
      int i; time_t To=time(NULL);

      /* pause (for up to 5s) until the mag-switch is low for 500ms */
      do {for (i=0; i<5; i++) {Wait(100); if (MagSwitchHigh()) break;}}
      while (i<5 && difftime(time(NULL),To)<5);
         
      /* re-initialize the mag-switch flip-flop */
      MagSwitchReset();
   }
   
   /* power down the APFx controller */
   Apf9PowerOff(AlarmSec);
}

/*------------------------------------------------------------------------*/
/* function to execute a self-test                                        */
/*------------------------------------------------------------------------*/
/**
   This function executes a self-test and returns a positive value if the
   self-test passed.  Zero is returned if the self-test failed.
*/
int SelfTest(void)
{
   /* define the logging signature */
   static cc FuncName[] = "SelfTest()";

   /* define message for keyboard escape requests */
   static cc AbortMsg[]="Request detected to abort self-test.\n";

   /* initialize the return value */
   int sbe,status = 1; float p; char buf[32];
   
   /* determine the positions for the piston-movement test */
   int P1=PistonPosition(),P2=(P1>(mission.PistonFullExtension/2))?(P1-4):(P1+4);

   /* define the required SBE41 firmware */
   const char *Sbe41cpRev="2.0";

   /* define the recommended LBT model */
   const char *LbtModel="9522B";

   /* define the recommended LBT firmware revision */
   const char *LbtFwRev="ST10001";

   /* set the maximum log size to 40kb */
   MaxLogSize=40960L;

   /* make a logentry about isus power-up initiation */
   {static cc msg[]="Executing self-test for ApfId %04u.\n"; LogEntry(FuncName,msg,mission.FloatId);}

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* execute a timed piston movement back and forth */
   if (PistonMoveAbsWTO(P2,NULL,NULL,30)>0 && PistonMoveAbsWTO(P1,NULL,NULL,30)>0)
   {
      static cc msg[]="Passed: Timed piston movements from %d to %d counts and back.\n";
      LogEntry(FuncName,msg,P1,P2);
   }
   else 
   {
      static cc msg[]="Failed: Timed piston movement from %d to %d counts and back.\n";
      LogEntry(FuncName,msg,P1,P2); status=0;
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* verify that the internal vacuum satisfies safe criteria */
   if ((vitals.Vacuum=BarometerAd8())>mission.OkVacuumCount)
   {
      /* create the message */
      static cc format[]="Failed: internal pressure [%d, %0.1f\"Hg] "
         "exceeds threshold [%d, %0.1f\"Hg].\n";

      /* make the logentry */
      LogEntry(FuncName,format,vitals.Vacuum,inHg(vitals.Vacuum),
               mission.OkVacuumCount,inHg(mission.OkVacuumCount));

      /* indicate failure */
      status=0;
   }
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the message */
      static cc format[]="Passed: internal pressure [%d, %0.1f\"Hg] less "
         "than threshold [%d, %0.1f\"Hg].\n";

      /* make the logentry */
      LogEntry(FuncName,format,vitals.Vacuum,inHg(vitals.Vacuum),
               mission.OkVacuumCount,inHg(mission.OkVacuumCount));
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* read the FLASH bad-block list from EEPROM */
   Tc58v64BadBlockListGet();

   /* check if the flash file system has been initialized */
   if (BadBlockCrc==Tc58v64BadBlockCrc())
   {
      static cc msg[]="Passed: FLASH file system has been initialized.\n";
      LogEntry(FuncName,msg);
   }
   else
   {
      static cc msg[]="Warning: FLASH file system has not been initialized.\n";
      LogEntry(FuncName,msg);
   }
   
   /* reformat the FLASH file system */
   if (fioFormat()>0)
   {
      static cc msg[]="Passed: FLASH file system has been reformatted.\n";
      LogEntry(FuncName,msg);
   }
   else
   {
      static cc msg[]="Failed: Attempt to reformat FLASH file system failed.\n";
      LogEntry(FuncName,msg);

      /* indicate failure */
      status=0;
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* verify successful SBE41 communications */
   if (GetP(&p)<=0)
   {
      /* create the message */
      static cc msg[]="Failed: Response not received from SBE41cp.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);

      /* indicate failure */
      status=0;
   }
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the message */
      static cc msg[]="Passed: Response received from SBE41cp.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);
   }
      
   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* query the Sbe41cp for its serial number */
   if ((sbe=Sbe41cpSerialNumber())<=0)
   {
      /* create the message */
      static cc msg[]="Failed: Query for Sbe41cp serial "
         "number failed.[errcode=%d]\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg,sbe);

      /* indicate failure */
      status=0;
   }

   /* Sbe41cp serial number ok */
   else
   {
      /* create the log message */
      static cc format[]="Passed: Sbe41cp SerNo: %04d\n";

      /* make the log entry */
      LogEntry(FuncName,format,sbe);
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* query the SBE41CP for its firmware revision */
   if (Sbe41cpFwRev(buf,sizeof(buf))<=0)
   {
      /* create the message */
      static cc msg[]="Failed: Query for SBE41cp FwRev failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);

      /* indicate failure */
      status=0;
   }

   /* verify the SBE41CP firmwware */
   else if (strcmp(Sbe41cpRev,buf))
   {
      static cc format[]="Warning: SBE41cp FwRev violation: [%s] != [%s] (expected).\n";
      LogEntry(FuncName,format,buf,Sbe41cpRev);
   }

   /* SBE41CP firmware OK */
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the log message */
      static cc format[]="Passed: SBE41cp (SerNo %04u) FwRev accepted: [%s]\n";

      /* request the SBE41CP serial number */
      int Sbe41cpSerno=Sbe41cpSerialNumber();

      /* make the log entry */
      LogEntry(FuncName,format,Sbe41cpSerno,buf);
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* configure the SBE41CP */
   if (Sbe41cpConfig(2)<=0 || Sbe41cpTsWait(20)<=0)
   {
      /* create the message */
      static cc msg[]="Failed: Attempt to configure the SBE41cp failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);

      /* indicate failure */
      status=0;
   }
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the message */
      static cc msg[]="Passed: SBE41cp configuration was successful.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* power-up the LBT */
   ModemEnable(19200);

   /* check the LBT model */
   if (IrModemModel(&modem,buf,sizeof(buf))<=0)
   {
      /* create the message */
      static cc msg[]="Warning: Query for LBT model failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);
   }

   /* verify the LBT model */
   else if (strcmp(LbtModel,buf))
   {
      static cc format[]="Warning: Unexpected LBT model: [%s] != [%s] (expected).\n";
      LogEntry(FuncName,format,buf,LbtModel);
   }

   /* LBT model OK */
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the log message */
      static cc format[]="Passed: LBT model: %s\n";
      
      /* make the log entry */
      LogEntry(FuncName,format,buf);
   }
    
   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* check the LBT firmware revision */
   if (IrModemFwRev(&modem,buf,sizeof(buf))<=0)
   {
      /* create the message */
      static cc msg[]="Warning: Query for LBT firmware revision failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);
   }

   /* verify the LBT model */
   else if (strcmp(LbtFwRev,buf))
   {
      static cc format[]="Warning: Unexpected LBT firmare: [%s] != [%s] (expected).\n";
      LogEntry(FuncName,format,buf,LbtFwRev);
   }

   /* LBT firmware OK */
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the log message */
      static cc format[]="Passed: LBT firmware revision: %s\n";

      /* make the log entry */
      LogEntry(FuncName,format,buf);
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* configure the LBT */
   if (IrModemConfigure(&modem)<=0)
   {
      /* create the message */
      static cc msg[]="Failed: Attempt to configure the LBT failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);

      /* indicate failure */
      status=0;
   }
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the message */
      static cc msg[]="Passed: LBT configuration was successful.\n";

      /* make the logentry */
      LogEntry(FuncName,msg);
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* check the LBT IMEI */
   if (IrModemImei(&modem,buf,sizeof(buf))<=0)
   {
      /* create the message */
      static cc msg[]="Warning: Query for LBT IMEI failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);
   }

   /* LBT IMEI OK */
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the log message */
      static cc format[]="Passed: LBT IMEI: %s\n";

      /* make the log entry */
      LogEntry(FuncName,format,buf);
   }

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* query the SIM ICCID number */
   if (IrModemIccid(&modem,buf,sizeof(buf))<=0)
   {
      /* create the message */
      static cc msg[]="Warning: Query for SIM card ICCID failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);
   }

   /* SIM ICCID numbers OK */
   else if (debuglevel>=2 || (debugbits&CONTROL_H))
   {
      /* create the log message */
      static cc format[]="Passed: SIM card ICCID: %s\n";

      /* make the log entry */
      LogEntry(FuncName,format,buf);
   }

   /* power-down the LBT */
   ModemDisable();

   /* check for keyboard escape request */
   if (KbdEscRequest()>0) {LogEntry(FuncName,AbortMsg); status=0; goto Err;}

   /* power-up the GPS */
   GpsEnable(4800);

   if (ConfigGarmin15(&gps)<=0)
   {
      /* create the message */
      static cc msg[]="Failed: Attempt to configure GPS failed.\n";
 
      /* make the logentry */
      LogEntry(FuncName,msg);

      /* indicate failure */
      status=0;
   }

   /* power-down the GPS */
   GpsDisable();

   /* collection point for keyboard escape */
  Err: ModemDisable();
   
   sleep(1);

   return status;
}
 
/*------------------------------------------------------------------------*/
/* function to display the time-based sequence points                     */
/*------------------------------------------------------------------------*/
/**
   This function displays the time-based sequence points.
*/
void SequencePointsDisplay(void)
{
   char buf[256],*phase;
   enum State state = StateGet();

   /* create a string that describes the current mission state */
   switch (state)
   {
      case INACTIVE:  {phase="Hibernate."; break;}
      case PACTIVATE: {phase="Pressure-activation."; break;}
      case RECOVERY:  {phase="Recovery."; break;}
      case PRELUDE:   {phase="Mission Prelude."; break;}
      case DESCENT:   {phase="Park Descent."; break;}
      case PARK:      {phase="Park."; break;}
      case GODEEP:    {phase="Profile Descent."; break;}
      case PROFILE:   {phase="Profile."; break;}
      case TELEMETRY: {phase="Telemetry."; break;}
      case UNDEFINED: {phase="Undefined."; break;}
      default:        {phase="Indeterminant."; break;}
   }

   /* create a string with the mission time, alarm time, and mission phase */
   snprintf(buf,sizeof(buf),"Mission time: %ldsec   Alarm time: %ldsec\n"
            "Phase of mission cycle: %s",itimer(),ialarm(),phase);

   /* write the mission time, alarm time, and mission phase to the console */
   pputs(&conio,buf,2,"\n");
   
   /* create a report for an active mission */
   if (state>INACTIVE && state<EOS)
   {
      /* create a report for the mission prelude */
      if (state==PRELUDE) {snprintf(buf,sizeof(buf),"Sequence Point: Mission "
                                    "prelude at %ldsec.",mission.TimePrelude);}
   
      /* create a report for an active mission */
      else
      {
         snprintf(buf,sizeof(buf),"Sequence Points:\n"
                  "   Park descent:    %6ldsec\n"
                  "   Park:            %6ldsec\n" 
                  "   Profile descent: %6ldsec\n" 
                  "   Profile[%4s]:   %6ldsec\n" 
                  "   Telemetry:       %6ldsec",
                  SeqPoint.Descent,SeqPoint.Park,SeqPoint.GoDeep,
                  ((DeepProfile()>0)?"Deep":"Park"),SeqPoint.Profile,
                  SeqPoint.Telemetry);
      }
      
      /* write the report to the console */
      pputs(&conio,buf,2,"\n");
   }
}

/*------------------------------------------------------------------------*/
/* function to validate the sequence points                               */
/*------------------------------------------------------------------------*/
/**
   This function verifies that sequence points satisfies ordering
   constraints.  If the ordering constraints are violated, this function
   adjusts the times of the sequence points as necessary.
*/
int SequencePointsValidate(void)
{
   /* define the logging signature */
   static cc FuncName[] = "SequencePointsValidate()";

   /* initialize the return value */
   int status=1;

   /* initialize the mission down-time */
   time_t TimeDown=mission.TimeDown;

   /* check if down-time should end at a user-specified time-of-day */
   if (inRange(0,mission.ToD,Day)) TimeDown=ToD(time(NULL),mission.TimeDown,mission.ToD);

   /* compute the sequence points */
   SeqPoint.Descent   = mission.TimeParkDescent;
   SeqPoint.Park      = TimeDown - ((DeepProfile()>0) ? mission.TimeDeepProfileDescent : 0); 
   SeqPoint.GoDeep    = TimeDown;
   SeqPoint.Profile   = TimeDown + mission.TimeOutAscent;
   SeqPoint.Telemetry = TimeDown + mission.TimeUp;
   
   /* validate the Profile sequence point */
   if (SeqPoint.Profile>SeqPoint.Telemetry)
   {
      /* create the message */
      static cc format[]="Invalid sequence points: "
         "Profile[%ldsec]>Telemetry[%ldsec] (rectified).\n";

      /* make the logentry */
      LogEntry(FuncName,format,SeqPoint.Profile,SeqPoint.Telemetry);
      
      /* recompute valid sequence points */
      SeqPoint.Profile = TimeDown + mission.TimeUp/2; vitals.status|=BadSeqPnt;
   }
   
   /* validate the Park sequence point */
   if (SeqPoint.Park>SeqPoint.GoDeep)
   {
      /* create the message */
      static cc format[]="Invalid sequence points: "
         "Park[%ldsec]>GoDeep[%ldsec] (rectified).\n";

      /* make the logentry */
      LogEntry(FuncName,format,SeqPoint.Park,SeqPoint.GoDeep);
      
      /* recompute valid sequence points */
      SeqPoint.Park=TimeDown; vitals.status|=BadSeqPnt;
   }
   
   /* validate the Descent sequence point */
   if (SeqPoint.Descent>SeqPoint.Park)
   {
      /* create the message */
      static cc format[]="Invalid sequence points: "
         "Descent[%ldsec]>Park[%ldsec] (rectified).\n";

      /* make the logentry */
      LogEntry(FuncName,format,SeqPoint.Descent,SeqPoint.Park);
      
      /* recompute valid sequence points */
      SeqPoint.Descent = SeqPoint.Park/2; vitals.status|=BadSeqPnt;
   }

   return status;
}

/*------------------------------------------------------------------------*/
/* adjust down-time to start profile at user specified time-of-day        */
/*------------------------------------------------------------------------*/
time_t ToD(time_t Tref,time_t TimeDown, time_t DaySec)
{
   /* validate the function arguments */
   if (TimeDown>=0 && inRange(0,DaySec,Day)) 
   {
      /* compute the estimated date/time for the start of the profile */
      time_t skew,daysec,t=Tref+TimeDown; struct tm T; T = *gmtime(&t);

      /* compute the seconds past midnight for the estimated start time */
      daysec=T.tm_sec + T.tm_min*Min + T.tm_hour*Hour;

      /* validate that the skew is less than one day */
      if (labs((skew=(DaySec-daysec)))<Day)
      {
         /* adjust down-time to end at specified seconds after midnight */
         TimeDown += skew; if (skew<0) TimeDown += Day;
      }
   }
   
   return TimeDown;
}
