=begin
 ******************************************************************************
 * Copyright 1990-2011 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Fluorometer instrument driver. Read and log data
 * Filename : fluorometer.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 1
 * Created  : August 2011
 * 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 the SES fluorometer.
# "dark" "measure" "timeset" "lighton" "lightoff"
#

class Fluorometer 
  include SyslogWriter

  attr_reader :dev, :name
  attr_reader :m_time, :m_690, :m_590

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

    #@dev = Posix::SerialPort.new(p_portname, serial_config_obj)
    @dev = SerialPort.new(p_portname)
    #`stty -F #{p_portname} sane`
    #`stty -F #{p_portname} -opost -isig -icanon -echo`

    @name = File.basename(p_portname)
    @model = "fluorometer"
    @sn = "xyz123"

    log_filename = DataLog.deployment_data_home + "/fluorometer.csv"
    @log = FluorometerLog.new(log_filename)

    # Initialize the object variables and constants
    @m_time = 4000     # Maximum 4 second wait for a response
    @m_690  = -1
    @m_590  = -1

  end

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


  #####
  # Turn light on
  #
  def light_on
    @dev.write("lighton\r")
  end
  #####


  #####
  # Turn light off
  #
  def light_off
    @dev.write("lightoff\r")
  end
  #####


  #####
  # Read results from a measurement from either
  # a "measure" or a "dark" command.
  #
  def get_measurement
    msg = get_msg

    ##
    # Return nil if there is no message
    unless (msg)
      syslog("! No measurement read")
      return nil
    end

    ##
    # Return nil if message format is unexpected
    tokens = msg.split
    syslog("_ #{tokens}")
    unless (tokens.length == 7)
      syslog("! Bad measurement format: #{tokens.length} tokens")
      return nil
    end
    unless ("measurement..." == tokens[2])
      syslog("! Bad measurement format: 3rd token = #{tokens[2]}")
      return nil
    end

    ##
    # Return the measurement values
    @m_time = tokens[3].to_i
    @m_590  = tokens[4].to_i
    @m_690  = tokens[4].to_i
    return tokens[3..5]
  end
  #####


  #####
  # Set the integration time
  #
  def time_set(milliseconds)
    syslog("_ Setting integration time to #{milliseconds} milliseconds")
    return nil unless (milliseconds.to_i > 0 && milliseconds.to_i <= 10000)
    
    @dev.write("timeset\r")
    msg = get_msg
    tokens = msg.split if (msg)
    unless (msg && tokens.length == 9 && tokens[8] == "10000):")
      syslog ("! Bad timeset response: #{tokens}") if (msg)
      syslog ("! No timeset response") unless (msg)
      return nil
    end
    
    val = (milliseconds.to_i).to_s + "\r"
    @dev.write(val)
    if (msg = get_msg)
      @m_time = milliseconds
    else
      return nil
    end
    milliseconds
  end
  #####


  #####
  # Take a normal "measure" measurement. Return array consisting of
  # the time, 590 value, and 690 value.
  #
  def measure(wait_time_in_milliseconds = @m_time)
    delay = (wait_time_in_milliseconds.to_i + 500) / 1000.0
    @dev.write("measure\r")
    syslog("_ waiting #{delay} for measurement...")
 
    sleep(delay)
    return get_measurement
  end
  #####
  
  
  #####
  # Take a "dark" measurement. Return array consisting of
  # the time, 590 value, and 690 value.
  #
  def dark(wait_time_in_milliseconds = @m_time)
    delay = (wait_time_in_milliseconds.to_i + 500) / 1000.0
    @dev.write("dark\r")
    syslog("_ waiting #{delay} for measurement...")
 
    sleep(delay)
    return get_measurement
  end
  #####
  
  
  #####
  # Perform a basic health check-up by attempting to acquire data.
  #
  def check_up
    ##
    # Execute in a separate thread to catch comms errors.
    # Timeout after 15 seconds.
    #
    got_data = nil

    t = RoverThread.new do
    end
    sleep(5)

    ##
    # 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

    if (got_data == false)
      syslog("_ bad data")
      return BadData
    else
      syslog("_ OK")
      return nil  # Passed
    end
  end 


  ##
  # Read string from serial port and return it, timeout in seconds
  # Not really sure if we need to use select and getc
  #
  def read_string(timeout=2)
    str = String.new("")
    while(IO.select([@dev], nil, nil, timeout))
      str << @dev.getc
    end
    str.strip!
    
    if str.length > 0
      syslog("_ Fluorometer received packet: #{str}")
    end

    return str
  end

  #####
  # Synonyms for read_string
  #
  def read_data(timeout=1.5)
    return nil unless (msg = self.get_msg(timeout))
    lines = msg.split("\n")
    tokens = lines[1].split
    return tokens
  end

  def get_msg(timeout=0.1)
    str = self.read_string(timeout)
    str = nil if (str == "")
    str
  end

  ##
  # Close the serial port.
  #
  def close
    syslog("_ Closing serial device for #{@name}")
    @dev.close
  end

  ##
  def flush_data
    #
    if (str = get_msg())
      syslog("_ #{@name} flushing #{str}")
      return true
    else
      syslog("_ #{@name} nothing to flush")
      return false
    end
  end

  ##
  # Write data to log file
  #
  def log_data(dt,d6,d5,rt,r6,r5,lt,l6,l5,ft,f6,f5,pt,p6,p5)
    @log.write(" #{dt}, #{d6}, #{d5}, \
#{rt}, #{r6}, #{r5}, \
#{lt}, #{l6}, #{l5}, \
#{ft}, #{f6}, #{f5}, \
#{pt}, #{p6}, #{p5}")
  end

end
##########


##########
# FluorometerLog specialized versions of DataLog class.
# Contains data from the fluorometer.
#
class FluorometerLog < DataLog

  # Column headings for the log
  #
  @@cols = [
    "Dark Ref Integration Time",
    "Dark Ref 690 Counts",
    "Dark Ref 590 Counts",

    "Measure Ref Integration Time",
    "Measure Ref 690 Counts",
    "Measure Ref 590 Counts",

    "Luminescense Integration Time",
    "Luminescense 690 Counts",
    "Luminescense 590 Counts",

    "Fluorescense Integration Time",
    "Fluorescense 690 Counts",
    "Fluorescense 590 Counts",

    "Phosphorescense Integration Time",
    "Phosphorescense 690 Counts",
    "Phosphorescense 590 Counts"
  ]

  def FluorometerLog.items
    @@cols
  end

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

  def write_heading
    @file.write("SES Timestamp (ISO601),#{@@cols.join(',')}\n")
    @file.flush
  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
