#--
#******************************************************************************
#* Copyright 1990-2007 MBARI
#* MBARI Proprietary Information. All rights reserved.
#******************************************************************************
#* Summary  : Class used to represent and run rover missions
#* Filename : mission.rb
#* Author   : Henthorn
#* Project  : Benthic Rover
#* Version  : 
#* Created  : May-06   Initial functionality
#* Modified : 
#******************************************************************************
#++

require "rover"
require "measure"
require "control_system"
require "rover_utils"
require "acm"
require "navigator"

# RoverMission class
# Purpose is to encapsulate the "old" rover mission script into
# a callable, repeatable object.
#
# May07 - modified to account for 2 rack assemblies (double measure time)
# 

$MEASURE = "Measure"
$MOVEMENT = "Movement"
$LAUNCH = "Launch"
$DONE = "Done"

class RoverMission

  include LogHelper

  attr_reader :power_on_off, :mission_phase, :mission_thread, :launch_time, \
  :current_timeout, :current_deviation, :movement_distance, \
  :exit_on_completion, :move_heading, :record_iterations, :num_cycles, \
  :mission_name, :mission_starttime

  #========================================================
  #
  def initialize(cfg_filename)

    @mission_name = cfg_filename

    # Load configuration
    #
    rv = Rover.instance
    rv.cm.configfile=cfg_filename
    puts("mission: let's load the configuration from #{cfg_filename}")
    rv.cm.load

    @mission_phase = $LAUNCH
    @mission_cycle = 0
    @start_cycle = 1

    @mission_starttime = 0 # record time mission starts
    @cycle_starttime = 0   # record time each cycle starts

    @move_heading = rv.cm.load_config_item("Heading").to_f
    @current_timeout = rv.cm.load_config_item("CurrentDirectionTimeout").to_f
    @current_deviation = rv.cm.load_config_item("CurrentDeviation").to_f
    @move_distance = rv.cm.load_config_item("Distance").to_f
    @iterations = rv.cm.load_config_item("RecordingIterations").to_i
    @launch_time = rv.cm.load_config_item("LaunchDuration").to_f
    @abort_check_interval = rv.cm.load_config_item("AbortCheckInterval").to_f
    @num_cycles = rv.cm.load_config_item("NumCycles").to_i
    @settle_time = rv.cm.load_config_item("DustSettleTime").to_f

    ci = rv.cm.load_config_item("PowerOffSleeps", "False")
    @power_off_on = (ci == "True")

    ci = rv.cm.load_config_item("ExitOnCompletion", "False")
    @exit_on_completion = (ci == "True")

    ci = rv.cm.load_config_item("SimpleMove", "False")
    @simple_move = (ci == "True")

    @saved_logdir = nil

    # Load the state of the mission before board was put to sleep
    #
    load_state
    # if (@power_off_on)

    # Instantiate our Measure object
    # to handle the Measure phase of this mission
    #
    @measure = Measure.new(@iterations)

    # Prepare for execution
    #
    @mission_thread = nil
    @valve_thread = nil
    @abort = false
    

  end

  # Method loads the saved state of the mission before
  # the Rover powered-off in power saving mode
  #
  def load_state

    rv = Rover.instance
    @saved_logdir = rv.cm.load_config_item("SavedLogDir")
    @saved_phase = rv.cm.load_config_item("SavedPhase")
    @saved_cycle = rv.cm.load_config_item("SavedCycle").to_i
    @saved_starttime = rv.cm.load_config_item("MST").to_f
    @start_cycle = @saved_cycle
    @mission_phase = @saved_phase

    # Install the mission start time, if applicable
    #
    if (@saved_starttime > 0)
      @mission_starttime= @saved_starttime 
      LogDir.instance.mst = @saved_starttime
    end

  end

############################# Mission Execution ######################

  #========================================================
  # Main mission loop
  #
  def run

    #########  Already Done ?  #########

    if done?
      puts("mission.run: Mission #{@mission_name} already completed")
      return
    end

    puts("mission: setting log directory")
    setup_log_dir
    # Monitor threads?
    #
    if (Clo.instance.monitor_threads)
      syslog("_ monitoring threads...")
      monitor_threads
    end

    #########  Start Mission Thread  #########

    @mission_thread = RoverThread.new do
      Thread.current["name"] = "mission"

      eventlog("_ Starting mission!")
      puts("Starting mission!")

      #########  Execute Launch sequence at the start  #########

      do_launch()


      #########  Do the Movement-then-Measure cycles  #########

      syslog("_ executing cycles #{@start_cycle}..#{@num_cycles}")
      for @mission_cycle in @start_cycle..@num_cycles

	break if self.abort?  # Abort could have been signaled during cycle


	#########  Do the move if the next phase is movement  #########

	if @mission_phase == $MOVEMENT
	  do_move()
    
    eventlog("_ Done with Movement")
	  break if self.abort?  # Abort could have been signaled during move

	  # Advance phase before sleeping/shutdown
	  advance_phase()

	  # Wait for dust to settle, sleep or shutdown
	  dust_settle()
	end

	break if self.abort?  # Abort could have been requested while waiting


	#########  Measure if the next phase is Measure  #########

	if @mission_phase == $MEASURE
    # Execute the measurement phase
    #
    do_measure()

	  # Advance phase
	  advance_phase()
	end

      end   #########  End of Move-Measure cycles  #########


      # Check to see if we received an abort signal
      #
      if self.abort?
	do_abort
      end

      #########  This mission is complete  #########

      set_phase($DONE)

      eventlog("_ Done with #{@mission_name}")

      save_state
      close

    end     # Thread do
  end     # run method

  ##########  Helper methods  #########

  #========================================================
  def done?
    return (@mission_phase == $DONE)
  end


  #========================================================
  # Dust settle process
  # Either shutdown the Rover during this period, or
  # just idle.
  #
  def dust_settle()
    # Wait here for the dust to settle before Measurement
    #
    eventlog("_ Waiting for #{@settle_time} minutes for dust to settle.")

    if @power_off_on
      # Power down the board if configured to do so
      power_off(@settle_time)

    else
      # Sleep (idle) but wake-up periodically to check abort flag.
      #
      st = @settle_time
      period = 2 # minutes
      while (st > 0)
      	Rover.instance.rt.rSleep(period*60)
      	break if self.abort?
      	st -= period
      end
    end

  end


  #========================================================
  # Save the mission state to the config file for use on restart
  #
  def save_state
    eventlog("_ Saving #{@mission_name}")
    cm = Rover.instance.cm
    cm.set_state(@mission_starttime.to_f.to_s, "MST")
    cm.set_state(@mission_phase, "SavedPhase")
    cm.set_state(@mission_cycle, "SavedCycle")
    cm.set_state(@measure.current_iter, "SavedIteration")
    cm.set_state(LogDir.instance.datalog_dir.chop, "SavedLogDir")
    cm.write
  end


  #========================================================
  # Time to put board to sleep for a number of minutes
  #
  def power_off(minutes)
    if (@power_off_on)
      save_state
      eventlog("_ Power Off #{@mission_name} for #{minutes} minutes")
      sleep(1)

      # Sim mode? Just exit this thread
      #
      if (Clo.instance.sim)
	@mission_phase = "Sleep"
	Thread.current.exit
      end

      # Otherwise, use bat3 board utilities to power down and
      # wake-up after a period of time
      #
      if (Clo.instance.warp)
        Rover.instance.sleep_cpu(60)
      else
        Rover.instance.sleep_cpu(minutes*60)
      end

    end
  end


  #========================================================
  # Abort tasks here
  #
  def do_abort
    eventlog("_ Aborting mission #{@mission_name}")
    @mission_phase = $DONE
    close
  end


  #========================================================
  # Mission clean-up tasks here
  #
  def close
    eventlog("_ Closing #{@mission_name}")
    Rover.instance.relay.allOff
    save_state
    Thread.current.exit
  end


  #========================================================
  # Set up the data log directory
  # Best if called just before calling the run method
  #
  def setup_log_dir()
    # Point to existing directories for data and logs
    # when continuing a mission
    #
    if (@mission_starttime && @mission_starttime > 0 && @saved_logdir)
      unless LogDir.instance.setlogdir(@saved_logdir)
	syslog("! Could not set top-level log dir to #{@saved_logdir}")
      end
    else
      # Make a new directory
      #
      unless LogDir.instance.newlogdir("/cf/rover/logs")
	syslog("! Could not create new top-level log dir")
      end
    end
  end


  #========================================================
  # Copy configuration files to the log directory
  # 
  def copy_configs()
    cpcmd = "cp " + Rover.instance.cm.configfile + " " + \
            LogDir.instance.dir_name + "/."
    eventlog(cpcmd)
    return system(cpcmd)
  end


  #========================================================
  # Logic to advance mission phase
  #
  def advance_phase
    # Go to next phase of mission
    #
    if (@mission_phase == $LAUNCH)
      set_phase($MOVEMENT)  # Launch to Movement

    elsif (@mission_phase == $MOVEMENT)
      @measure.reset()
      set_phase($MEASURE)   # Movement to Measure

    elsif (@mission_phase == $MEASURE)
      @measure.reset()
      set_phase($MOVEMENT)  # Measure back to Movement

    end
  end


  #========================================================
  # 
  def set_phase(phase)
    raise("Exception! #{phase} is an unknown phase") unless \
    (phase==$DONE || phase==$LAUNCH || phase==$MOVEMENT || phase==$MEASURE)

    @mission_phase = phase
    syslog("_ mission phase is now #{@mission_phase}")
  end


  #========================================================
  # This method handles the launch phase.
  # The launch phase is surprisingly busy.
  #
  def do_launch()

    return unless (@mission_phase == $LAUNCH || @mission_phase == "")

    @mission_phase = $LAUNCH

    # Only at start of mission
    @mission_starttime = Time.new
    copy_configs()

    # Launch phase, just sleep and wake-up
    # @launch_time minutes launch time (launch, ROV to reach bottom)
    #
    launchTimeSec = @launch_time*60 
    eventlog("_ Launch phase, sleep for #{launchTimeSec} seconds")

    # Toggle camera lights for while on deck, just to
    # reassure the team that I'm actually running :)
    #
    if (@launch_time > 10)
      syslog("_ Blink lights at start for everyone")
      for i in 1..8
        Rover.instance.relay.camerasOn
        sleep(1)
      	Rover.instance.relay.camerasOff
      	sleep(1)
      	launchTimeSec -= 4
      end
    end

    # Only do this launch stuff if the launch time is at least 5 minutes
    #
    if (@launch_time >= 5)
      # Allow air in chambers to escape, then reset relays
      #
      purge_time = self.purgeChambers #(@launch_time)
      Rover.instance.relay.allOff
     
      # Collect current data on the way to the bottom
      #
      Rover.instance.relay.acmOn
      sleep(3)
      Rover.instance.acm.run

      # Check if user decided to abort while I was busy.
      # We'll have to do this every so often.
      #      
      if(self.abort?)
        do_abort
      end

      # Sleep during dive, wake-up 10 minutes early and blink again
      #
      launchTimeSec = @launch_time > 20? launchTimeSec - 600 : launchTimeSec

      #until the issues with the v12/video are fixed, the acm should only
      #be running when the video is not. use acm.stop_thread/acm.run to toggle
      #Rover.instance.acm.run
    
      # Set-up wake-up schedule to check for an abort command
      #
      sleepPeriods = (launchTimeSec / @abort_check_interval).to_i
      syslog("_ Sleep #{@launchTimeSec} seconds during dive")
      syslog("_ Wake-up every #{@abort_check_interval} seconds to check for abort")
      syslog("_ Waking up #{sleepPeriods} times")

      while(sleepPeriods > 0)
        Rover.instance.rt.rSleep(@abort_check_interval)
       
        if(self.abort?)
	        do_abort
        end
        sleepPeriods -= 1
      end

      #the rest of the time to sleep
      sleepRemainder = launchTimeSec % @abort_check_interval
      Rover.instance.rt.rSleep(sleepRemainder)

      tn = Time.now().strftime("%Y%m%d-%H:%M:%S")
      Rover.instance.modem.write("#{tn}: Rover awake and preparing for Movement")
      if (@launch_time > 20)
        syslog("_ Blink lights at finish for everyone")
        for i in 1..5
        	Rover.instance.relay.camerasOn
        	sleep(1)
        	Rover.instance.relay.camerasOff
        	sleep(1)
        end
      end

      # Stop Acm data collection and reset all relays
      #
      Rover.instance.acm.stop_thread
      Rover.instance.relay.allOff
      
      
      if(self.abort?)
        do_abort
      end
      
    else
      Rover.instance.rt.rSleep(launchTimeSec)
    end
    
    # Done with Launch phase
    advance_phase()

  end


  #========================================================
  # Measurement phase method
  #
  def do_measure
    eventlog("_ Measure phase #{@mission_cycle} of #{@num_cycles}")

    tn = Time.now().strftime("%Y%m%d-%H:%M:%S")
    Rover.instance.modem.write("#{tn}: Rover beginning Measure phase")

    set_phase($MEASURE)

    #
    # Allow time for the measurement phase to complete.
    # Wake-up every so often (wakeInterval) and check
    # to see if the phase is complete
    #
    wakeInterval = 1   #minutes
    cm = Rover.instance.cm
    ci = cm.get("MeasurementProgressCheckInterval")
    wakeInterval = ci.to_f if (ci)
    syslog("_ Measure check interval is #{wakeInterval} minutes")

    #
    # Execute the Measure iterations
    #
    @measure.run(@mission_cycle)
    mt = @measure.thr

    
    measureTime = 0     #minutes
    totalMeasureTime = (@iterations*     \
                       (@measure.record_time+@measure.record_interval)) + 30
    pct = 0
    while (mt.alive? && measureTime < totalMeasureTime && pct < 100) do

      sleep(wakeInterval*60)
      pct = @measure.percentComplete
      syslog("_ Measurement: #{measureTime} of #{totalMeasureTime} minutes ")
      syslog("_ #{@measure.currentAction}, #{pct}% complete")

      if (self.abort?)
        @measure.abort
        eventlog("_ Aborting within do_measure method while loop")
        return
      end
    
      measureTime += wakeInterval
  
    end
    eventlog("Measurement time was #{measureTime} minutes")

    if (100 == pct)
      eventlog("_ Measurement Done")
    elsif (!mt.alive?)
      eventlog("_ Measurement thread finished")
      power_off(@measure.record_interval) if @power_off_on

    else
      eventlog("_ Measurement timed-out!")
      @measure.abort
      while (@measure.percentComplete < 100) do
        eventlog("_ Aborting Measurement process")
        sleep(30)
      end
    end
  end


  #========================================================
  # We don't want to bring oxygen from the surface down to
  # the floor, so we need to open the chamber valves for
  # a time after we hit the water.
  # 
  def purgeChambers
    st = 0
    # Open chamber valves to allow water to pass through
    # during first 10 minutes of launch - ballasting issue
    #
    unless (@launch_time > 10)
      relay = Rover.instance.relay
      ref = Rover.instance.ref_optode
      stb = Rover.instance.starboard_optode
      port = Rover.instance.port_optode
      
      syslog("_ waiting for vehicle to get wet before purging chambers")
      # Start data collection on the optodes and grab a
      # saturation values in air
      # 
      relay.optodesOn
      ref.run
      stb.run
      port.run
      sleep(3)
      dry_sat = ref.sat
        
      # Wait max of 20 minutes to get vehicle in water
      # which we determine as a saturation value change
      # of 4 or more percentage points
      #
      sat_diff = 0.0
      while (sat_diff.abs <= 4 && st < 1200)
        sat_diff = ref.sat - dry_sat
        Rover.instance.rt.rSleep(10)
        st += 10
      end
        
      # When we're in the water, open the valves for 2.5 minutes
      eventlog("_ Okay, we're in the drink, so chamber valves")
          
      relay.s_valveOn
      relay.p_valveOn
      relay.s_cleanOn
      relay.p_cleanOn
      
      Rover.instance.rt.rSleep(150)
      st += 150

      relay.s_valveOff
      relay.p_valveOff
      relay.s_cleanOff
      relay.p_cleanOff

      # Shutdown optodes and optode threads
      #
      relay.optodesOff
      ref.stop
      stb.stop
      port.stop

      eventlog("_ purging complete after #{st} seconds")
    end
    return st
  end
  

  #========================================================
  # Method returns true if both the starboard and port chambers
  # are in the home position.
  #
  def chambersHome?
    ret = false
    rack = Rover.instance.rack

    # Query the starboard home switch
    # Make an attempt to home the rack if the switch is off
    #
    rack.reinit($STBD_RACK)
    s_home = rack.home_query($STBD_RACK)
    if (!s_home || s_home > 2000)
      syslog("_ Try to move stbd rack home")
      rack.move_home($STBD_RACK)
      s_home = rack.home_query($STBD_RACK)
    end
    Rover.instance.relay.s_rackOff

    # Query the port home switch
    # Make an attempt to home the rack if the switch is off
    #
    rack.reinit($PORT_RACK)
    p_home = rack.home_query($PORT_RACK)
    if (!p_home || p_home > 2000)
      syslog("_ Try to move port rack home")
      rack.move_home($PORT_RACK)
      p_home = rack.home_query($PORT_RACK)
    end
    Rover.instance.relay.p_rackOff

    # Determine the return value (2000 seems to be a fair value)
    #
    if (s_home && s_home < 2000 && p_home && p_home < 2000)
      ret = true
    else
      eventlog("! Racks not at home after homing attempt (stbd=#{s_home}, port=#{p_home}")
    end
    return ret

  end
  

  #========================================================
  # Method returns true if the measured current direction is
  # within the given deviation of @move_heading, otherwise false.
  # 
  def favorable_current(deviation)
    acm = Rover.instance.acm
    favorable = false
    direction = false

    # Calculate desired current heading (dch)
    # Current-meter reports the direction from which the current
    # is coming. Optimally, that is 180 degrees from the
    # movement heading.
    #
    dch = @move_heading + 180
    dch = dch % 360

    # What does the current-meter say?
    #
    ch = acm.avg_current_heading().to_f

    # Handle desired current heading within #{deviation} degress of 0
    # if necessary. Otherwise, just check if the current heading
    # is within #{deviation} degrees of the desired heading
    #
    if ( (dch >= (360-deviation)) && (dch <= 360) )
      #
      # Desired heading is nearly 360
      #
      direction = true if (ch >= (dch-deviation))  # the slice up to 360
      direction = true if (ch <= (deviation-(360-dch))) # slice on the other side

    elsif ( ((dch <= deviation) && (dch >= 0)) )
      #
      # Desired heading is just above 0
      #
      direction = true if (ch <= (dch+deviation)) # the slice on the 0+ side
      direction = true if (ch >= (360-(deviation-dch))) # slice on the other side

    else
      direction = true if ((ch <= (dch+deviation)) && (ch >= dch-deviation))
    end

    # Return value is determined here
    #
    #
    # If favorable direction, check magnitude of current
    #
    if (direction)
      mag = acm.avg_magnitude_of_current
      if (mag > 2)
        syslog("_ Current is favorable: reading heading of #{ch} at #{mag}")
        favorable = true

      else
        syslog("_ Current is favorable, but mag is too small: #{mag}")
      end

    else
      syslog("_ Current not favorable: looking for ~#{dch} and reading #{ch}")
    end

    return favorable

  end


  #========================================================
  # Execute move to new measurement site.
  # FIXME! Perhaps this should be encapsulated in separate class
  # 
  def do_move()
    @cycle_starttime = Time.new  # movement is the beginning of a cycle

    eventlog("_ Movement phase #{@mission_cycle} of #{@num_cycles}")
    tn = Time.now().strftime("%Y%m%d-%H:%M:%S")
    Rover.instance.modem.write("#{tn}: Rover beginning Movement phase")

    # Only move if chambers are in the home position
    #
    return nil unless chambersHome?

    return unless @move_distance > 0

    Rover.instance.ethernet(true)
    Rover.instance.relay.transitOn
    if(@simple_move)
      go_forward()
    else 

    # Examine the current direction - turn on the acm and
    # run the driver thread.
    # Make sure video is off before powering up acm
    #
    syslog("_ Sleeping 60 seconds to let the acm accumulate data")
    Rover.instance.relay.videoOff
    Rover.instance.relay.acmOn
    sleep(3)
    Rover.instance.acm.run
    sleep(1)

    # If a heading is specified, we will wait for a period of time
    # for the current to flow in roughly the opposite direction.
    # Otherwise, just head into the current.
    # 
    wm = 0
    if (@move_heading)
      syslog("_ Looking to move at a heading of #{@move_heading}")
      direction = @move_heading

      Rover.instance.rt.rSleep(60)
      wm += 1   # minutes we've been waiting
      while (true)
        if (wm > @current_timeout)
          syslog("_ Time-out waiting for favorable current. Just go")
          break
        end

        if (favorable_current(@current_deviation))
          # Set direction here if we want a heading of @move_heading only if
          # we have favorable current (otherwise head into current)
          direction = @move_heading
          break
        end      

        if(self.abort?)
          eventlog("_ Aborting from within do_move")
          return
        end
        
        Rover.instance.rt.rSleep(6)
        wm += 0.1
      end

    else
      # Just head into the current
      #
      direction = \
      (Rover.instance.acm.avg_current_heading().to_f + 180) % 360

    end

    # Move the vehicle
    #
    eventlog("_ Moving #{@move_distance} meters, heading #{direction}")

    navigator = Navigator.new(Rover.instance)
    Rover.instance.relay.motorsOn    
    Rover.instance.rt.rSleep(10)
    
#    Rover.instance.ethernet(true)

    navigator.forward(direction, @move_distance)
    Rover.instance.acm.stop_thread

    Rover.instance.relay.motorsOff
    
    Rover.instance.ethernet(false) if (!Clo.instance.sim && !Clo.instance.warp)

    end

   # Capture one last image at the end of the move
   #
   Rover.instance.transit_cam.capture_image()
	 sleep(2)
   Rover.instance.relay.transitOff
   Rover.instance.ethernet(false) if (!Clo.instance.sim && !Clo.instance.warp)
    
 end


  #========================================================
  # go forward regardless of heading or current
  #
  def go_forward
    eventlog("_ Starting Forward movement phase")

    # Only move if chambers are in the home position
    #
    return nil unless chambersHome?
    
    #syslog("_ skipping move")
    #return
    return unless @move_distance > 0
  
    motors = Rover.instance.motors
    motors.close
  
    relay=Rover.instance.relay

#    Rover.instance.ethernet(true)
    relay.motorsOn
    sleep(2)

    Rover.instance.motors = Ezservo.rover
    motors = Rover.instance.motors

    #set up motor gains
    motors.proportional_gain = 1000
    sleep 1
    motors.integral_gain = 0
    sleep 1
    motors.velocity = 500
    sleep 1

    #send motors forward 2000 counts 
    navigator = Navigator.new(Rover.instance)
    eventlog("_ Moving forward #{@move_distance} meters")
    navigator.move_forward(@move_distance)
    eventlog("_ Movement done")
    relay.motorsOff
    #Rover.instance.ethernet(false) if (!Clo.instance.sim && !Clo.instance.warp)
    #relay.transitOff
  end                     


  #========================================================
  # Abort mission.
  #
  def abort
    eventlog("_ Aborting mission...")
    @abort = true
  end

  #========================================================
  def abort?
    @abort
  end


  #========================================================
  # Construct a string that gives a synopsis of the current mission state
  # 
  def status
    if @mission_starttime
      duration = Time.new - @mission_starttime
    else
      duration = 0
    end
    
    h = sprintf("%.3f", duration / 3600)
    status_message = String.new ""
    status_message << "Mission Time:#{h} hours:Phase #{@mission_phase}"
    if @mission_phase == $MEASURE
      status_message << ":Cycle #{@mission_cycle}/#{@num_cycles}"
      status_message << ":" << @measure.action
    end
  
    if @mission_phase == $MOVEMENT
    end
  
    status_message
  end

  #========================================================
  # Monitor threads for debugging purposes
  # When any thread in the control system is created or
  # changes state a summary is printed to stdout
  #
  def monitor_threads

    RoverThread.new do
      Thread.current["name"] = "thread monitor"
      nthreads = 0

      while (true)
	# Assume nothing has changed
	changed = false
	now = nil

	# If a thread has started or one has finished, that's a change
	if (Thread.list.length != nthreads)
	  nthreads = Thread.list.length
	  changed = true
	end

	# Check the state of every thread
	#
	Thread.list.each { |thr|

	  # See if the state has changed
	  if thr.status != thr["status"]
	    changed = true
	    thr["status"] = thr.status
	  end

	  # Produce a summary if there have been any changes
	  #
	  if changed

	    # Header line with timestamp
	    if !now
	      now = Time.now
	      syslog("----------------- Thread Activity -----------------")
	    end

	    # Assemble a blurb about the thread
	    blurb = " "
	    blurb = thr["name"] + " " if thr["name"]
	    blurb = blurb + thr.to_s
	    if (thr.status)
	      stat = thr["status"]
	    else
	      stat = "nil - quit on exception"
	    end
	    syslog("#{blurb} #{stat}")
	  end
	}

	if (changed)
	  syslog("------------------------------------------------------")
	end

	# run the monitor at 5Hz
	sleep(.20)

      end
    end
  end

end
