=begin
 ******************************************************************************
 * Copyright 1990-2009 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Aanderaa optode instrument driver. Read and log data
 * Filename : aanderaa.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 2
 * Created  : Nov 08
 * Modified : 
 ******************************************************************************
=end

# Refer to Rover-code home directory as base
#
require "#{ENV['ROVER_HOME']}/utils/rover_environment"
#

require 'rubygems'
require 'serialport'
require 'utils/datalog'
require 'utils/rover_thread'

##########
# Implements a driver/logger function for Aanderaa optodes. The optodes are
# set-up to emit data at a desired interval (e.g., 10 secs). The driver simply
# does a blocking read on the port, then reads, parses, and logs a line of data
# when one becomes available. 
#

class Aanderaa 
  include SyslogWriter

  attr_reader :rawdata, :oxy, :sat, :temp, :name, :thread

  # Args: serial port name.
  # Typical usage: op = Aanderaa.new('/dev/this_optode')
  #
  def initialize(p_portname = '/dev/stbd_ref_optode')
    serial_config_obj = SerialPort::Configuration.default
    serial_config_obj.baud = 9600

    @dev = Posix::SerialPort.new(p_portname, serial_config_obj)
    @name = File.basename(p_portname)
    @model = "Optode"
    @sn = "xyz123"

    # Initialize the object variables and constants
    @oxy  = -1
    @sat  = 0
    @temp = 0
    @thread = nil
  end

  NoComms = "no comms"
  BadData = "bad data"

  #####
  # Perform a basic health check-up by attempting to acquire data.
  # Assume that power to the optode was just applied so there will
  # be data in the buffer.
  # Return nil if check-up succeeded. Otherwise, return a string
  # describing cause of failure.
  #
  def check_up
    ##
    # Execute in a separate thread to catch comms errors.
    # Timeout after 15 seconds.
    #
    got_data = nil
    t = RoverThread.new do
      got_data = acquire_data
    end
    sleep(15)

    ##
    # If check-up thread still running, kill it and
    # report NoComms
    #
    if (t.alive?)
      t.kill
      t.join
      syslog("_ no comms")
      return NoComms
    end
    t.join

    ##
    # A return value of false from acquire_data means
    # the information from the device is malformed. A value
    # of true means all is well.
    #
    if (got_data == false)
      syslog("_ bad data")
      return BadData
    else
      syslog("_ OK")
      return nil  # Passed
    end
  end 


  ##
  # Read and log data in a separate thread
  #
  def run(data_dir=DataLog.behavior_data_home)
    if (@thread && @thread.active)
      syslog("! Already running")
      return
    end

    # May be a different deployment. Open log files just in case.
    #
    unless (data_dir && File.exist?(data_dir) && File.directory?(data_dir))
      data_dir = "."
    end

    syslog("_ Running optode thread for #{@name}")
    @logfilename = data_dir + @name + ".csv"
    @rawlog = AanderaaLog.new(@logfilename)
    @thread = RoverThread.new do
      Thread.current["name"] = @name
      while true
        log_data if acquire_data
      end
    end
  end

  ##
  # Stop the read/log thread by killing it. Set :thread to nil
  #
  def stop
    if self.is_running?
      @thread.kill if @thread
      @thread = nil
      syslog("_ Stopping #{@name} optode thread")
    end
  end

  ##
  # Close the optode data port. Stop/kill the read/log thread first
  #
  def close
    self.stop
    syslog("_ Closing serial device for #{@name}")
    @dev.close
  end

  ##
  # Perform a blocking read on the optode data port. Read a line of data, 
  # parse, and log the data elements. The optode firmware setup controls
  # the rate of data flow.
  #
  def acquire_data
    # Aanderaa simply returns data string
    #
    @rawdata = @dev.gets
    
    # Process data
    #
    @data = @rawdata.split
    
    if @data.length == 9
      @oxy  = @data[4].to_f
      @sat  = @data[6].to_f
      @temp = @data[8].to_f
    elsif @data.length == 6
      @oxy  = @data[3].to_f
      @sat  = @data[4].to_f
      @temp = @data[5].to_f
    else
      syslog("! #{@name}, @data array length is: " +
             "#{@data.length} and should be 6 or 9")
      return false
    end
        
    syslog("_ Obtained data #{@name}: o2=#{@oxy},sat=#{@sat},temp=#{@temp}")
    return true
  end

  ##
  # Write data to log file
  #
  def log_data
    @rawlog.write("#{@oxy},#{@sat},#{@temp}")
  end

  ##
  # Indicates whether read/log thread is running
  def is_running?
    if @thread
      return @thread.alive?
    else
      return false
    end
  end
  
end
##########


##########
# AanderaaLog specialized versions of DataLog class.
# Contains data from an Aanderaa optode.
#
class AanderaaLog < DataLog

  # Column headings for the log
  #
  @@cols = [
    "Measured O2 (uM)",
    "O2 Saturation (%)",
    "Water Temp (C)"
  ]

  def AanderaaLog.items
    @@cols
  end

  def initialize(p_filename)
    super(p_filename)
    write_heading if @need_heading
  end

  def write_heading
    @file.write("Rover Timestamp (ISO601),#{@@cols.join(',')}\n")
    @file.flush
  end
end
##########


##########
# SimAanderaa - simulated Aanderaa driver/logger
#
class SimAanderaa < Aanderaa

  ##
  # Args: serial port name.
  # Typical usage: op = Aanderaa.new('/dev/this_optode')
  #
  def initialize(p_portname = '/dev/stbd_ref_optode')
    @name = File.basename(p_portname)
    @model = "Optode"
    @sn = "xyz123"

    # Initialize the object variables and constants
    @oxy  = -1
    @sat  = 0
    @temp = 0
    @thread = nil
  end

  def acquire_data

    @oxy  = 23.4
    @sat  = 34.5
    @temp = 4.1
    sleep(7)
        
    syslog("_ Obtained data #{@name}: o2=#{@oxy},sat=#{@sat},temp=#{@temp}")
    return true
  end

end


##
# Standalone unit test (recommended)
# Include at the end of the file as a hook to run a unit test of
# the code from the command line (e.g., "$ ruby  my_class.rb")
#
if __FILE__ == $0
  # Test code here
end
