#####################  slide.rb -- brent@mbari.org  #######################
# $Source: /home/cvs/ESP/gen2/software/esp/lib/slide.rb,v $
#    Copyright (C) 2003 MBARI
#    MBARI Proprietary Information. All rights reserved.
# $Id: slide.rb,v 1.35 2005/10/25 19:57:59 brent Exp $
#
#     A Slide is a LinearAxis without a measurement function
#
############################################################################

require 'i2c/servo'
require 'axis'

class Slide < LinearAxis

  class Error < LinearAxis::Error
    def initialize text, axis, reply=nil
      @reply = reply
      super text, axis
    end
    attr_reader :reply    
  end


  def initialize(slideName, dwarf, slideNumber,
                 cfg=I2C::Servo::Configuration.default,
                 maxDuration=180, slop=cfg.maxPositionErr)
  #return a new slide object
  #dwarf is the network target that receives replies and sources requests
  #slideNumber is its servo channel (typically 0..1)
  #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
    @onExtraReply = proc {|this, extraReply|
      this.request.retire
      raiseErr extraReply, this.thread
    }
    @goHome=cfg.jogHome
    super
  end
  attr_reader :lastConfigSent
  attr_accessor :goHome

  def reconfigure newCfg=nil
  #send configuration only if it has changed
    @config=newCfg if newCfg
    @config.jogHome=@goHome
    configure unless @lastConfigSent.reallyEqual? @config
  end
  
  def home
    @goHome = true
    self
  end
    
  def rawGoal maxDuration=5
  #return the current raw goal of the servo
    begin
      @dwarf.goal(@channel, maxDuration).goal
    rescue =>err
      err.message[0,0]="Failed reading #{@name}'s goal:  "
      raise
    end
  end
    
  def rawPosition
  #return the current raw position
    stat=status
    raise Error.new "#{@name} Not yet Homed!", self, stat if stat.lost
    stat.position
  end
  
  def position slop=@slop
  #return the named position the slide is in
  #  if not in a named position, return a LinearAxis::Between object
    rawId rawPosition, slop
  end

  def goal slop=@slop
  #return current goal position
    rawId rawGoal, slop
  end
  
  def configure newCfg=nil
  #send configuration unconditionally
    @config=newCfg if newCfg
    Thread.log.recordMethod self, :configure, [@config]
    @lastConfigSent=@config.deepClone
    @lastConfigSent.jogHome=@goHome
    begin
      @dwarf.configure @channel, @lastConfigSent.freeze
    rescue =>err
      err.message[0,0]="Cannot configure #{@name}:  "
      raise
    end
  end
  
  def seek goal, maxDuration=@maxDuration
  #move to the specified goal position
    goal = asPosition goal
    reconfigure
    args=[goal]
    maxDuration=Delay.new maxDuration
    args<<maxDuration unless maxDuration==@maxDuration
    rawGoal = goal.raw
    if rawGoal > maxRaw or rawGoal < minRaw
      raise Error.new "#{goal} would be out of bounds!", self, goal
    end
    reply = nil
    3.times {
      Thread.log.recordMethod self, :seek, args
      begin
        reply = @dwarf.absMove @channel, rawGoal, maxDuration, &@onExtraReply
      rescue =>err
        err.message[0,0]="Cannot move #{@name}:  "
        raise
      end
      replyStatus = reply.status
      unless err=reply.error
        @goHome=false unless replyStatus and replyStatus.lost
        return rawId rawGoal
      end
      replyStatus = status unless replyStatus  #really shouldn't be necessary
      break if err!=:notReady or replyStatus.enabled or (
               replyStatus.lost and !@goHome)
      hold  #retry after enabling the servo...
    }
    raiseErr reply
  end

  def moveTo goal, tmpCfg=nil, maxDuration=@maxDuration
  #like seek, but allows for a servo configuration for just this move
    return seek goal, maxDuration if tmpCfg.nil? or tmpCfg.equal? @config
    oldConfig = @config
    begin
      @config = tmpCfg
      seek goal, maxDuration
    ensure
      @config = oldConfig
    end
  end
  alias_method :to, :moveTo
  
  #any undefined method is interpreted as a goal symbol!
  alias_method :method_missing, :moveTo


  def hold maxDuration=5
  #hold specified slide at its current position
    reconfigure
    Thread.log.recordMethod self, :hold, maxDuration==5 ? [] : [maxDuration]
    begin
      reply = @dwarf.holdPosition(@channel, maxDuration, &@onExtraReply)
      if reply.error  #kludge for PS failing to come up on reset
        Thread.log.recordException Error.
          new "Hold #{@name} failed, retrying...", self, reply
        getConfig  #just to generate a log entry
        configure
        reply = @dwarf.holdPosition(@channel, maxDuration, &@onExtraReply)
      end 
    rescue =>err
      err.message[0,0]="Cannot hold #{@name}'s position:  "
      raise
    end
    raiseErr reply if reply.error
    self
  end 

  def coast maxDuration=5
  #let specified slide coast (to stop) from its current position
    Thread.log.recordMethod self, :coast, maxDuration==5 ? [] : [maxDuration]
    begin
      reply = @dwarf.coast @channel
    rescue =>err
      err.message[0,0]="Cannot coast #{@name}:  "
      raise
    end
    self
  end
  alias_method :stop, :coast 

  private
  def raiseErr reply, thread=Thread.current
    thread.raise Error.new "#{@name} #{reply.error}", self, reply
  end

end #Slide class


