#!/usr/local/bin/ruby
#
=begin
 ******************************************************************************
 * Copyright 1990-2011 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Wakey board driver. Power control and hibernate controller.
 * Filename : wakey.rb
 * Author   : Henthorn
 * Project  : SES/Benthic Rover
 * Version  : 1
 * Created  : August 2011
 * Modified : June 2014   RGH  Integration with Rover and SES
 ******************************************************************************

sleep           sleep after delay
setsleep        set sleep time in minutes
setdelay        set delay time in seconds
settimeout      set max time awake in minutes
readsleep       read sleep time
readdelay       read delay time
readtimeout     read max time awake
set12von        turn 12V on
set12voff       turn 12V off
read12v         read state of 12V
setbatt         set battery voltage calibration
readbatt        read battery voltage
readwake        read cause of last wakeup
setserno        set serial number
readserno       read serial number
help or ?       print command list

=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 wakey board.
# "setdelay" "sleep" "setsleep" "set12von" "set12voff" "readwake" "read12v"
#

class Wakey 
  include SyslogWriter

  attr_reader :dev, :name, :wake_status, :sn
  attr_reader :sleep_time

  # Args: serial port name.
  # Typical usage: op = Wakey.new('/dev/port')
  #
  def initialize(p_portname = '/dev/wakey')
    #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} -opost -isig -icanon -echo`

    @name = File.basename(p_portname)
    @model = "wakey board 1.0"
    @sn = "1"

    log_filename = DataLog.system_logs + "/voltage.csv"
    @log = WakeyLog.new(log_filename)

    # Initialize the object variables and constants
    @sleep_time = 1

    # FIXME: read sometimes returns nil
    #
    @sn = self.read_serial_num.to_i
    self.read_wake()
    #self.set_delay_time(5)  #seconds
    
    self.log_voltage()
  end

  NoComms = "no comms"
  BadData = "bad data"
  NormalReset  = 0       # "normal reset" : power reapplied to the wakey board
  NormalWake   = 1       # "normal wakeup": power reapplied after hibernation
  ModemTraffic = 2       # "": wakey alerted by modem traffic
  OtherWake    = 3       # Did not read a normal response to 'readwake'
  

  #####
  # Query the serial number and return the respones
  #
  def read_serial_num
    @dev.write("readserno\r")
    get_msg
  end
  
  #####
  # Log the battery voltage
  #
  def log_voltage
    v = "error" unless (v = self.read_batt())
    @log.write(v)
  end
  #####
  
  
  #####
  # Turn 12V power on
  #
  def v12_on
    self.log_voltage()
    @dev.write("set12von\r")
    get_msg
  end
  #####


  #####
  # Turn 12V power off
  #
  def v12_off
    @dev.write("set12voff\r")
    get_msg
  end
  #####


  #####
  # Hibernate the given amount of minutes
  # Caller should see the minutes returned
  # 
  def hibernate_setup(minutes)
    minutes = minutes.to_i
    syslog("_ hibernate_setup(#{minutes})")
    return nil if (minutes < 1)

    # Set the delay time to 2 seconds before shutdown
    #
    syslog("_ hibernate_setup delay 2 seconds")
    response = set_delay_time(2)
    return nil unless (response.to_i == 2)

    # Set the hibernation duration in minutes
    #
    syslog("_ hibernate_setup hibernate for #{minutes} minutes")
    response = set_hibernate_time(minutes)
    return response unless (response.to_i == minutes)

    return response.to_i
  end
  #####



  #####
  # Hibernate the set amount of minutes
  # Returns the sleep duration in minutes if successful
  #
  def hibernate(minutes=nil)
    msg = hibernate_setup(minutes) if (minutes)
    return msg unless (msg.to_i == minutes.to_i)

    self.log_voltage()

    # Tell wakey to sleep, check response
    #
    syslog("_ hibernate says \"sleep\"")
    @dev.write("sleep\r")
    msg = get_msg(0.5)

    return nil unless (msg)

    tokens = msg.split
    return nil unless (tokens[1] == "Sleeping")

#    syslog("_ umount USBs")
#    puts(`umount /mnt/usb0`)
#    puts(`umount /mnt/usb1`)
    
    return tokens[2].to_i
  end
  #####


  #####
  # Read the maximum time wakey will allow the system to remain awake
  # "readtimeout"
  #
  def read_timeout
    @dev.write("readtimeout\r")
    msg = get_msg
    if (msg)
      syslog("_ read_timeout? #{msg}")
      tokens = msg.split
      msg = tokens[4] if (tokens.length == 7)
    else
      syslog("! no response to readtimeout")
    end
    msg
  end
  #####


  #####
  # Set the maximum time wakey will allow the system to remain awake
  # "settimeout"
  #
  def set_timeout(minutes)
    syslog("_ Setting timeout time to #{minutes} minutes")
    return nil unless (minutes.to_i >= 0 && minutes.to_i <= 180)
    
    @dev.write("settimeout\r")
    msg = get_msg
    tokens = msg.split if (msg)
    unless (msg && tokens.length == 11 && tokens[7] == "1092,")
      if (msg)
        syslog("! Bad settimeout response: #{tokens}")
      else
        syslog("! No settimeout response")
      end
      return nil
    end
    
    val = (minutes.to_i).to_s + "\r"
    @dev.write(val)
    if (msg = get_msg(0.5))
    else
      return nil
    end
    minutes
  end
  #####


  #####
  # Inquire about the hibernate time
  #
  def read_hibernate
    @dev.write("readsleep\r")
    msg = get_msg
    if (msg)
      syslog("_ read_hibernate? #{msg}")
      tokens = msg.split
      msg = tokens[4] if (tokens.length == 7)
    else
      syslog("! no response to readsleep")
    end
    msg
  end
  #####


  #####
  # Inquire about the battery voltage
  #
  def read_batt
    @dev.write("readbatt\r")
    msg = get_msg(0.5)
    if (msg)
      syslog("_ readbatt? #{msg}")
      tokens = msg.split
      msg = tokens[1] if (tokens.length == 3)
    else
      syslog("! no response to readbatt")
    end
    msg
  end
  #####


  #####
  # Inquire what caused the latest power-up
  #
  def read_wake
    @dev.write("readwake\r")
    msg = get_msg
    if (msg)
      syslog("_ readwake? #{msg}")
      
      ##
      # Attempt to determine cause of last power startup
      #
      if (msg.include?("normal wakeup"))
        @wake_status = NormalWake
      elsif (msg.include?("normal reset"))
        @wake_status = NormalReset
      else
        @wake_status = OtherWake
      end

      tokens = msg.split
      msg = tokens[1] if (tokens.length == 3)
    else
      syslog("! no response to readwake")
    end
    msg
  end
  #####


  #####
  # Inquire about 12V power
  #
  def v12?
    @dev.write("read12v\r")
    msg = get_msg
    if (msg)
      syslog("_ v12? #{msg}")
      tokens = msg.split
      if (tokens.length == 5)
        msg = tokens[3]
        if (msg == "ON")
          return true
        elsif (msg == "OFF")
          return false
        end
      end
    else
      syslog("! no response to readwake")
    end
    msg
  end
  #####


  #####
  # Set the delay time
  #
  def set_delay_time(seconds)
    syslog("_ Setting delay time to #{seconds} seconds")
    return nil unless (seconds.to_i > 0 && seconds.to_i <= 60)
    
    @dev.write("setdelay\r")
    msg = get_msg
    tokens = msg.split if (msg)
    unless (msg && tokens.length == 9 && tokens[8] == "60):")
      syslog ("! Bad setdelay response: #{tokens}") if (msg)
      syslog ("! No setdelay response") unless (msg)
      return nil
    end
    
    val = (seconds.to_i).to_s + "\r"
    @dev.write(val)
    if (msg = get_msg(0.5))
      syslog("_ #{msg}")
      msg = msg.split
      syslog("_ #{msg[4]}")
    else
      return nil
    end
    seconds
  end
  #####


  #####
  # Set the hibernate time
  #
  def set_hibernate_time(minutes)
    syslog("_ Setting hibernation time to #{minutes} minutes")
    return nil unless (minutes.to_i > 0 && minutes.to_i <= 1092)
    
    @dev.write("setsleep\r")
    msg = get_msg
    tokens = msg.split if (msg)
    unless (msg && tokens.length == 9 && tokens[8] == "1092):")
      syslog ("! Bad setsleep response: #{tokens}") if (msg)
      syslog ("! No setsleep response") unless (msg)
      return nil
    end
    
    val = (minutes.to_i).to_s + "\r"
    @dev.write(val)
    if (msg = get_msg(0.5))
    else
      return nil
    end
    minutes
  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
      @dev.write("read12v\r")
      sleep(1)
      got_data = get_msg
    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 == nil)
      syslog("_ bad data")
      return BadData
    else
      syslog("_ OK")
      return nil  # Passed
    end
  end 


  #####
  # Read results of a command
  #
  def get_response
    msg = get_msg

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

    msg
  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("_ Wakey received packet: #{str}")
    end

    return str
  end


  #####
  # See if the Wakey board is functional
  # TODO: In a separate thread, attempt to read the serial number. Return nil if good.
  #
  def check_up
    nil  # Status is good
  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)
    return str unless (str)

    ##
    # Replace newlines with spaces
    str.gsub!("\n"," ")
    str.gsub!("\r"," ")
    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

end
##########


##########
# WakeyLog specialized versions of DataLog class.
# Contains data from the wakey.
#
class WakeyLog < DataLog

  # Column headings for the log
  #
  @@cols = [
    "Battery Voltage (V)"
  ]

  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 implementation
# Used so a process doesn't lock-up the wakey port. 
#
if __FILE__ == $0
  # Use a semaphore file to ensure serial access to the wakey port
  #
  File.new("/tmp/wakey.lock", File::CREAT || File::EXCL)
  
  # Get the command and execute if valid
  # Write the result to stdio
  #
  w = Wakey.new('/dev/wakey')
  cmd = ARGV[0]

  # Is there a command in ARGV?
  #
  unless (cmd)
    puts("No command!")
    exit
  end
  
  # Is the command a valid Wakey method?
  #
  unless(w.respond_to?(cmd))
    puts "#{cmd}: unknown command!"
    exit
  end
  
  # Zero or one argument
  #
  if (ARGV[1])
    result = w.send(cmd, ARGV[1])
  else
    result = w.send(cmd)
  end
  puts(result)
end
