Gen 3.2 Agent

From Seamonster

Jump to: navigation, search

Gen 3.2

Gen 3.1

Microcontroller-related

Other Microserver-Related


Contents

Introduction

The Gen 3.2 Agent program (formerly 'milo' in Gen 3.1) runs on boot and tries to accommodate the operating plan:

  • Ensure that the communication with the microcontroller is working
  • If the battery runs down: Notice this, broadcast a remark, hibernate for awhile
  • Stay awake near the top of the hour to capture any streaming serial data
  • Stay awake near the top of every N hours to do WiFi data updates
  • If certain "signal" files are discovered in /home/brick/uploads, respond appropriately
  • Save as much energy as possible by sleeping when there is nothing to do


How Agent Works

As usual I will use program variable names in this description to facilitate a transition to reading the source code.


The time-model for Agent is hourly: Tasks are done around the top of each hour (perhaps) and any sleeping will last until the top of the subsequent hour. To that end we use the variable thisHour to indicate the hour we are currently working against. For example if the system wakes up at 7:42 or at 8:09, thisHour will be 8. The dividing line is at 30 minutes past the hour.


Agent has two "modes" that work more or less independently of one another: modeAwake and modeRadio. If modeAwake = 1 then the Agent will periodically put the system to sleep, always with the idea of waking up just prior to the top of the next hour. If modeAwake = 0 then the system will stay on perpetually; no sleeping cycles. Correspondingly in the second mode: If modeRadio = 1 then the WiFi power will be turned on at the top of every radioHourModulus hours. If radioHourModulus is set to 3 then the WiFi will be powered up at 0, 3, 6, 9, 12, 15, 18, and 21 hours.

Installing the Agent

  • On NSRL1 (or other computer) checkout 'Gen32_src'
   svn co http://seamonster.jun.alaska.edu/svn/seamonster/Gen32_src
  • Move the file to the uS (uS should be on the UAS network, ## stands for uS id)
   scp Agent.* brick@137.229.208.1##:./src 
  • Log into the uS
   ssh root@137.229.208.1##
  • Append this entry to /home/brick/src/makefile
   Agent: Agent.c Agent.h 
       $(CC) -o ../bin/Agent Agent.c
  • Compile the Agent
   make Agent
  • Copy the executable file to /root/bin (as root)
   cp /home/brick/bin/Agent /root/bin/
   chown root:root /root/bin/Agent
   chmod a+x /root/bin/Agent
  • See the instructions in Gen 3.2 SBC Congiguration under '3.1.6 Config SBC 1.6: Configure a task manager to run on boot' for further instructions on getting the Agent program to run on boot.

To Do and To Incorporate into this page

  • Add power conversion: Voltage is approx 0.0669 * ADC-value
  • Cla example translates:
    • 1 Do sleep/wake/sleep/wake
    • 1 Do radio
    • 6 Turn on the local bridge, not a router
    • 10 Keep the radio on for 10 minutes
    • 0 No time hour offset
    • 4 Radio on every 4 hours
    • 3 Wake up 3 minutes before top of hour for “radioness”
    • 5 Wake up 5 minutes before top of hour for “awakeness” (supersedes previous radioness)
    • 15 Stay on until Hour:15


Add Agent updates:

Logan and I were wondering if it is possible to reconfigure the agent program without having to manually disconnect and reconnect the bricks from the power.


Yes, see below. I suggest setting up a fourth lab brick and testing first manual and then automated updating there.


And we'll have to think about how we want to show the reactivity, do we want to reconfigure the campbells? That would mean we need to find out how to access them via linux.


There’s the intermediate step of having the brick computer “look at” the data without impacting operations. You should do this first and simply react by logging some remarks. For example you can have Agent spawn a background program (use & or something; refer to Matt) that opens a data file, looks at the values, and reports average values from a couple sensors. You’re basically going into uncharted territory so I suggest that your implementation model has as its first priority: “Don’t damage the basic operational flow of the brick.” Low impact camping. As to how to carry that to camera control or modifying Campbell behavior: I have no expertise; this’d make a good conference call subject.


Let me also add this suggestion: An interesting and useful first step would simply be to determine what the external voltage is and report this via some means down to the database. The (approximate) equation to use is Voltage = 0.067 * ADC value – 0.27. The code might look like this, we call ucInterface and then read the ucInterface results file (which consists of four integers, the last one being the ADC value in this instance):


    int adcValue, iTmp;
    float voltage;
    FILE *fp;

    sprintf(cmd, "/root/bin/ucInterface 4 > /root/tmp/ucI.tmp");             Log(cmd);  system(cmd); 
    sprintf(cmd, "cat %s >> %s", uciOutFnm, AgentLogFnm);                               system(cmd);

    fp = fopen(uciOutFnm, “r”); 
    fread(fp, “%d”, &iTmp);
    fread(fp, “%d”, &iTmp);
    fread(fp, “%d”, &iTmp);
    fread(fp, “%d”, &adcValue);
    fclose(fp);
    voltage = 0.067*(float)(adcValue) – 0.27;

    printf (“OMG!!! I’m down to %f volts!!! \n”, voltage);


Are the Vanderbilt people working on this part?


I doubt they are working on Campbell programming but that’d be worth checking with Matt on.


Two stages for Agent update:

1.	Manual
2.	Automated


1. Manual

Since Agent runs in memory, it doesn’t matter what is sitting on the disk, particularly what the contents of the file /root/bin/Agent are (after boot). This means that you can log on to a brick, move over a new Agent (source code, say), compile it, move it to /root/bin/Agent. The next time the system boots the new Agent will run. One possible procedure, supposing the brick is up the hill, would be:


  • Wait for brick radio on
  • Log on to brick over VPN as brick
  • ftp Agent.c and Agent.h to /home/brick/uploads
  • move these files to /home/brick/src
  • Compile to /home/brick/bin
  • Su to root
  • Halt the current Agent process to keep the radio on
  • Move the executable Agent to /root/bin
  • Make sure it is executable
  • Touch the file /home/brick/uploads/firstRadioOn.txt so that a reboot will turn on the radio for 10 minutes
  • Rename /root/tmp/Agent.log so you can start with a fresh log file
    • # mv /root/tmp/Agent.log /root/tmp/Agent.previous.log
  • Use ucInterface to set the sleep interval to 0 days 0 hours 1 minute
    • # ucInterface 5 128
    • # ucInterface 5 64
    • # ucInterface 5 1
  • Use ucInterface to set the current DropDead mode to TRUE
    • # ucInterface 6 255
  • Use ucInterface to set the current DropDead (operational) interval to 0 days 0 hours 2 minutes
    • # ucInterface 8 0
    • # ucInterface 9 0
    • # ucInterface 10 2
  • Issue shutdown –h now
  • Permit the system a couple minutes to shutdown, sleep and reboot
  • Log in and verify that the new Agent is running

2. Automated

Modify Agent to look for Agent.c and/or Agent.h in some expected directory, e.g. /home/brick/uploads. See for example the code already in Agent that looks for /home/brick/uploads/firstRadioOn.txt. If either file exists, execute a build script (perhaps simply as root to make this) that moves the files to /root/src and creates a new /root/bin/Agent executable. Make sure the Agent source code files are no longer at their arrival location (or else this will keep happening). Now Agent has replaced itself with a new version. The way you guys would do this might be to use an rsynch that goes up the hill from a server to a target brick. To engage the new Agent you just need a reboot.


Agent.c

This code is certainly out-of-date. The current version may be found at http://seamonster.jun.alaska.edu/websvn/seamonster. It was originally placed in the Gen32_src repository; may still be there. If so, here are the direct links to Agent.c and Agent.h:


http://seamonster.jun.alaska.edu/websvn/filedetails.php?repname=seamonster&path=%2FGen32_src%2FAgent.c


http://seamonster.jun.alaska.edu/websvn/filedetails.php?repname=seamonster&path=%2FGen32_src%2FAgent.c


Code follows:

// Agent:    Summer 2008 SBC field task manager program
// version:  1.3
// Author:   Rob Fatland
// Rev date: 07/17/2008
//
// Rev notes:
//   Polished up auto-Agent install; maybe it works now; can test.
//     Does an automated Agent have the path to gcc, chown, mv?
//   Installed a camera up/down at the top of the hour.
//
// To Do
//   Use the DB mechanism to record power consumption info (who is on/off when)
//   Examine: What is the actual time density of voltage data recorded once every N inf loop iterations?
//
// This program does not address PORTB / PORTC directly; uses programs
//   'bc' and 'ucInterface'.
// See Agent.h for default parameter values 
// 
// Three state variables apply to both radio and sleep/awake cycles:
//   mode:  Capacity enabled or disabled
//   this:  During this op interval: Use this mode?
//   state: Phase 0 / 1 / 2 (pre / during / after)
//
//   Note: When a mode is disabled, it is by default always ON (SBC / radio)
//
// thisAwake is not used since every hour period the sleep / awake cycling is engaged
// thisRadio is used to indicate whether this hour will include some radio-ON time
//
// stateAwake and stateRadio variables take on 3 values:
//   0: Not yet engaged
//   1: Engaged (inside an operational time interval)
//   2: Dis-engaged (post-op-interval)
//      Once state=2 is reached (assuming modes allow) we are ok to sleep
//
// Program features:
//   10 Minutes Radio On if /home/brick/uploads/firstRadioOn.txt exists
//     (deletes /home/brick/uploads/firstRadioOn.txt)
//
//   Infinite loop
//     Sleep 15 sec
//     SetRestState() (always tries to set RestState; will shutdown if PORTC == 0)
//     get theTime
//
//     Report external voltage if !mod(voltageModulus)
//
//     Process mode-logic-based tasks
//
//   End of infinite loop
//
//   Results are appended to /root/tmp/Agent.log
//

#include "Agent.h"

main(int argc, char **argv)
{
  int  sendType, sendData, receiveType, receiveData;
  int  sleepMinutes;
  char msg[512], *mp;
  char cmd[512];
  FILE *fp;

  myTime tStart, theTime;

  sprintf(tmpDateFnm, "/root/tmp/date.tmp");
  tStart = ParseDate();


  // See if /root/bricknum exists. It should be ascii text with contents = this 
  //   bricks index, like 53 or 54, call this 'N'): If so this brick will turn on 
  //   its external-voltage-monitoring function, reporting external supply voltage 
  //   periodically to ExternalVoltageN.log in the /root/brickmove/inbound directory.
  if (fileExist("/root/bricknum")) {
    fp = fopen("/root/bricknum", "r");
    fscanf(fp, "%d", &bricknum);
    fclose(fp);
    if (bricknum > 0) { doVoltageLog = 1; }
  }


  // If no cla's print usage message 
  if (argc < 4) {
    printf ("                                                   \n");
    printf ("usage: Agent [a [b [c [d [e [f [g [h [i]]]]]]]]]   \n");
    printf ("  where                                            \n");
    printf ("        a = modeAwake,           default %d        \n", modeAwake); 
    printf ("        b = modeRadio,           default %d        \n", modeRadio); 
    printf ("        c = powerOnSignal,       default %d        \n", powerOnSignal); 
    printf ("        d = radioOnMinutes,      default %d        \n", radioOnMinutes); 
    printf ("        e = offsetHours,         default %d        \n", offsetHours); 
    printf ("        f = radioHourModulus,    default %d        \n", radioHourModulus); 
    printf ("        g = preRadioLeadMinutes, default %d        \n", preRadioLeadMinutes); 
    printf ("        h = preAwakeLeadMinutes, default %d        \n", preAwakeLeadMinutes); 
    printf ("        i = awakeOnMinutes,      default %d        \n", awakeOnMinutes); 
    printf ("                                                   \n");
  }

  // try to override the default values if the appropriate cla exists
  int tryOride;
  if (argc > 1) { tryOride = atoi(argv[1]); if (tryOride >=   0 && tryOride <=    1) {           modeAwake = tryOride; } }
  if (argc > 2) { tryOride = atoi(argv[2]); if (tryOride >=   0 && tryOride <=    1) {           modeRadio = tryOride; } }
  if (argc > 3) { tryOride = atoi(argv[3]); if (tryOride >    0 && tryOride <   256) {       powerOnSignal = tryOride; } }
  if (argc > 4) { tryOride = atoi(argv[4]); if (tryOride >=   0 && tryOride <=  240) {      radioOnMinutes = tryOride; } }
  if (argc > 5) { tryOride = atoi(argv[5]); if (tryOride >= -12 && tryOride <=   12) {         offsetHours = tryOride; } }
  if (argc > 6) { tryOride = atoi(argv[6]); if (tryOride >    0 && tryOride <=   24) {    radioHourModulus = tryOride; } }
  if (argc > 7) { tryOride = atoi(argv[7]); if (tryOride >=   0 && tryOride <  2400) { preRadioLeadMinutes = tryOride; } }
  if (argc > 8) { tryOride = atoi(argv[8]); if (tryOride >=   0 && tryOride <=   60) { preAwakeLeadMinutes = tryOride; } }
  if (argc > 9) { tryOride = atoi(argv[9]); if (tryOride >    0 && tryOride <=   60) {      awakeOnMinutes = tryOride; } }
    
  // prelim state variable sets
  thisAwake = modeAwake;                          // not true for thisRadio; that must be calculated from the time
  if (modeRadio && !modeAwake) { thisRadio = 1; } // a formality; variable is not used

  sprintf(AgentLogFnm, "/root/tmp/Agent.log"    );
  sprintf(  uciOutFnm, "/root/tmp/ucInterface.out");

  // Log this instance of Agent including the command line arguments (cla's)
  Log("  ");
  Log("  ");
  mp = msg;
  sprintf(mp, "Agent running ");
  mp = msg + strlen(msg);
  int argCounter;
  for (argCounter = 1; argCounter < argc; argCounter++) {
    sprintf(mp, "%s ", argv[argCounter]);
    mp = msg + strlen(msg);
  }
  Log(msg); 
  LogDate();

  // Before getting into uc-comm: Install any new agents
  //   This must however follow the Log() setup since it does logging.
  InstallAgent();

  // This is a hack-synch:
  //   Set RestState; wait 60 seconds in case (on boot) there is a synch window for the SBC and 
  //     uc to both come to Rest State. (Recall the uc waits for awhile on boot before going 
  //     into its polling loop to avoid the 0xff on PORTB on boot trap.)
  SetRestState();
  sprintf (msg, "  Agent on start: prior PORTC = 0x%x, PORTB = 0x%x, PORTC = 0x%x\n", oldPORTC, PORTB, PORTC);
  Log(msg); 
  sprintf (msg, "  Agent on start: Begin pause 60 sec"); 
  Log(msg); 
  LogDate();
  sleep(60); 
  SetRestState();
  sprintf (msg, "  Agent after start + 60: prior PORTC = 0x%x, PORTB = 0x%x, PORTC = 0x%x", oldPORTC, PORTB, PORTC);
  Log(msg);
  LogDate();

  // See if we are in Rest State; if not, try and fix that
  if (PORTC != 128) {         // Not in Rest State
    if (PORTC == 0xf0) {      // Perceived by uc as SBC-SEND
                              // Go through a 0-128-0-128-0-128 sequence
      system("/root/bin/bc   0 > /root/tmp/bc.out");
      ReadBCDotOut();
      if (PORTC == 0) { 
        system("/root/bin/bc 128 > /root/tmp/bc.out"); 
        ReadBCDotOut();
        if (PORTC == 128) { 
          system("/root/bin/bc   0 > /root/tmp/bc.out");
          ReadBCDotOut();
          if (PORTC == 0) { 
            system("/root/bin/bc 128 > /root/tmp/bc.out");
            ReadBCDotOut();
            if (PORTC == 128) { 
              system("/root/bin/bc   0 > /root/tmp/bc.out");
              ReadBCDotOut();
              if (PORTC == 0) { 
                system("/root/bin/bc 128 > /root/tmp/bc.out");
                ReadBCDotOut();
                if (PORTC == 128) { 
                  system("/root/bin/bc   0 > /root/tmp/bc.out");
                  ReadBCDotOut();
                  if (PORTC == 128) { 
                    system("/root/bin/bc 128 > /root/tmp/bc.out");
                    ReadBCDotOut();
                    if (PORTC == 128) { 
                      Log("Finished a SBC-SEND PORT repair");
                    }
                  }
                }
              }
            }
          }
        }
      }
      else {
        Log("PORTC is 0xf0 and does not budge on bc 0 at time...");
        LogDate();
        Log("...therefore Agent is halting during startup");
        exit(0);
      }
    }
    else if (PORTC < 128) {   // Suspect this is perceived by uc as uc-SEND
      sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
      if (PORTC != PORTB) {
        sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
        if (PORTC != PORTB) {
          sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
          if (PORTC != PORTB) {
            sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
            if (PORTC != PORTB) {
              sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
              if (PORTC != PORTB) {
                sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
                if (PORTC != PORTB) {
                  sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
                  if (PORTC != PORTB) {
                    sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
                    if (PORTC != PORTB) {
                      sprintf(cmd, "/root/bin/bc %d > /root/tmp/bc.out", PORTC); system(cmd); ReadBCDotOut();
                    }
                  }
                }
              }
            }
          }
        }
        if (PORTC == 128) {
          Log("PORTC appears to be in RestState after uc-SEND repair effort at...");
          LogDate();
          Log("...therefore Agent is continuing from startup");
        }
        else {
          Log("PORTC is apparently not recovering from uc-SEND repair effort at time...");
          LogDate();
          Log("...therefore Agent is halting during startup");
          exit(0);
        }
      }
    }
    else {
      Log("PORTC is in an uncrecognized state at ...");
      LogDate();
      Log("...therefore Agent is halting during startup");
      exit(0);
    }
  }
  else {
    sprintf(msg, "Agent PORTC initially ok at 0x%x", PORTC); 
    Log(msg);
  }
  Log("  ");

  
  // Possibly (first time on) turn on the radio for 10 minutes...
  //   and query power state and battery respectively
  if (fileExist("/home/brick/uploads/firstRadioOn.txt")) {
    Log("Init-RADIO-ON due to Exist(/home/brick/uploads/firstRadioOn.txt");
    system("rm /home/brick/uploads/firstRadioOn.txt");
    turn_on_the_radio();
    sprintf(cmd, "cat %s >> %s", uciOutFnm, AgentLogFnm);                               system(cmd);
    sprintf(cmd, "/root/bin/ucInterface 3 > /root/tmp/ucI.tmp");             Log(cmd);  system(cmd); 
    sprintf(cmd, "cat %s >> %s", uciOutFnm, AgentLogFnm);                               system(cmd);
    sprintf(cmd, "/root/bin/ucInterface 4 > /root/tmp/ucI.tmp");             Log(cmd);  system(cmd); 
    sprintf(cmd, "cat %s >> %s", uciOutFnm, AgentLogFnm);                               system(cmd);
    sleep (600);
  }
  else {
    Log("NO Init-RADIO-ON due to NOT Exist(/home/brick/uploads/firstRadioOn.txt");
  }

  if (fileExist("/home/brick/uploads/RunCamera.txt")) { doPeripheral = 1; }

  //
  // These comments describe state logic and behavior
  //   They are implemented inside the infinite loop
  // modeRadio = 0, modeAwake = 0: Always be on, radio always on: 
  //   Inf loop with SetRestState() to guard against power failure
  //     thisX and stateX are not used; both 0
  // modeRadio = 1, modeAwake = 0: Always be on, periodically turn on the radio
  //   Inf loop with SetRestState() 
  //     thisRadio is 1 (since there are no sequential op intervals)
  //     stateRadio will rock between 0 and 1, never 2
  //     thisAwake and stateAwake are not used; both 0
  // modeRadio = 0, modeAwake = 1: Sleep/Awake cycles, radio always on
  //   Inf loop with SetRestState() where
  //     thisRadio, stateRadio = 0, not used
  //     thisAwake = 1, not used
  //     stateAwake starts out at 0, goes to 1, goes to 2, this triggers AgentSleep()
  // modeRadio = 1, modeAwake = 1: Sleep/Awake cycles, Radio On intervals
  //   Inf loop with SetRestState() where
  //     thisAwake = 1 (every hour we have an awake interval)
  //     thisRadio is calculated based on the hour
  //     stateAwake goes from 0 to 1 to 2
  //     if thisRadio == 1 stateRadio goes from 0 to 1 to 2
  //

  // outside inf loop, if modeAwake && modeRadio: do some initialization
  //   This assumes that if the start-up (current) time is > 30 minutes we are 
  //     approaching the top of the hour; otherwise we are already past it.
  //   If we are already past the top of the hour there has been a problem in the
  //     prior sleep interval and we "woke up late"; in this case I have inserted
  //     a stop-gap two-minute radio-on Hold.
  if (modeAwake && modeRadio) {
    theTime = ParseDate();
    if (theTime.minute > 30) { 
                               thisHour = theTime.hour + 1; 
                               while (thisHour > 23) { thisHour -= 24; }
                             }
    else                     { 
                               thisHour = theTime.hour; 
                               turn_on_the_radio(); 
                               stateRadio = 1; 
                               sleep(120); 
                             }
    if (!((offsetHours + thisHour)%radioHourModulus)) { thisRadio = 1; }
    else                                              { thisRadio = 0; }
  }

  int modCounter = -1, iTmp;
  
  // Infinite loop
  do {

    sleep(sleepSec);
    SetRestState();
    theTime = ParseDate();
    UpdatePeripheral(theTime);

    // Deal with voltage reporting
    if (doVoltageLog) {
      modCounter++;
      if (!(modCounter%voltageCheckModulus)) {

        modCounter = 0;

        sprintf(cmd, "/root/bin/ucInterface 4 > /root/tmp/ucI.tmp"); 
        system(cmd); 
        fp = fopen("/root/tmp/ucInterface.out", "r");
        fscanf (fp, "%d", &iTmp);
        fscanf (fp, "%d", &iTmp);
        fscanf (fp, "%d", &iTmp);
        fscanf (fp, "%d", &ADC);
        fclose(fp);
        voltage = 0.067 * (float)(ADC); 
          // voltage: This is only approximate based on testing 16 boards
        sprintf(voltageString, "%4d-%d-%d %2d:%2d:%2d, %d, %d, %f, %d, %d", 
                  theTime.year, 
                  theTime.month, 
                  theTime.day, 
                  theTime.hour, 
                  theTime.minute, 
                  theTime.second, 
                  bricknum, 
                  ADC, 
                  voltage,
                  stateRadio,
                  (int)powerOnSignal);
 
        if (theTime.month < 10)  { sprintf(monthString,  "0%d", theTime.month  ); }
        else                     { sprintf(monthString,  "%d",  theTime.month  ); }
        if (theTime.day < 10)    { sprintf(dayString,    "0%d", theTime.day    ); }
        else                     { sprintf(dayString,    "%d",  theTime.day    ); }
        if (theTime.hour < 10)   { sprintf(hourString,   "0%d", theTime.hour   ); }
        else                     { sprintf(hourString,   "%d",  theTime.hour   ); }
        if (theTime.minute < 10) { sprintf(minuteString, "0%d", theTime.minute ); }
        else                     { sprintf(minuteString, "%d",  theTime.minute ); }

        sprintf(dateString, "%d%s%s%s%s", theTime.year, monthString, dayString, hourString, minuteString);

        sprintf(VoltageLogFnm, "/root/brickmove/inbound/brick%d_%s.log", bricknum, dateString); 
        fp = fopen(VoltageLogFnm, "a");
        fprintf(fp, "%s\n", voltageString);
        fclose(fp);
        Log(voltageString);
      }
    }


    ////////////////////////
    // State Switching
    ////////////////////////
    
    // Handle the case of no sleep/awake cycling and no radio
    if      (!modeAwake && !modeRadio) { // easy: make sure the radio is on
      if (stateRadio != 1) {
        stateRadio = 1;
        turn_on_the_radio();
      }
    }

    // Handle the case of NO sleep/awake cycling but radio On-intervals are implemented
    else if (!modeAwake &&  modeRadio) { // easy: periodically turn the radio on / off
      // For radio-on we have two allowable time conditions: 
      //   Either it is the top of the hour in the radioOnMinutes interval OR
      //          it is the bottom of the hour-prior in preRadioLeadMinutes interval
      //   Note that here we can use hour values outside the range [0, 23] just fine
      //     since it is just a modulus calculation.
      if ( (!((offsetHours + theTime.hour    )%radioHourModulus) && (theTime.minute <= radioOnMinutes          )) ||
           (!((offsetHours + theTime.hour + 1)%radioHourModulus) && (theTime.minute >= 60 - preRadioLeadMinutes))     ) {
        if (stateRadio == 0) {
          stateRadio = 1;
          turn_on_the_radio();
        }
      }
      else {
        if (stateRadio == 1) {
          stateRadio = 0;
          turn_off_the_radio();
        }
      }
    }

    // Handle the case of sleep/awake cycling and radio always On (modeRadio = 0)
    //   Here the hour doesn't matter as we're on at the top of every hour 
    else if ( modeAwake && !modeRadio ) {
      if (stateRadio != 1) {
        stateRadio = 1;
        turn_on_the_radio();
      }
      if (stateAwake == 0) {
        stateAwake = 1;  // transition to "I'm obviously ON" 
      }
      if (stateAwake == 1) { 
        if ( (theTime.minute > awakeOnMinutes) && (theTime.minute < 60 - preAwakeLeadMinutes) ) {
          stateAwake = 2; 
        } 
      }
      if (stateAwake == 2) {
        // Try to estimate sleepMinutes low so we wake up a bit early
        //   This means use larger values to subtract from 60
        //     Use 2 minutes as estimated boot time
        //     Use 2 minutes as estimated shutdown time
        //     Use 1 minute slack
        //     --> slackMinutes = 5, set in Agent.h
        // Once we have a sleep interval in minutes either we go to sleep (if it is longer than 
        //   one minute) or we revert to stateAwake = 0 because there is no point in going to
        //   sleep.
        sleepMinutes = 60 - preAwakeLeadMinutes - theTime.minute - slackMinutes;
        if (sleepMinutes > 63) { sleepMinutes = 63;        }   // unnecessary safety check
        if (sleepMinutes >  1) { AgentSleep(sleepMinutes); }
        else                   { stateAwake = 0;           }
      }
    }

    // Handle the most usual case of sleep/awake cycling and radio On-intervals 
    else if ( modeAwake &&  modeRadio) {

      // First deal with stateRadio  
      if ( thisRadio == 1 ) {
        if ( ( theTime.hour == thisHour      && (theTime.minute <= radioOnMinutes          )) ||
             ( theTime.hour == thisHour -  1 && (theTime.minute >= 60 - preRadioLeadMinutes)) || 
             ( theTime.hour == thisHour + 23 && (theTime.minute >= 60 - preRadioLeadMinutes)) 
                                                                                                  ) {
          if (stateRadio != 1) {
            stateRadio = 1;
            turn_on_the_radio();
          }
        }
        else if (theTime.hour == thisHour && theTime.minute > radioOnMinutes) {
          if (stateRadio < 2) {
            turn_off_the_radio();
            stateRadio = 2;
          }
        }
      }
      else if (thisRadio == 0 && stateRadio == 1) { 
        turn_off_the_radio();
        stateRadio = 2; 
        // this follows up on the we-woke-up-late stop gap implemented above this loop
      }

      // Next deal with stateAwake as independent if-statements
      if (stateAwake == 0) { stateAwake = 1; }
      if (stateAwake == 1) { 
        if (theTime.hour == thisHour && theTime.minute > awakeOnMinutes) { stateAwake = 2; } 
      }
      if (stateAwake == 2) {
        if (thisRadio == 1 && stateRadio == 2) {  
          // can fall asleep if thisRadio is true and the radio is off now
          //   ...so to calculate sleepMinutes we choose the larger lead-time 
          //      value from radio and awake lead-times
          if (preAwakeLeadMinutes > preRadioLeadMinutes) { 
            sleepMinutes = 60 - preAwakeLeadMinutes - theTime.minute - slackMinutes;
          }
          else {
            sleepMinutes = 60 - preRadioLeadMinutes - theTime.minute - slackMinutes;
          }
          if (sleepMinutes <  1) { sleepMinutes =  1; }
          if (sleepMinutes > 63) { sleepMinutes = 63; }
          AgentSleep(sleepMinutes);
        }
        else if (thisRadio == 0) {
          // can fall asleep if thisRadio is false (since stateAwake is 2)
          //   ...so to calculate sleepMinutes we just use the awake lead-time
          sleepMinutes = 60 - preAwakeLeadMinutes - theTime.minute - slackMinutes;
          if (sleepMinutes <  1) { sleepMinutes =  1; }
          if (sleepMinutes > 63) { sleepMinutes = 63; }
          AgentSleep(sleepMinutes);
        }
        // else haven't reached sleep conditions yet; keep cycling
      }
    }


  } while (1);

  exit(0);
}


//
// This code is drastically simplified by the assumption that the 
//   radio-on time window surrounds the camera+router time window.
//   This is bad coding practice undertaken for expediency and 
//   merits a big fat Cassandra warning.
//
// This code also assumes that the system came up in the second 
//   half of the hour and will roll over the top of the hour to
//   small minutes.  Specifically this code will not run
//   unless minutes < 30. This prevents a boolean true from occurring
//   at minute 57 when periphState is still zero. 
//
void UpdatePeripheral(myTime theTime)
{
  int powerState, queryResult;

  if (theTime.minute <= 30) {
    if (periphState == 0) {
      if (theTime.minute >= periphMinuteMark1) { 
        powerState = QueryUc(3, 0); 
        if (powerState & 0x80) { 
          // i.e. Internal 12V is already ON
          turnInternal12Off = 0;
        }
        else { 
          turnInternal12Off = 1; 
          powerState  |= 0x80;
          queryResult  = QueryUc(2, powerState);
          queryResult  = QueryUc(3, 0);
          if (queryResult != powerState) { Log("Try Internal12 up: failed"); }
        }
        periphState++;
      }
    }
    else if (periphState == 1) {
      if (theTime.minute >= periphMinuteMark2) { 
        powerState = QueryUc(3, 0); 
        powerState |= 0x10;
        queryResult = QueryUc(2, powerState); 
        queryResult = QueryUc(3, 0);
        if (queryResult != powerState) { Log("Try External12 up: failed"); }
        periphState++;
      }
    }
    else if (periphState == 2) {
      if (theTime.minute >= periphMinuteMark3) {
        powerState = QueryUc(3, 0); 
        if (turnInternal12Off) { powerState &= 0x6f; }
        else                   { powerState &= 0xef; }
        queryResult  = QueryUc(2, powerState);
        queryResult  = QueryUc(3, 0);
        if (queryResult != powerState) { Log("Try Peripherals down: failed"); }
        periphState++;
      }
    }
  }
  return;
}

int QueryUc(int type, int data)
{
  char cmd[512];
  FILE *fp;
  int  sbcType, sbcData, ucType, ucData;

  sprintf(cmd, "/root/bin/ucInterface %d %d > /root/tmp/ucI.tmp", type, data);
  Log(cmd);  
  system(cmd); 
  fp = fopen("/root/tmp/ucInterface.out", "r");
  fscanf (fp, "%d", &sbcType);
  fscanf (fp, "%d", &sbcData);
  fscanf (fp, "%d", &ucType);
  fscanf (fp, "%d", &ucData);
  fclose(fp);

  return ucData;
}



//////////////////////////
// Install a new Agent
//////////////////////////
// First: There are two ways to STOP this from happening
//   1. Don't push an Agent.h or an Agent.c file to /home/brick/uploads OR
//   2. Push a file called DontInstallAgent.txt to /home/brick/uploads
//  
// Next: If either Agent.c or Agent.h exists, all will be moved to /root/src
//   and the program will attempt to compile Agent there. If this works the
//   result will be pushed to /root/bin where it will presumably run the 
//   next time the SBC boots.
///////////////////////////
void InstallAgent()
{
  // If a stopper file exists: Don't try to do anything
  if (fileExist("/home/brick/uploads/DontInstallAgent.txt")) { doInstallAgent = 0; }

  if (doInstallAgent) {
    if ( fileExist("/home/brick/uploads/Agent.c") || fileExist("/home/brick/uploads/Agent.h")    ) {
      if ( fileExist("/home/brick/uploads/Agent.c")) { system("/bin/mv /home/brick/uploads/Agent.c /root/src/Agent.c"); }
      if ( fileExist("/home/brick/uploads/Agent.h")) { system("/bin/mv /home/brick/uploads/Agent.h /root/src/Agent.h"); }

      if (!fileExist("/root/src/Agent.c")) { system("/bin/cp /home/brick/src/Agent.c /root/src/Agent.c"); }
      if (!fileExist("/root/src/Agent.h")) { system("/bin/cp /home/brick/src/Agent.h /root/src/Agent.h"); }
      system("/bin/chown root:root /root/src/Agent*"); 
      if (fileExist("/root/src/Agent.c") && fileExist("/root/src/Agent.h")) {
        system("/usr/bin/gcc -o /root/src/Agent /root/src/Agent.c");
        if ( fileExist("/root/src/Agent")) { 
          system("/bin/mv /root/src/Agent /root/bin/Agent"); 
          system("/bin/chmod a+rx /root/bin/Agent"); 
          Log("New Agent installed");
        }
        else { Log("No Agent compiled; no Agent install"); }
      }
      else { Log("Missing Agent.c or Agent.h in /root/src; no Agent install"); }
    }
    else { Log("No Agent source found; no Agent install"); }
  }
  else {
    Log("doInstallAgent FALSE; no Agent install");
  }
  return;
}


// Sleep in 1 minute for nMinutes
void   AgentSleep(int nMinutes)
{
  char cmd[512];
  if (nMinutes <  1) { nMinutes =  1; }
  if (nMinutes > 63) { nMinutes = 63; }
  BroadcastHalting("this unit going to sleep normally");
  sprintf(cmd, "/root/bin/ucInterface  5 128 > /root/tmp/ucI.tmp");           Log(cmd);  system(cmd); 
  sprintf(cmd, "/root/bin/ucInterface  5  64 > /root/tmp/ucI.tmp");           Log(cmd);  system(cmd); 
  sprintf(cmd, "/root/bin/ucInterface  5  %d > /root/tmp/ucI.tmp", nMinutes); Log(cmd);  system(cmd); 
  sprintf(cmd, "/root/bin/ucInterface  6 255 > /root/tmp/ucI.tmp");           Log(cmd);  system(cmd); 
  sprintf(cmd, "/root/bin/ucInterface  8   0 > /root/tmp/ucI.tmp");           Log(cmd);  system(cmd); 
  sprintf(cmd, "/root/bin/ucInterface  9   0 > /root/tmp/ucI.tmp");           Log(cmd);  system(cmd); 
  sprintf(cmd, "/root/bin/ucInterface 10   1 > /root/tmp/ucI.tmp");           Log(cmd);  system(cmd); 
  do { SetRestState(); } while (1); 
}

void   BroadcastHalting(char *msg)
{
  Log(msg);
  if (stateRadio == 1) {
    // This is not implemented; it must be done in synchrony with the wireless 
    //   backbone communication scheme, e.g. using rsynch, for example:
    //   1. Create a file describing the system state and plan to sleep
    //   2. Place this in a system state directory
    //   3. Issue an rsynch
    // Whatever it is should not take more than a few seconds
    turn_off_the_radio();
    stateRadio = 2;
  }
  return;
}

void turn_on_the_radio(void)
{
  char cmd[512];
  sprintf(cmd, "/root/bin/ucInterface 2 %d > /root/tmp/ucI.tmp", powerOnSignal); 
  Log(cmd);  
  system(cmd); 
  return;
}

void turn_off_the_radio(void)
{
  char cmd[512];
  sprintf(cmd, "/root/bin/ucInterface 2 0 > /root/tmp/ucI.tmp");
  Log(cmd);  
  system(cmd); 
  return;
}


void SetRestState(void)
{
  char   msg[512];
  char   cmd[512];
  system("/root/bin/bc > /root/tmp/bc.out");
  ReadBCDotOut();
  if (!PORTC) {
    sprintf(msg, "Agent: PORTC == 0"); Log(msg); 
    sprintf(cmd, "shutdown -h now");   Log(cmd); LogDate(); Log("  "); system(cmd);
    exit(0);
  }
  return;
}

void ReadBCDotOut(void)
{
  FILE  *fp;
  int    value;
  fp = fopen("/root/tmp/bc.out", "r");
  fscanf (fp, "%d", &value); oldPORTC = (unsigned char)value;
  fscanf (fp, "%d", &value);    PORTB = (unsigned char)value;
  fscanf (fp, "%d", &value);    PORTC = (unsigned char)value;
  fclose(fp);
}


//////////////////////
// Diagnostics and Process Control
//////////////////////
void Log(char *msg ) 
{ 
  FILE *logfp;
  char cmd[512];
  logfp = fopen(AgentLogFnm, "a"); fprintf(logfp, "%s\n", msg); fclose(logfp);
  return;
}

void LogDate(void)
{
  char cmd[512];
  sprintf(cmd, "date >> %s", AgentLogFnm);
  system(cmd);
  return;
}

Agent.h

#ifndef AGENT_DOT_H
#define AGENT_DOT_H

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/termios.h>

// Simple UTC 24-hour-clock time structure
typedef struct {
  int year;
  int month;
  int day;
  int hour;
  int minute; 
  int second;
} myTime;

myTime oneDay;
myTime oneHour;
myTime oneMinute;
myTime oneSecond;
myTime zeroTime;

char tmpDateFnm[512];
char monthString[32];
char dayString[32];
char hourString[32];
char minuteString[32];
char dateString[128];

//////////////////////
// Function declarations
//////////////////////
void   SetRestState(void);
void   ReadBCDotOut(void);
void   Log(char *) ;
void   LogDate(void);

void   AgentSleep(int);
void   BroadcastHalting(char *);
void   turn_on_the_radio(void);
void   turn_off_the_radio(void);

void   InstallAgent(void);


// (inlined) file utilities
int    fileExist(char *);
int    fileNumLines(char *);
int    fileSize(char *);
void   Clobber(char *);

////////////////////
// functions from milo
////////////////////
// TIME functions
int    AddTime(myTime, myTime, myTime *);
int    CompareNowTime(myTime);
int    CompareTwoTimes(myTime, myTime);
myTime ParseDateString(char *);           // This is a keeper utility that
                                          //   operates on a date string rather
                                          //   than using current system time.
myTime ParseDate(void);
int    GenerateYrDay(myTime);
void   PrintTime(char *, myTime);
int    MinutesUntil(myTime, myTime);
myTime ModeBasedNextDataTime(int);
myTime GenerateNextTwentyMinuteTime(void);
myTime GenerateNextEvenHour(void);
myTime GenerateNextSixthHour(void);
myTime GenerateNextNoon(void);
myTime GenerateNextEvenJDayNoon(void);


/////////////////////////////
/////////////////////////////
///
/// GLOBALS 
///
/////////////////////////////
/////////////////////////////
///////////////
// Configuration default settings: Adjustable on command line
///////////////
int    modeAwake            =   1;   // intermittent on times enable / disable
int    modeRadio            =   1;   // intermittent radio on-intervals enable / disable
int    powerOnSignal        =   6;
int    radioOnMinutes       =   7;
int    offsetHours          =   0;
int    radioHourModulus     =   4;
int    preRadioLeadMinutes  =   4;
int    preAwakeLeadMinutes  =   0;
int    awakeOnMinutes       =   5;

/////////////////////////////
// peripheral (Camera) stuff
/////////////////////////////
// A proper "peripheral" event/device model could use a formalized 
//   structure to go through a progression of power switch states. 
//
// Here we proceed more on a 'one-off' basis. The recipe is: Make sure 
//   the router (Internal 12) is ON for one minute, then turn on the 
//   Camera (External 12) for one minute, then off and (if the Router
//   is not needed for other purposes) turn off the Router.
//
// All of this is tracked inside the function UpdatePeripheral() so that
//   it does not clutter the infinite loop (and the call can be moved around
//   within that structure).
//
// periphState = 0: pre-Start
// periphState = 1: Router is ON
// periphState = 2: Camera and Router are ON
// periphState = 3; Camera and Router are OFF
//
// The entire process can be voided using doPeripheral = 0 (the default). 
// To enable (doPeripheral = 1) we check for the existence of 
//   /home/brick/uploads/RunCamera.txt. (This is not deleted!) 
//

int   doPeripheral  =   0;
int   periphState   =   0;
int   turnInternal12Off;
void  UpdatePeripheral(myTime);
int   QueryUc(int, int);
int   periphMinuteMark1 = 0;
int   periphMinuteMark2 = 1;
int   periphMinuteMark3 = 2;

///////////////
// Configuration default settings: Adjustable here only (not a cla)
///////////////
// polling loop interval plus power tracking
int    sleepSec     = 15;
int    voltageCheckModulus = 8;  // Report power every pCM times thru the inf loop
int    slackMinutes = 5;       // estimated shutdown / boot buffer
int    thisHour;               // used for thisRadio state determination
char   voltageString[512];
int    ADC;
float  voltage;
int    doVoltageLog = 0;
int    doInstallAgent = 1;

// state variables
// declared/initialized above: modeRadio, modeAwake
int    thisRadio = 0, thisAwake = 0;
int    stateRadio = 0, stateAwake = 0;

///////////////
// Comm and logging
///////////////
char          uciOutFnm[512];
char          AgentLogFnm[512];
char          VoltageLogFnm[512];
unsigned char oldPORTC, PORTC, PORTB;

//////////////////
// Bricknum
//////////////////
int   bricknum;

/////////////////////////////
/// time-related, inherited from milo
/////////////////////////////
int   yrDay;
int   dpm[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 
int   adpm[13];

double secPerMinute =            60.;
double secPerHour   =          3600.;
double secPerDay    =      24.*3600.;
double secPerYear   = 24.*3600.*365.;
double secAfterMonth[14];


/////////////////////////////////
/////////////////////////////////
/////
///// FILE UTILITIES
/////
/////////////////////////////////
/////////////////////////////////
int fileExist(char *f)
{
  int fd;
  fd = open(f, 0);
  if (fd < 3) { return 0; }
  close(fd);
  return 1;
}

int fileNumLines(char *fnm)
{
  FILE *fNLfp;
  int   nlines;
  char  *c, *s;

  nlines = 0;
  s      = (char *)(malloc(32768));
  fNLfp  = fopen(fnm, "r");

  if (fNLfp == NULL) {
    printf ("fileNumLines(fnm = %s) file dne; returning -1\n", fnm);
    return -1;
  }

  do { c = fgets(s, 32768, fNLfp); nlines++; } while (c != NULL);

  fclose(fNLfp);
  nlines--;
  free(s);
  return nlines;
}

// fileSize returns -1 if the operation fails
// otherwise returns file size in bytes.
// lseek() is supposed to return -1 on fail
int fileSize(char *fnm)
{
  int fd, size;
  fd = open(fnm, 0);
  if (fd < 3) { return -1; }
  size = lseek(fd, 0, 2);
  close(fd);
  return(size);
}

void Clobber(char *f)
{
  char cmd[512];
  if (fileExist(f)) {
    sprintf(cmd, "rm %s", f);
    system(cmd);
  }
  return;
}



// This will add a small time increment dTime to a date
//   The small time increment is required to be days/hrs/min/sec only
// the context of leap year is carried in the time.year field
// we deal with 2008/2012/2016/2020 as leap years
int AddTime(myTime time, myTime dTime, myTime *newTime)
{
  int isLeapYear  = 0;
  int minuteCarry = 0;
  int hourCarry   = 0;
  int dayCarry    = 0;
  int monthCarry  = 0;

  // PrintTime ("AddTime:  time", time);
  // PrintTime ("AddTime: dTime", dTime);

  // ignore dTime month and year values since dTime must be small
  newTime->month = time.month;
  newTime->year  = time.year;

  if (time.year == 2008 || time.year == 2012 || time.year == 2016 || time.year == 2020) {
    isLeapYear = 1;
  }
  
  // Small-time-unit additions are pretty simple
  newTime->second = time.second + dTime.second;
  while (newTime->second >= 60) { newTime->second -= 60; minuteCarry++; }

  newTime->minute = time.minute + dTime.minute + minuteCarry;
  while (newTime->minute >= 60) { newTime->minute -= 60; hourCarry++; }

  newTime->hour = time.hour + dTime.hour + hourCarry;
  while (newTime->hour >= 24) { newTime->hour -= 24; dayCarry++; }

  newTime->day = time.day + dTime.day + dayCarry;
  //
  // and now it gets messy; 
  //   months start on day 1, not day 0
  //   we deal with months via pushbroom method
  // 
  do { 
    if (newTime->month ==  1) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
    if (newTime->month ==  2) {
      if (isLeapYear) { if (newTime->day >= 30) { newTime->day -= 29; newTime->month++; } }
      else            { if (newTime->day >= 29) { newTime->day -= 28; newTime->month++; } }
    }
    if (newTime->month ==  3) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
    if (newTime->month ==  4) { if (newTime->day >= 31) { newTime->day -= 30; newTime->month++; } }
    if (newTime->month ==  5) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
    if (newTime->month ==  6) { if (newTime->day >= 31) { newTime->day -= 30; newTime->month++; } }
    if (newTime->month ==  7) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
    if (newTime->month ==  8) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
    if (newTime->month ==  9) { if (newTime->day >= 31) { newTime->day -= 30; newTime->month++; } }
    if (newTime->month == 10) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
    if (newTime->month == 11) { if (newTime->day >= 31) { newTime->day -= 30; newTime->month++; } }
    if (newTime->month == 12) { if (newTime->day >= 32) { newTime->day -= 31; newTime->month++; } }
 
    if (newTime->month > 12) {
      newTime->year++;
      newTime->month = 1;
      isLeapYear = 0; 
      if (newTime->year == 2008 || 
          newTime->year == 2012 || 
          newTime->year == 2016 || 
          newTime->year == 2020    ) { isLeapYear = 1; }
    }
  } while (newTime->day >= 32);

  // PrintTime("AddTime:   sum", *newTime);

  return 1;
}

// compares system time to a passed time value
//   return -1 if current time is early
//   return  0 if current time is equal
//   return  1 if current time is late
int CompareNowTime(myTime t)
{
  myTime sysT;
  sysT = ParseDate();

  // proceed top down (assumes all values are within proper ranges
  if      (sysT.year > t.year) { return  1; }
  else if (sysT.year < t.year) { return -1; }

  if      (sysT.month > t.month) { return  1; }
  else if (sysT.month < t.month) { return -1; }

  if      (sysT.day > t.day) { return  1; }
  else if (sysT.day < t.day) { return -1; }

  if      (sysT.hour > t.hour) { return  1; }
  else if (sysT.hour < t.hour) { return -1; }

  if      (sysT.minute > t.minute) { return  1; }
  else if (sysT.minute < t.minute) { return -1; }

  if      (sysT.second > t.second) { return  1; }
  else if (sysT.second < t.second) { return -1; }

  // equal down to the second
  return 0;
}

int CompareTwoTimes(myTime tA, myTime tB)
{
  // proceed top down (assumes all values are within proper ranges)
  if      (tA.year > tB.year)     { return  1; }
  else if (tA.year < tB.year)     { return -1; }

  if      (tA.month > tB.month)   { return  1; }
  else if (tA.month < tB.month)   { return -1; }

  if      (tA.day > tB.day)       { return  1; }
  else if (tA.day < tB.day)       { return -1; }

  if      (tA.hour > tB.hour)     { return  1; }
  else if (tA.hour < tB.hour)     { return -1; }

  if      (tA.minute > tB.minute) { return  1; }
  else if (tA.minute < tB.minute) { return -1; }

  if      (tA.second > tB.second) { return  1; }
  else if (tA.second < tB.second) { return -1; }
  
  return 0;
}

myTime ParseDateString(char *aDateString)
{
  myTime t;
  char cmd[512], dayString[16], dayNumString[16], yearString[16]; 
  char monthString[16], zoneString[16], hmsString[16];
  int  year, month, day, hour, minute, second;

  // Mon Dec 31 00:00:01 UTC 2007
  strncpy(   dayString, aDateString     , 3); *(dayString+3)    = 0;
  strncpy( monthString, aDateString +  4, 3); *(monthString+3)  = 0;
  strncpy(dayNumString, aDateString +  8, 2); *(dayNumString+2) = 0;
  strncpy(   hmsString, aDateString + 11, 8); *(hmsString+8)    = 0;
  strncpy(  zoneString, aDateString + 20, 3); *(zoneString+3)   = 0;
  strncpy(  yearString, aDateString + 24, 4); *(yearString+4)   = 0;

  year = atoi(yearString);
  day  = atoi(dayNumString);

  if      (!strcmp(monthString, "Jan")) { month =  1; }
  else if (!strcmp(monthString, "Feb")) { month =  2; }
  else if (!strcmp(monthString, "Mar")) { month =  3; }
  else if (!strcmp(monthString, "Apr")) { month =  4; }
  else if (!strcmp(monthString, "May")) { month =  5; }
  else if (!strcmp(monthString, "Jun")) { month =  6; }
  else if (!strcmp(monthString, "Jul")) { month =  7; }
  else if (!strcmp(monthString, "Aug")) { month =  8; }
  else if (!strcmp(monthString, "Sep")) { month =  9; }
  else if (!strcmp(monthString, "Oct")) { month = 10; }
  else if (!strcmp(monthString, "Nov")) { month = 11; }
  else if (!strcmp(monthString, "Dec")) { month = 12; }
  else                                  { month =  0; }

  // parse the hms string
  char *p = hmsString;
  hour = atoi(p);
  do { p++; } while (*p != ':');
  p++;
  minute = atoi(p);
  do { p++; } while (*p != ':');
  p++;
  second = atoi(p);

  t.year   = year;
  t.month  = month;
  t.day    = day;
  t.hour   = hour;
  t.minute = minute;
  t.second = second;

  return t;
}


myTime ParseDate(void)
{
  myTime t;
  FILE *tmpDateFp;
  char cmd[512], dayString[16], monthString[16], zoneString[16], hmsString[16];
  int  year, month, day, hour, minute, second;

  sprintf(cmd, "date >> %s", tmpDateFnm);
  system(cmd);

  tmpDateFp = fopen(tmpDateFnm, "r");
  fscanf(tmpDateFp, "%s", dayString);
  fscanf(tmpDateFp, "%s", monthString);
  fscanf(tmpDateFp, "%d", &day);
  fscanf(tmpDateFp, "%s", hmsString);
  fscanf(tmpDateFp, "%s", zoneString);
  fscanf(tmpDateFp, "%d", &year);
  fclose(tmpDateFp);

  if      (!strcmp(monthString, "Jan")) { month =  1; }
  else if (!strcmp(monthString, "Feb")) { month =  2; }
  else if (!strcmp(monthString, "Mar")) { month =  3; }
  else if (!strcmp(monthString, "Apr")) { month =  4; }
  else if (!strcmp(monthString, "May")) { month =  5; }
  else if (!strcmp(monthString, "Jun")) { month =  6; }
  else if (!strcmp(monthString, "Jul")) { month =  7; }
  else if (!strcmp(monthString, "Aug")) { month =  8; }
  else if (!strcmp(monthString, "Sep")) { month =  9; }
  else if (!strcmp(monthString, "Oct")) { month = 10; }
  else if (!strcmp(monthString, "Nov")) { month = 11; }
  else if (!strcmp(monthString, "Dec")) { month = 12; }
  else                                  { month =  0; }

  // parse the hms string
  char *p = hmsString;
  hour = atoi(p);
  do { p++; } while (*p != ':');
  p++;
  minute = atoi(p);
  do { p++; } while (*p != ':');
  p++;
  second = atoi(p);
  // printf ("hmsString %s led to hour %d minute %d second %d\n", hmsString, hour, minute, second);

  t.year   = year;
  t.month  = month;
  t.day    = day;
  t.hour   = hour;
  t.minute = minute;
  t.second = second;

  // printf ("ParseDate: system(\"date\"); returns:\n");
  // system("date");
  // PrintTime("ParseDate gives:", t);

  sprintf(cmd, "rm %s", tmpDateFnm);
  system(cmd);
  return t;
}

int GenerateYrDay(myTime t)
{
  int priorDays = 0;
  if      (t.month == 1)  { priorDays = 0; }
  else if (t.month == 2)  { priorDays = 31; }
  else if (t.month == 3)  { priorDays = 31 + 28; }
  else if (t.month == 4)  { priorDays = 31 + 28 + 31; }
  else if (t.month == 5)  { priorDays = 31 + 28 + 31 + 30; }
  else if (t.month == 6)  { priorDays = 31 + 28 + 31 + 30 + 31; }
  else if (t.month == 7)  { priorDays = 31 + 28 + 31 + 30 + 31 + 30; }
  else if (t.month == 8)  { priorDays = 31 + 28 + 31 + 30 + 31 + 30 + 31; }
  else if (t.month == 9)  { priorDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31; }
  else if (t.month == 10) { priorDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30; }
  else if (t.month == 11) { priorDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31; }
  else if (t.month == 12) { priorDays = 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30; }

  // handle leap years
  if (t.month > 2 && (t.year == 2008 || 
                      t.year == 2012 || 
                      t.year == 2016 || 
                      t.year == 2020)) 
    { priorDays++; }

  return 1000*t.year + t.day + priorDays;
}

void PrintTime(char *m, myTime t)
{
  printf ("%s %4d %2d %2d %2d %2d %2d\n", m, t.year,   t.month, 
                                             t.day,    t.hour, 
                                             t.minute, t.second);
  return;
}

// This will usually be called with one of the arguments being
//   NOW and the other being some point in the future. Notice
//   that these are labeled start and end so the expected order
//   is NOW and FUTURE-TIME.
int MinutesUntil(myTime start, myTime end)
{
  int minutesUntil = 0, comparison;

  // comparison = 
  //   if start is later than   end: -1
  //   if start is same as      end:  0
  //   if start is earlier than end:  1
  comparison = CompareTwoTimes(end, start); 

  if (comparison == 0)  { return 0; }

  if (comparison == -1) {  // end is earlier than start
    while(CompareTwoTimes(end, start) < 0) { 
      AddTime(end, oneMinute, &end); 
      minutesUntil--;
    }
  }
  else { // start is earlier than end
    while(CompareTwoTimes(start, end) < 0) { 
      AddTime(start, oneMinute, &start); 
      minutesUntil++;
    }
  }
  return minutesUntil;
}

// ModeBasedNextDataTime(int mode)
// This switch determines the datatake frequency by returning
//   an absolute time of the next datatake based on the current 
//   mode.
//
// The following dangerous waters should be clear: If the 
//   sleep interval miscalculates and wakes up after a target
//   time, the system will simply recalculate the next target
//   time and start "sleeping towards" it. For this reason 
//   the RequestSleep() function tries to be very conservative
//   in order to wake up early.
//   
// Second consideration: Suppose the idea is to acquire data
//   every two hours, i.e. as fast as possible. In this case
//   the return value should really be Now because this will
//   give minimal reset cycling and will cause the datatake
//   to begin immediately. Suppose it is noon and instead
//   GenerateNextEvenHour() is used. This datatake begins, 
//   a bit more than 120 minutes elapses, and the next calculated
//   datatake time will be 4pm, creating an effective data
//   acquisition once every 4 hours. 
//
// TAG the following rules determine data acquisition duty cycles
//   milo 2.5 has these set per Brad Danielson's schema for Devon.
myTime ModeBasedNextDataTime(int mode)
{
  if      (mode == 1) { return GenerateNextEvenJDayNoon(); }
  else if (mode == 2) { return GenerateNextNoon();         }
  else if (mode == 3) { return GenerateNextSixthHour();    }
  else if (mode == 4) { return ParseDate();                }
  else                { return ParseDate();                }
}
    

// Time Generation remarks
//   All these functions work by generating a candidate
//     time == Now and incrementing it until it meets
//     desired criteria i.e. == a desired time. 
//   Even if Now is one second after a desired time
//     we increment first seconds and then minutes to get 
//     to a starting point and if necessary increment
//     from there by hours to get to the *next* desired
//     time.  This hopes to avoid logic land-mines implicit
//     in  returning times earlier than Now.
//   Sneaking up on desired times like this is very simple 
//     minded but gets around having to write and test
//     trickier (buggier) code. Of course the point is that
//     the AddTime function takes care of the bizarre variety
//     of carry conditions so that adding one second to
//     2008  2 28 23 59 59 will correctly give midnight on
//     February 29.
//   There are--I believe--UNIX utilities for doing this stuff
//     as well.
// 
//
myTime GenerateNextTwentyMinuteTime(void)
{
  myTime ntmTime = ParseDate();
  while (ntmTime.second)    { AddTime(ntmTime, oneSecond, &ntmTime); }
  while (ntmTime.minute%20) { AddTime(ntmTime, oneMinute, &ntmTime); }
  return ntmTime;
}

myTime GenerateNextEvenHour(void)
{
  myTime neHour = ParseDate();

  while (neHour.second) { AddTime(neHour, oneSecond, &neHour); }
  while (neHour.minute) { AddTime(neHour, oneMinute, &neHour); }

  // TAG these are even UTC hours hence odd hours in East Coast N.America
  if ((neHour.hour)%2) { AddTime(neHour, oneHour, &neHour); }
  return neHour;
}

// TAG We add three to nfHour.hour before checking it modulus 4
//   This value is boolean true unless modules 4 == 0 so that
//   we ensure the candidate return time is incremented unless 
//   the hour is 1, 5, 9, 13, 17, or 21.
//   The corresponding times in the 'UTC - 5' (East Coast N.America) 
//   time zone are 20, 24, 4, 8, 12, and 16, what we want.
myTime GenerateNextFourthHour()
{
  myTime nfHour = ParseDate();

  while (nfHour.second) { AddTime(nfHour, oneSecond, &nfHour); }
  while (nfHour.minute) { AddTime(nfHour, oneMinute, &nfHour); }

  while ((nfHour.hour + 3)%4) { AddTime(nfHour, oneHour, &nfHour); }
  return nfHour;
}


// TAG see GenerateNextFourthHour for time zone logic
myTime GenerateNextSixthHour()
{
  myTime nsHour = ParseDate();

  while (nsHour.second) { AddTime(nsHour, oneSecond, &nsHour); }
  while (nsHour.minute) { AddTime(nsHour, oneMinute, &nsHour); }

  while (nsHour.minute != 0 || (nsHour.hour + 1)%6 ) { AddTime(nsHour, oneHour, &nsHour); }
  return nsHour;
}

// TAG see GenerateNextFourthHour for time zone logic
myTime GenerateNextNoon()         // TAG put a shift in to convert UTC to local = UTC - 5
{
  myTime nNoon = ParseDate();

  while (nNoon.second)     { AddTime(nNoon, oneSecond, &nNoon); }
  while (nNoon.minute)     { AddTime(nNoon, oneMinute, &nNoon); }
  while (nNoon.hour != 17) { AddTime(nNoon, oneHour,   &nNoon); }
  return nNoon;
}

// TAG see GenerateNextFourthHour for time zone logic
myTime GenerateNextEvenJDayNoon()         // TAG put a shift in to convert UTC to local = UTC - 5
{
  myTime nejNoon = ParseDate();

  while (nejNoon.second) { AddTime(nejNoon, oneSecond, &nejNoon); }
  while (nejNoon.minute) { AddTime(nejNoon, oneMinute, &nejNoon); }
  int    runningDay = nejNoon.day;
  int    startYrDay;
  int    startYear;
  int    startJDay;
  int    isOddJDay;

  startYrDay = GenerateYrDay(nejNoon);
  startYear  = startYrDay / 1000;
  startJDay  = startYrDay - startYear*1000;
  isOddJDay  = startJDay % 2 ? 1 : 0;

  // The 'worst case': Now is after local noon on an even JDay.
  // This code uses runningDay to track day changes in the
  //   incrementing candidate time nejNoon. isOddJDay toggles 
  //   whenever the day changes. In the worst case it will
  //   start out 1, then switch to 0 at midnight, then back
  //   to 1 on the next day.
  while (nejNoon.hour != 17 || isOddJDay) { 
    AddTime(nejNoon, oneHour, &nejNoon); 
    if (nejNoon.day != runningDay) { 
      runningDay = nejNoon.day;
      isOddJDay  = 1 - isOddJDay; 
    }
  }
  return nejNoon;
}

#endif
Personal tools