#--
#******************************************************************************
#* Copyright 1990-2007 MBARI
#* MBARI Proprietary Information. All rights reserved.
#******************************************************************************
#* Summary  : Interface to the relay board
#* Filename : relay_board.rb
#* Author   : A. Chase, Henthorn
#* Project  : Benthic Rover
#* Version  : 2
#* Created  : May-06  Basic functionality (relay-on/off)
#* Modified : May-07  RelayMan class, higher-level functions, handles multiple
#                     boards and dependencies between relays
#******************************************************************************

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

# Low-level interface to serial relay board  controller. Used by RelayMan
# class to control Rover relays.
#
class RelayBoard

  include LogHelper
  attr_reader :serial_port

  # Initializer
  #
  def initialize config, portname='/dev/relay'
    unless config
      config = SerialPort::Configuration.default
      config.baud = 9600
    end
    @serial_port = Posix::SerialPort.new portname, config
  end

  # Is this a valid relay number?
  #
  def valid_relay?(relay_num)
    if relay_num && relay_num > -1 && relay_num < 9
      true
    else
      false
    end
  end
  
  # Chew past the prompt
  #
  def flush_prompt
    @serial_port.read(3)
  end

  # Send a command to the board to close a given relay. Returns nil
  # is successful.
  # Logs invalid relay number
  #
  def turn_on(relay_num)
    if !valid_relay?(relay_num)
      syslog("! invalid relay number")
      return
    end
    num_bytes = @serial_port.write("N#{relay_num}\r")
    read = @serial_port.read(num_bytes - 1)
    flush_prompt
    nil
  end

  # Send a command to open a given relay. Returns nil on success.
  #
  def turn_off(relay_num)
    if !valid_relay?(relay_num)
      syslog("! invalid relay number")
      return
    end
    num_bytes = @serial_port.write("F#{relay_num}\r")
    read = @serial_port.read(num_bytes - 1)
    flush_prompt
    nil
  end

  # Return status of relay (open or closed). 0 = open
  #
  def get_status(relay_num)
    if !valid_relay?(relay_num)
      syslog("! invalid relay number")
      return
    end
    num_bytes = @serial_port.write("S#{relay_num}\r")
    line = @serial_port.read(num_bytes + 1)
    if(relay_num == 0)
      line = @serial_port.read(2)
    else
      line = @serial_port.read(1)
    end
    flush_prompt
    line 
  end
  
end


# High-level interface to Rover relays, a power manager of sorts.
#
class RelayMan
  include LogHelper

  # Define relay switches
  #
  ######### Rover Relay board 1 ##########
  MOTORS     = 1
  V12A       = 2  # 12V for video and cleaning pumps
  V12B       = 3  # 12V for cameras, valve pumps, optodes, acm
  V5         = 4  # stir motors
  S_VALVE    = 5  # Starboard valve pump, needs V12B
  OPTODES    = 6  # Starboard chamber optode, needs V12B
  S_RACK     = 7  # Starboard rack motor
  S_STIR     = 8  # Starboard stir motor, needs 5V

  #
  ######### Rover Relay board 2 ##########
  P_RACK    = 1  # Port rack motor
  CAMERAS   = 2  # Both cameras, needs V12B
  TRANSCAM  = 3  # Transit Prosilica camera, needs V12B
  P_VALVE   = 4  # Port valve pump, needs V12B
  P_STIR    = 5  # Port stir motor, needs 5V
  P_CLEAN   = 6  # Port cleaning motor, needs V12A
  S_CLEAN   = 7  # Starboard cleaning motor, needs V12A
  VIDEO     = 8  # Needs V12A

  attr_reader :rb1, :rb2, :acmIsOn

  # Initialize two relay boards, all relays open
  #
  def initialize
    @acmIsOn = false
    @rb1 = RelayBoard.new nil, "/dev/relay1"
    @rb2 = RelayBoard.new nil, "/dev/relay2"
    
    self.allOff
    syslog("_ RelayMan: RelayMan initialized")
  end
  
  # 5-volt power on
  def v5On
    @rb1.turn_on(V5)
    syslog("_ RelayMan: 5V On")
  end
  
  # 5-volt power off
  def v5Off
    @rb1.turn_off(V5)
    syslog("_ RelayMan: 5V Off")
  end
  
  # 12-volt power, set A, on
  def v12AOn
    @rb1.turn_on(V12A)
    syslog("_ RelayMan: 12V-A on")
  end
  
  # 12-volt power, set A, off
  def v12AOff
    @rb1.turn_off(V12A)
    syslog("_ RelayMan: 12V-A off")
  end
  
  # 12-volt power, set B, on
  def v12BOn
    @rb1.turn_on(V12B)
    syslog("_ RelayMan: 12V-B on")
  end

  # 12-volt power, set B, off
  def v12BOff
    @rb1.turn_off(V12B)
    syslog("_ RelayMan: 12V-B off")
  end
  
  # Return true if no devices that need the 12vA relay on
  # are currently on. Used to determine if Rover can also
  # shutdown power to 12V-A
  #
  def v12ANotNeeded?
    if @rb2.get_status(P_CLEAN) == "0" && @rb2.get_status(S_CLEAN) == "0" && \
       @rb2.get_status(VIDEO) == "0"
      true   # everything on V12A is off
    else
      false
    end
  end
  
  # Return true if no devices that need the 12vB relay on
  # are currently on. Used to determine if Rover can also
  # shutdown power to 12V-B.
  # 
  def v12BNotNeeded?
    if !@acmIsOn && @rb2.get_status(P_VALVE)=="0" && @rb1.get_status(S_VALVE)=="0" && \
       @rb1.get_status(OPTODES)=="0" && @rb2.get_status(CAMERAS)=="0"
      true   # everything on V12B is off
    else
      false
    end
  end

  # Turn on the acm by turning on V12B
  #   
  def acmOn
    v12BOn
    @acmIsOn = true  # since acm == v12B
    syslog("_ RelayMan: acm on")
  end

  # Flag that power to acm is not needed, may have to keep
  # v12B relay closed because other devices still need it.
  #
  def acmOff
    @acmIsOn = false
    if v12BNotNeeded?
      v12BOff if v12BNotNeeded?
      syslog("_ RelayMan: acm off")
    else
      syslog("_ RelayMan: acm is off (wink-wink)")
    end
  end
  
  # Turn on starboard cleaning pump by first turning on 12VA 
  # and then the starboard pump
  # 
  def s_cleanOn
    v12AOn
    @rb2.turn_on(S_CLEAN)
    syslog("_ RelayMan: starboard cleaning pump on")
  end
  
  # Turn off s clean: (1) s_clean off
  #                   (2) 12VA off only if all other 12vA devices are off
  # 
  def s_cleanOff
    @rb2.turn_off(S_CLEAN)
    syslog("_ RelayMan: starboard cleaning pump off")
    v12AOff if v12ANotNeeded?
  end

  # Turn on port cleaning pump by first turning on 12VA 
  # and then the port pump
  # 
  def p_cleanOn
    v12AOn
    @rb2.turn_on(P_CLEAN)
    syslog("_ RelayMan: port cleaning pump on")
  end
  
  # Turn off p clean: (1) p_clean off
  #                   (2) 12VA off only if all other 12vA devices are off
  # 
  def p_cleanOff
    @rb2.turn_off(P_CLEAN)
    syslog("_ RelayMan: port cleaning pump off")
    v12AOff if v12ANotNeeded?
  end

  # Turn on video server by first turning on 12VA and then video
  # 
  def videoOn
    v12AOn
    @rb2.turn_on(VIDEO)
    syslog("_ RelayMan: video on")
  end
  
  # Turn off video: (1) video off
  #                 (2) 12VA off only if all other 12vA devices are off
  # 
  def videoOff
    @rb2.turn_off(VIDEO)
    syslog("_ RelayMan: video off")
    v12AOff if v12ANotNeeded?
  end

  # Turn on cameras by first turning on 12VB and then cameras
  # 
  def camerasOn
    v12BOn
    @rb2.turn_on(CAMERAS)
    syslog("_ RelayMan: cameras on")
  end
  
  # Turn off cameras: (1) cameras off
  #                   (2) 12VB off only if all other 12vB devices are off
  # 
  def camerasOff
    @rb2.turn_off(CAMERAS)
    syslog("_ RelayMan: cameras off")
    v12BOff if v12BNotNeeded?
  end

  # Turn on starboard rack motor
  # 
  def s_rackOn
    @rb1.turn_on(S_RACK)
    syslog("_ RelayMan: starboard rack on")
  end
  
  # Turn off starboard rack motor
  # 
  def s_rackOff
    @rb1.turn_off(S_RACK)
    syslog("_ RelayMan: starboard rack off")
  end
  
  # Turn on port rack motor
  # 
  def p_rackOn
    @rb2.turn_on(P_RACK)
    syslog("_ RelayMan: port rack on")
  end
  
  # Turn off port rack motor
  # 
  def p_rackOff
    @rb2.turn_off(P_RACK)
    syslog("_ RelayMan: port rack off")
  end
  
  # Turn on the propulsion motors
  # 
  def motorsOn
    @rb1.turn_on(MOTORS)
    syslog("_ RelayMan: propulsion motors on")
  end
  
  # Turn off the propulsion motors
  # 
  def motorsOff
    @rb1.turn_off(MOTORS)
    syslog("_ RelayMan: propulsion motors off")
  end
  
  def transitOn
    v12BOn
    @rb2.turn_on(TRANSCAM)
    syslog("_ RelayMan: transit camera on")
  end
  
  def transitOff
    @rb2.turn_off(TRANSCAM)
    syslog("_ RelayMan: transit camera off")
    v12BOff if v12BNotNeeded?
  end

  # Turn on the optodes
  # (1) turn on both 12v supplies
  # (2) turn on optodes
  # 
  def optodesOn
    v12AOn
    v12BOn
    @rb1.turn_on(OPTODES)
    syslog("_ RelayMan: optodes on")
  end
  
  # Turn off the optodes
  # (1) turn off optodes
  # (2) turn off 12vA if not needed
  # (3) turn off 12vB if not needed
  # 
  def optodesOff
    @rb1.turn_off(OPTODES)
    syslog("_ RelayMan: optodes off")
    v12AOff if v12ANotNeeded?
    v12BOff if v12BNotNeeded?
  end

  # Turn on starboard valve pump
  #  
  def s_valveOn
    v12BOn
    @rb1.turn_on(S_VALVE)
    syslog("_ RelayMan: starboard valve open")
  end
  
  # Turn off starboard valve pump
  # Turn off power if not needed
  # 
  def s_valveOff
    @rb1.turn_off(S_VALVE)
    syslog("_ RelayMan: starboard valve closed")
    v12BOff if v12BNotNeeded?
  end
  
  # Turn on port valve pump
  #  
  def p_valveOn
    v12BOn
    @rb2.turn_on(P_VALVE)
    syslog("_ RelayMan: port valve open")
  end
  
  # Turn off port valve pump
  # Turn off power if not needed
  # 
  def p_valveOff
    @rb2.turn_off(P_VALVE)
    syslog("_ RelayMan: port valve closed")
    v12BOff if v12BNotNeeded?
  end
  
  # Turn on starboard stir motor
  # 
  def s_stirOn
    v5On
    @rb1.turn_on(S_STIR)
    syslog("_ RelayMan: starboard stir motor on")
  end
  
  # Turn off starboard stir motor
  # 
  def s_stirOff
    @rb1.turn_off(S_STIR)
    syslog("_ RelayMan: starboard stir motor off")
    v5Off if @rb2.get_status(P_STIR) == "0"
  end
  
  # Turn on port stir motor
  # 
  def p_stirOn
    v5On
    @rb2.turn_on(P_STIR)
    syslog("_ RelayMan: port stir motor on")
  end
  
  # Turn off port stir motor
  # 
  def p_stirOff
    @rb2.turn_off(P_STIR)
    syslog("_ RelayMan: port stir motor off")
    v5Off if @rb1.get_status(S_STIR) == "0"
  end

  # Open all relays
  #
  def allOff
    for i in 1..8
      @rb1.turn_off(i)
      @rb2.turn_off(i)
    end
    syslog("_ RelayMan: all off")
  end
  
end


# SimRelayBoard: Simulated RelayBoard class
#
class SimRelayBoard
  include LogHelper
  
  def initialize config, portname='/dev/relay'
    syslog("_ initializer called with portname = #{portname}")
    @relay_status = {1 => "0", 2 => "0", 3 => "0", 4 => "0",
                     5 => "0", 6 => "0", 7 => "0", 8 => "0"}
  end
  
  def get_status(relay_num)
    if !valid_relay?(relay_num)
      syslog("! invalid relay number")
      return
    end
    @relay_status[relay_num]
  end
  
  def turn_on(relay_num)
    if !valid_relay?(relay_num)
      syslog("! invalid relay number")
      return
    end
    @relay_status[relay_num] = "1"
    nil
  end
  
  def turn_off(relay_num)
    if !valid_relay?(relay_num)
      syslog("! invalid relay number")
      return
    end
    @relay_status[relay_num] = "0"
    nil
  end  

  def valid_relay?(relay_num)
    if relay_num && relay_num > 0 && relay_num < 9
      true
    elsif relay_num == 0
      syslog("Relay number 0 not supported by mock relay board")
      false
    else
      false
    end
  end
  
end

# SimRelayMan: Simulated RelayMan derives from RelayMan
#
class SimRelayMan < RelayMan
  include LogHelper

  def initialize
    @acmIsOn = false
    @rb1 = SimRelayBoard.new nil, "/dev/relay1"
    @rb2 = SimRelayBoard.new nil, "/dev/relay2"
    
    self.allOff
    syslog("_ SimRelayMan initialized")
  end

end

