=begin
 ******************************************************************************
 * Copyright 1990-2007 MBARI
 * MBARI Proprietary Information. All rights reserved.
 ******************************************************************************
 * Summary  : Interfaces to the Benthos acoustic modem
 * Filename : modem.rb
 * Author   : Henthorn
 * Project  : Benthic Rover
 * Version  : 2
 * Created  : Oct  2008
 * Modified : June 2012 - Added comments about register set-up.
 * Modified : Fall 2014 - removed setup commands as they were consuming power
 ******************************************************************************
=end

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

require 'utils/misc'
require 'utils/datalog'
require 'rubygems'
require 'serialport'

# Benthos Modem commands and responses
#
# Modem register settings.
# These are written to the Rover modem using the init() method.
# We do not want to set these each time we instatiate a Modem object since
# that uses modem battery energy.
#
# Just set them up in the Rover modems and save them there.
# 
$DOPPLER =       "ATS1=1\r"
$WAIT_TIME =     "ATS7=15\r"
$WRITE_DELAY =   "ATS8=10\r"
$IDLE_TIME =     "ATS10=1\r"
$VERBOSE_LEVEL = "ATS13=00\r"
$DEV_ENABLE    = "ATS28*2\r"
$WHAT_DEV_ENABLE = "ATS28\?\r"
$SAVE          = "AT\$W\r"

$CMD_MODE =      "+++"
$ONLINE_MODE =   "ATO\r"
$CR_LF =         "\xd\xa"

$CMD_MODE_CONF = ">"
$ONLINE_CONF =   "bits/sec\xd\xa"
$CMD_CONF =      "OK\xd\xa>"
  
##########
# BenthosModem class attempts to provide a useful interface to the
# acoustic modem, to set-up, read data and write data.
#
class BenthosModem
  include SyslogWriter
  
  attr_reader :dev, :name

  ##
  # Initializer usage: modem = BenthosModem.new(nil, "/dev/lfmodem_port")
  # Does not assume modem is powered-up.
  #
  def initialize(portname='/dev/lfmodem')
#    config = SerialPort::Configuration.default
#    config.baud = 9600
      
    @name = portname
    @dev = SerialPort.new(portname) #, config)
#    @dev.baud = 115200
    @dev.sync=true
    @rd = @wr = @dev    # read and write from modem port
    @cmd_mode = false
    @log = DataLog.new(DataLog.deployment_data_home + "/modem.csv")

    # Is there a default client?
    @default = false
  end

  ##
  # Set up a modem object for SES
  #
  def BenthosModem.ses
    m = BenthosModem.new('/dev/lfmodem')
  end 

  OnlineFailed  = "no online"
  CommandFailed = "no command"
  NoComms       = "no comms"

  #####
  # Basic health check-up. Return true if passed, otherwise false.
  #
  def check_up
    ret = nil

    t = RoverThread.new do
      if (command_mode)
        syslog("_ check_up command_mode passed")
        if (online_mode)
          syslog("_ check_up online_mode passed")
          ret = nil
        else
          ret = OnlineFailed
        end
      else
        ret = CommandFailed
      end
    end
    sleep(10)

    if (t.alive?)
      t.kill
      t.join
      ret = NoComms
    end
    t.join

    ret
  end
  #####
  
  ##
  # Initialize modem using pre-defined constant commands.
  # This method should be called at the start of a deployment.
  # Does not assume modem is in "online" mode or command
  # mode.
  #
  def init()

    # Put modem in command mode
    #
    return false unless self.command_mode

    # Set verbose level, delays, and doppler correction
    #
#    return false unless command($VERBOSE_LEVEL)
#    return false unless command($WRITE_DELAY)
#    return false unless command($WAIT_TIME)
#    return false unless command($DOPPLER)
    return false unless command($IDLE_TIME)
    return false unless command($DEV_ENABLE)
    return false unless command($SAVE)

    # Set to online mode, ready for data transfer
    #
    return false unless self.online_mode

    return true    
  end

  ##
  # Close the ports
  #
  def close
    @rd.close
  #  @wr.close
  end

  ##
  # Set the modem to command mode, and confirm
  #
  def command_mode
    syslog("_ Modem - setting command mode from #{@cmd_mode}")
    @wr.write($CMD_MODE)
    if nil == self.confirm($CMD_MODE_CONF)
      @cmd_mode = true
    else
      syslog("! Modem - setting command mode failed")
      @cmd_mode = false
    end
    return @cmd_mode
  end

  ##
  # Send a modem command to the modem, confirm that
  # command was carried-out
  #
  def command(cmdstr)
    command_mode unless @cmd_mode
    syslog("_ Modem - command \"#{cmdstr}\"")
    @wr.write(cmdstr)
    if  nil == self.confirm($CMD_CONF)
      return true
    else
      syslog("!Modem - command (#{cmdstr}) unsuccessful")
      return false
    end
  end

  ##
  # Set the modem to online mode, and confirm
  #
  def online_mode
    read_data(1)   # Clear the read buffer
    
    unless @cmd_mode
      command_mode
    end
    
    unless @cmd_mode
      syslog("! Modem - Unable to command modem to online mode")
      return false
    end
    
    @wr.write($ONLINE_MODE)
    @cmd_mode = false
    return true

  end

  ##
  # Send a packet to the listening modem
  #
  # Modified to send the characters one at a time
  # to accomodate the SER1 board on the 7250-V2
  #
  def write(p)
    packet = p.to_s
    if @cmd_mode
      syslog("! Oops. Modem is in command mode: #{packet} dropped")
    else
      syslog("_ Modem transferring packet: #{packet}")
      @log.write(name,packet)
      i = 0
      while (i < packet.length)
        @wr.write(packet[i])
        sleep(0.01)
        i += 1
      end
      #@wr.write(packet)
    end
  end


  ##
  # Synonym for write
  #
  def send_msg(packet, send_anyway=false)
    if (@default || send_anyway)
      write(packet)
    end
  end

  ##
  # Read string from modem 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([@rd], nil, nil, timeout))
      str << @rd.getc
    end
    str.strip!
    
    if str.length > 0
      syslog("_ Modem received packet: #{str}")
      @log.write(name,str)
    end

    ##
    # Handle internal messages and handshakes here
    #
    if (str.strip.downcase == "default")
      @default = true
      self.send_msg("default ack")
      str = nil
    elsif (str.strip.downcase == "close")
      @default = false
      str = nil
    end

    return str
  end

  #####
  # Synonyms for read_string
  #
  def read_data(timeout=2)
    self.read_string(timeout)
  end

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

  
  # Read response from modem and confirm against expected response.
  # The confirm string will be compared to the LAST thing in the read buffer.
  # Return nil if equal, otherwise return the actual response.
  #
  def confirm(confstr)
    clen = confstr.length
    response = self.read_string

    return "" unless (response)

    # Compare the confirm string to the last part of the response
    #
    token = response.slice(0-clen, clen)
    if token == confstr
      return nil
    else
      syslog("Command unconfirmed. Expected (#{confstr}) but recv'd (#{token})")
      return token
    end
  end

  ##
  # Check to see if there is any data waiting to be read
  #
  def data_present?(timeout=0.1)
    return (nil != IO.select([@rd], nil, nil, timeout))
  end
  
  def data?(timeout=0.1)
    self.data_present?(timeout)
  end

end

##########
# Class SimModem creates a class that can be used in place of
# a BenthosModem class to simulate SES ops
#
class SimModem < BenthosModem
  include SyslogWriter

  ##
  # Use test streams for @dev
  #
  def initialize(portname="/dev/lfmodem")
    syslog("_ SimModem created")
    @cmd_mode = false
    @name = portname
    @default = true
  end

  def init()
    @log = DataLog.new("modem.csv")
    true
  end

  def close()
  end

  def check_up
    return nil
  end

  def write(packet)
    @log.write(name, packet)
  end

  def read_string(timeout=2)
    now = Time.now.to_i
    nil
  end

  def data_present?(to=0.1)
    nil
  end

end

##
# Standalone unit test (recommended)
#
if __FILE__ == $0
  # Test code here
  m = SimModem.new($stdin, $stderr)
  if (m.init)
    str = nil
    while ("quit" != str) do
      str = m.read_string
      puts(str) if (str)
    end
  end
  puts("Done")
end
