############################## relay_board.rb achase@mbari.org #################
#
# Control the (temporary) relay board for switching power on the rover
#
# The relay boards response protocol is slightly non-standard. When a on or off
# command is sent the relay board echoes the command characters but nothing else
# however, when the status command is sent the relay board echoes both the command
# characters and a newline/carriage return. That is why the reads on the turn_on
# and turn_off commands subtract 1 from their value and the read from the status
# adds 1 (1, not 2, because there is already a \r character, so we just have to
# subtract it, or add the \n character)
#
# May07 - adapted for use with multiple relay boards. Moved all the On and Off
#         methods into a new RelayMan class that manages the two relay boards.
# 
################################################################################

require 'rover_environment'
require 'posix/serialport'
require 'rover_utils'

class RelayBoard

  include LogHelper
  attr_reader :serial_port

  def initialize config, portname='/dev/relay'
    unless config
      config = SerialPort::Configuration.default
      config.baud = 9600
    end
    @serial_port = Posix::SerialPort.new portname, config
  end

  def valid_relay?(relay_num)
    if relay_num && relay_num > -1 && relay_num < 9
      true
    else
      false
    end
  end
  
  def flush_prompt
    @serial_port.read(3)
  end

  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

  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

  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


class RelayMan
  ##################################
  # Power Manager
  # Manage power relay switches
  # 
  # Define relay switches
  #
  ######### Relay board 1 ##########
  MOTORS     = 1
  V12A       = 2  # 12V for video and cleaning pumps
  V12B       = 3  # 12V for cameras, valve pumps, optodes, acm
  V5         = 4
  S_VALVE    = 5  # Starboard valve pump, needs V12B
  S_OPTODE   = 6  # Starboard chamber optode, needs V12B
  REF_OPTODE = 6  # Reference optode also on relay 6, needs V12B
  S_RACK     = 7  # Starboard rack motor
  S_STIR     = 8  # Starboard stir motor

  #
  ######### Relay board 2 ##########
  P_RACK    = 1  # Port rack motor
  CAMERAS   = 2  # Both cameras, needs V12B
  P_OPTODE  = 3  # Port chamber optode, needs V12B
  P_VALVE   = 4  # Port valve pump, needs V12B
  P_STIR    = 5  # Port stir motor
  P_CLEAN   = 6  # Port cleaning motor, needs V12A
  S_CLEAN   = 7  # Starboard cleaning motor, needs V12A
  VIDEO     = 8  # Needs V12A

  include LogHelper

  attr_reader :rb1, :rb2, :acmIsOn
  
  def initialize
    @acmIsOn = false
    @rb1 = RelayBoard.new nil, "/dev/relay1"
    @rb2 = RelayBoard.new nil, "/dev/relay2"
    
    self.allOff
    syslog("_ RelayMan: RelayMan initialized")
  end
  
  def v5On
    @rb1.turn_on(V5)
    syslog("_ RelayMan: 5V On")
  end
  
  def v5Off
    @rb1.turn_off(V5)
    syslog("_ RelayMan: 5V Off")
  end
  
  def v12AOn
    @rb1.turn_on(V12A)
    syslog("_ RelayMan: 12V-A on")
  end
  
  def v12AOff
    @rb1.turn_off(V12A)
    syslog("_ RelayMan: 12V-A off")
  end
  
  def v12BOn
    @rb1.turn_on(V12B)
    syslog("_ RelayMan: 12V-B on")
  end

  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
  # 
  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
  # 
  def v12BNotNeeded?
    if !@acmIsOn && @rb2.get_status(P_VALVE)=="0" && @rb1.get_status(S_VALVE)=="0" && \
       @rb1.get_status(REF_OPTODE)=="0" && @rb2.get_status(CAMERAS)=="0" && \
       @rb2.get_status(P_OPTODE)=="0" && @rb1.get_status(S_OPTODE)=="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
  
  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")
    #sleep(1)
    #Rover.instance.s_rack.reinit()
    #sleep(1)
  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")
    #sleep(1)
    #Rover.instance.p_rack.reinit()
    #sleep(1)
  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
  
  # Turn on the starboard chamber optode
  # (1) turn on 12v supply
  # (2) turn on optode
  # 
  def s_optodeOn
    v12BOn
    @rb1.turn_on(S_OPTODE)
    syslog("_ RelayMan: starboard optode on")
  end
  
  # Turn off the starboard chamber optode
  # (1) turn off optode
  # (2) turn off 12vB if cameras, valve pumps, and acm are also off
  # 
  def s_optodeOff
    @rb1.turn_off(S_OPTODE)
    syslog("_ RelayMan: starboard optode off")
    v12BOff if v12BNotNeeded?
  end

  # Reference optode is on the same relay as starboard optode
  #
  def ref_optodeOn
    s_optodeOn
    syslog("_ RelayMan: reference optode on")
  end
    
  def ref_optodeOff
    s_optodeOff
    syslog("_ RelayMan: reference optode off")
  end
    
  # Turn on the port chamber optode
  # (1) turn on 12v supply
  # (2) turn on optode
  # 
  def p_optodeOn
    v12BOn
    @rb2.turn_on(P_OPTODE)
    syslog("_ RelayMan: port optode on")
  end
  
  # Turn off the port chamber optode
  # (1) turn off optode
  # (2) turn off 12vB if cameras, valve pumps, and acm are also off
  # 
  def p_optodeOff
    @rb2.turn_off(P_OPTODE)
    syslog("_ RelayMan: port optode off")
    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
  
  def allOff
    for i in 1..8
      @rb1.turn_off(i)
      @rb2.turn_off(i)
    end
    syslog("_ RelayMan: all off")
  end
  
end

