#####################  shaft.rb -- brent@mbari.org  ######################
# $Source: /home/cvs/ESP/gen2/software/esp/lib/shaft.rb,v $
#    Copyright (C) 2003 MBARI
#    MBARI Proprietary Information. All rights reserved.
# $Id: shaft.rb,v 1.31 2005/05/16 17:54:25 brent Exp $
#
#     Rotary Shaft control
#
#  This layers on top of I2C/Shaft to define operations on 
#  abstract shafts.
#
############################################################################

require 'i2c/shaft'
require 'axis'

class Shaft < RotaryAxis

  class Error < RotaryAxis::Error
    def initialize text, axis, reply
      @reply = reply
      super text, axis
    end
    attr_reader :reply    
  end
  
  def initialize(shaftName, dwarf, shaftNumber,
                 cfg=I2C::Shaft::Configuration.default,
                 maxDuration=10, slop=cfg.maxError+cfg.lash)
  #return a new Shaft object
  #dwarf is the network target that receives replies and sources requests
  #shaftNumber is the shaft channel (typically 0..3)
  #cfg is the device specific configuration
  #maxDuration is the max # of seconds to await a reply
  #slop is the size of detents about every position in counts
    super
  end
  
  def rawAngle
  #return the raw angle in counts
    status.angle
  end
  alias_method :rawPosition, :rawAngle
  
  def position slop=@slop
  #return the named position the shaft is in
  #  if shaft is not in a named position, return a RotaryAxis::Between object
  #if last rotate succeeded, don't bother querying dwarf for position
    @position ? @position : angleId(rawAngle, slop)
  end

  def dial goal, direction=nil, maxDuration=@maxDuration
  #rotate to the specified goal position in specified direction
    args = [goal]
    if maxDuration == @maxDuration
      args << direction if direction
    else
      args << direction << maxDuration
    end      
    Thread.log.recordMethod self, :dial, args
    dialQuietly goal, direction, maxDuration
  end
  alias_method :rotateTo, :dial
  alias_method :to, :dial
  alias_method :select, :dial
  
  #interpret any undefined method as dial to that goal position!
  alias_method :method_missing, :dial
  
  def upto goal, maxDuration=@maxDuration
    dial goal, :up, maxDuration
  end
  
  def downto goal, maxDuration=@maxDuration
    dial goal, :down, maxDuration
  end
    
  def dialBetween start, terminus, direction=nil, maxDuration=@maxDuration
    dial(between(start,terminus), direction, maxDuration)
  end
  alias_method :rotateBetween, :dialBetween

  def forget
  #forget the last position rotated to so next rotate will always occur
    @position = nil
  end
  alias_method :forgetPosition, :forget
  
  def stop maxDuration=5
  #stop specified shaft (implemented as a truncated rotate request)
    Thread.log.recordMethod self, :stop, maxDuration==5 ? [] : [maxDuration]
    begin
      reply = @dwarf.rotate @channel, nil, nil, maxDuration
    rescue =>err
      err.message[0,0]="Failed to stop #{@name}:  "
      raise
    end
    raiseErr reply if reply.error  
    angleId reply.angle
  end
#  alias_method :coast, :stop

  protected  #just so advance and retard work
  def seek goal, maxDuration=@maxDuration
    dial goal, nil, maxDuration
  end
  
  def dialQuietly goal, direction=nil, maxDuration=@maxDuration
  #rotate to the specified goal position in specified direction
    goal = asPosition goal
    #send command if goal really is a different position   
    return goal if @position == (goal = goal.reduce)
    @position = nil
    begin
      reply = @dwarf.rotate @channel, goal.angle, direction, maxDuration    
    rescue =>err
      err.message[0,0]="Cannot rotate #{@name}:  "
      raise
    end
    raiseErr reply if reply.error
    endAngle = angleId reply.angle
    @position = (endAngle unless endAngle.offset)
    endAngle
  end

  private
  def raiseErr reply
    errPos = angleId reply.angle
    raise Error.new "#{name} #{reply.error} #{errPos}", self, reply
  end

end #Shaft class
