#--
#******************************************************************************
#* 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  : Integrated real and simulated drivers
#* Created  :
#* Modified :
#******************************************************************************

require 'rover_environment'
require 'rover_utils'
require 'posix/serialport'

# Benthos Modem commands and responses
#
$VERBOSE_LEVEL = "ATS13=00\r"
$WRITE_DELAY =   "ATS8=10\r"
$WAIT_TIME =     "ATS7=10\r"
$DOPPLER =       "ATS1=1\r"
$IDLE_TIME =     "ATS10=36\r"
$CMD_MODE =      "+++\r"
$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 LogHelper
  
  attr_reader :dev
  
  # Initializer usage: modem = BenthosModem.new(nil, "/dev/modem_port")
  # Defaults to "online" mode (send and recv data) rather than command
  # mode (set up)
  #
  def initialize config, portname='/dev/modem' 
      unless config
        config = SerialPort::Configuration.default
        #default baud is 38.4k, let's change it to 9600
        config.baud = 9600
      end
      
    @dev = Posix::SerialPort.new portname, config
    @dev.sync=true
    @cmdMode = false
  end

  # Set up a modem object for Rover
  #
  def BenthosModem.rover
    m = BenthosModem.new nil, '/dev/modem'
  end 

  # Initialize modem using pre-defined constant commands
  #
  def init

    # Put modem in command mode
    #
    return false unless self.commandMode
    
    # 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)

    # Set to onlineMode, ready for data transfer
    #
    return false unless self.onlineMode

    return true    
  end

  # Close the port
  #
  def close
    @dev.close
  end

  # Return non-false if we're in command mode
  #
  def commandMode?
    @cmdMode
  end
  
  # Set the modem to command mode, and confirm
  #
  def commandMode
    syslog("_ Modem - setting command mode from #{@cmdMode}")
    @dev.write($CMD_MODE)
    if nil == self.confirm($CMD_MODE_CONF)
      @cmdMode = true
    else
      syslog("! Modem - setting command mode failed")
      @cmdMode = false
    end
    return @cmdMode
  end
  
  # Send a modem command to the modem, confirm that
  # command was carried-out
  #
  def command(cmdstr)
    commandMode unless @cmdMode
    syslog("_ Modem - command \"#{cmdstr}\"")
    @dev.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 onlineMode
    readData(1)   # Clear the read buffer
    
    unless @cmdMode
      commandMode
    end
    
    unless @cmdMode
      syslog("! Modem - Unable to command modem to online mode")
      return false
    end
    
    @dev.write($ONLINE_MODE)
    @cmdMode = false
    return true
    
#    resp = self.confirm($ONLINE_CONF)
#    if resp == nil
#      @cmdMode = false
#    elsif resp == $CR_LF
#      @cmdMode = false
#    end
#    return !@cmdMode
  end

  # Send a packet to the listening modem
  #
  def write(packet)
    if @cmdMode
      syslog("! Oops. Modem is in command mode: #{packet} dropped")
    else
      syslog("_ Modem transferring packet: #{packet}")
      @dev.write(packet) unless @cmdMode
    end
  end

  # Read string from modem and return it, timeout in seconds
  #
  def readString(timeout=2)
    a = Array.new(1,@dev)
    str = String.new("")
    while(IO.select(a, nil, nil, timeout))
      str << @dev.getc
    end
    if str.length > 0
      syslog("_ Modem received packet: #{str}")
    end
    return str
  end
  
  def readData(timeout=2)
    self.readString(timeout)
  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.readString
    
    # Compare the confirm string to the last part of the response
    #
    token = response.slice(0-clen, clen)
    if token == confstr
      return nil
    else
      syslog("!Modem - command not confirmed. Expected (#{confstr}) but recv'd (#{token})")
      return token
    end
  end

  # Check to see if there is any data waiting to be read
  #
  def dataPresent?
    a = Array.new(1,@dev)
    return (nil != IO.select(a, nil, nil, 0.1))
  end
  
end


# Class SimModem creates a class that can be used in place of
# a BenthosModem class to simulate Rover ops
#
class SimModem
  include LogHelper

  def initialize(config, portname = "/dev/modem")
    syslog("_ SimModem created on port #{portname}")
    @cmdMode = false
  end

  def SimModem.rover
    m = SimModem.new(nil)
  end

  def init
    syslog("_ SimModem initialized")
    return true
  end

  def write(packet)
    syslog("_ SimModem transferring packet: #{packet}")
    packet.size
  end

  def readData(timeout=2)
    sleep(timeout)
    syslog("_ SimModem: nothing read")
  end

  def dataPresent?
    false
  end

end
