#------------------------------------------------------------------
# This is driver for the combination Phytron MCC-2 Motor Controller 
# and SICS using the scriptcontext asynchronous I/O system. The 
# MCC-2 has a funny protocol as that messages are enclosed into 
# <STX>  data <ETX> sequences. This protocol is handled by the 
# C-language phytron protocol handler. Per default, the MCC-2 is 
# configured to use 57600 baud. I have configured it to use 9600
# baud and it ought to remember this. The command to change this 
# 0IC1S9600, the command to read this is 0IC1R. 
#
# So, if this thing does not work on a serial port then the solution is 
# to set the terminal server to 57600 and try again. And set the baud rate
# or leave it.
#
# There are surely many ways to use the MCC-2. It supports two axes, X and Y. 
# All examples below are given for X only. This driver uses it in
# this way: 
# 
# Nothing works properly without a reference run. The reference run is done 
# in  the following way:
# 1) Send it into the negative limit switch with 0X0-   
# 2) Set the mechanical position with 0XP20Swert to the negative limit
# 3) Set the encoder position with 0XP22Swert to the negative limit
#
# Position ever afterwards with 0XAwert, read encoder with 0XP22R
#
# While driving 0X=H return ACKN, else ACKE
#
# Stopping goes via 0XSN
#
# copyright: see file COPYRIGHT
#
# Script chains:
#
# - reading position:
#    readpos - posrcv
#
# - writing postion:
#    setpos - setrcv
#
# - reading status:
#     sendstatus - rcvstatus - statpos
#
# - reading speed:
#     readspeed - rcvspeed
#
# - setting speed:
#     writespeed - rcvwspeed - rcvspeed
# 
# Mark Koennecke, June 2009
#
# Added code to switch a brake on for schneider_m2
#
# Mark Koennecke, September 2009
#
# Added code to support the speed parameter
# 
# Mark Koennecke, December 2009
#
# Added more code to configure non encoder phytron motors which need to 
# read another parameter for position
#
# Mark Koennecke, January 2011
#
# Added withencoder parameter to give the user the possiblilty to change
# encoder mode
#
# Markus Zolliker, April 2011
#
# Modified error handling: set status to "error" in case of a communication
# error and set the property of the main object accordingly
#
# Markus Zolliker, June 2012
#
# Set the motor position to the encoder position before staring the motor
# This is only active when wthencoder is 1
#    Markus Zolliker, August 2017
#
# Revised to allow for an address replacing the 0 in the example above. 
# The address is in the range from 0-F
#    Mark Koennecke, June 2018  
#-------------------------------------------------------------------------

namespace eval phytron {}

#-----------------------------------------------------------------------
proc phytron::check {} {
    global count
    set data [sct result]
    if {[string match "ASCERR: *" $data]} {
      set err [string range $data 8 end]
      catch {hupdate [sct parent]/status error}
      if {[string match "*unconnected*" $err] || [string match "*offline*" $err]} {
        set err [silent $err hgetpropval [sct parent] offline_msg]
      } elseif {[string match "Connection refused*" $err]} {
        set err [silent $err hgetpropval [sct parent] refused_msg]
      }
      hsetprop [sct parent] geterror $err
      error $err
    }
    catch {hdelprop [sct parent] geterror} 
    return $data
}
#------------------------------------------------------------------------
proc phytron::readpos {addr axis} {
# the following command must be P20R without encoder, P22R with encoder
    if {[hval [sct @withencoderpath]] == 1} {
	sct send "${addr}${axis}P22R"
    } else {
	sct send "${addr}${axis}P20R"
    }
    return posrcv
}
#------------------------------------------------------------------------
proc phytron::posrcv {} {
    set data [phytron::check]

    set pos [string range $data 3 end]
    sct update $pos
    return idle
}
#------------------------------------------------------------------------
proc phytron::setpos {addr axis name} {
    if {[hval [sct @withencoderpath]] == 1} {
        # set motor position to encoder position"
	sct send "${addr}${axis}P20S[hval [sct]]"
        return setpos_raw
    }
    return [phytron::setpos_raw $addr $axis $name]
}
#------------------------------------------------------------------------
proc phytron::setpos_raw {addr axis name} {
    set val [sct target]
    if {abs($val) < .001} {
	set val .001
    }
    sct send "${addr}${axis}A$val"
    hupdate /sics/${name}/status run
    return setrcv
}
#------------------------------------------------------------------------
proc phytron::setrcv {controller name} {
    set data [phytron::check]
    if {[string first NACK $data] >= 0} {
	error "Invalid command"
    }
    $controller queue /sics/${name}/status progress read
    return idle
}
#-------------------------------------------------------------------------
proc phytron::sendstatus {addr axis} {
    sct send "${addr}${axis}=H"
    return rcvstatus
}
#-------------------------------------------------------------------------
proc phytron::rcvstatus {addr axis controller} {
    set data [phytron::check]
    if {[string first ACKN $data] >= 0} {
	sct update run
	$controller queue [sct] progress read
    }
    if {[string first ACKE $data] >= 0} {
	phytron::readpos $addr $axis
	return posrcv
    }
    # update motor position during run
    $controller queue [sct parent]/hardposition progress read
    return idle
}
#-------------------------------------------------------------------------
proc phytron::statpos {addr axis name} {
    set data [phytron::check]
    set pos [string range $data 3 end]
    hupdate /sics/${name}/hardposition $pos
    sct send "${addr}${axis}=I+"
    return statposlim
}
#------------------------------------------------------------------------
proc phytron::statposlim {addr axis} {
    set data [phytron::check]
    if {[string first ACKE $data] >= 0} {
	sct update error
	clientput "Hit positive limit switch"
        return idle
    }
    sct send "${addr}${axis}=I-"
    return statneglim
}
#------------------------------------------------------------------------
proc phytron::statneglim {addr axis} {
    set data [phytron::check]
    if {[string first ACKE $data] >= 0} {
	sct update error
	clientput "Hit negative limit switch"
        return idle
    }
    sct send "${addr}${axis}=E"
    return statend
}
#------------------------------------------------------------------------
proc phytron::statend {addr axis} {
    set data [phytron::check]
    if {[string first ACKE $data] >= 0} {
	sct update error
	clientput "Electronics error"
        return idle
    }
    sct update idle
    return idle
}
#------------------------------------------------------------------------
proc phytron::readspeed {addr axis} {
    sct send "${addr}${axis}P14R"
    return rcvspeed
}
#------------------------------------------------------------------------
proc phytron::rcvspeed {} {
    set data [phytron::check]
    set speed [string range $data 3 end]
    sct update $speed
    return idle
}
#------------------------------------------------------------------------
proc phytron::writespeed {addr axis} {
    set val [sct target]
    sct send "${addr}${axis}P14S$val"
    return rcvwspeed
}
#------------------------------------------------------------------------
proc phytron::rcvwspeed {addr axis} {
    set data [phytron::check]
    if {[string first NACK $data] >= 0} {
	error "Invalid command"
    }
    return [phytron::readspeed $addr $axis]
}
#-------------------------------------------------------------------------
proc phytron::halt {controller addr axis} {
    $controller send "${addr}${axis}SN"
    return Done
}
#--------------------------------------------------------------------------
proc phytron::refrun {name controller addr axis lowlim} {
    set path /sics/${name}/status
    $controller send "${addr}${axis}0-"
    hupdate $path run
    set motstat run
    wait 3
    while {[string compare $motstat run] == 0} {
	$controller queue $path progress read
	wait 1
	set motstat [string trim [hval $path]]
    }
    $controller transact "${addr}${axis}P20S$lowlim"
    $controller transact "${addr}${axis}P22S$lowlim"
    return Done
}
#-------------------------------------------------------------------------
proc phytron::defpos {controller addr axis value} {
    $controller transact "${addr}${axis}P20S$value"
    $controller transact "${addr}${axis}P22S$value"
    return Done
}
#-------------------------------------------------------------------------
proc phytron::connect {controller name hostport} {
  $controller reconnect $hostport
# queue a read operation in order to update the error message
  $controller queue /sics/${name}/hardposition read read
}
#--------------------------------------------------------------------------
proc phytron::make {name axis controller lowlim upperlim {enc 1}} {
    return [phytron::makefull $name 0 $axis $controller $lowlim $upperlim $enc]
}
#---------------------------------------------------------------------------
proc phytron::makefull {name addr axis controller lowlim upperlim {enc 1}} {
    MakeSecMotor $name

    # make geterror work on the object
    $controller connect /sics/$name

    hdel /sics/${name}/hardupperlim
    hdel /sics/${name}/hardlowerlim
    hfactory /sics/${name}/hardupperlim plain internal float
    hfactory /sics/${name}/hardlowerlim plain internal float
    $name hardlowerlim $lowlim
    $name softlowerlim $lowlim
    $name hardupperlim $upperlim
    $name softupperlim $upperlim
 
    hsetprop /sics/${name}/hardposition read phytron::readpos $addr $axis
    hsetprop /sics/${name}/hardposition posrcv phytron::posrcv 
    $controller poll /sics/${name}/hardposition 10

    hsetprop /sics/${name}/hardposition write phytron::setpos $addr $axis $name
    hsetprop /sics/${name}/hardposition setpos_raw phytron::setpos_raw $addr $axis $name
    hsetprop /sics/${name}/hardposition setrcv phytron::setrcv $controller $name
    $controller write /sics/${name}/hardposition
    
    hsetprop /sics/${name}/status read phytron::sendstatus $addr $axis
    hsetprop /sics/${name}/status rcvstatus phytron::rcvstatus $addr $axis $controller
    hsetprop /sics/${name}/status posrcv  phytron::statpos $addr $axis $name
    hsetprop /sics/${name}/status statposlim  phytron::statposlim $addr $axis 
    hsetprop /sics/${name}/status statneglim  phytron::statneglim $addr $axis 
    hsetprop /sics/${name}/status statend  phytron::statend $addr $axis 
    $controller poll /sics/${name}/status 10

    hfactory /sics/${name}/speed plain user float
    hsetprop /sics/${name}/speed read "phytron::readspeed $addr $axis"
    hsetprop /sics/${name}/speed rcvspeed "phytron::rcvspeed"
    hsetprop /sics/${name}/speed write "phytron::writespeed $addr $axis"
    hsetprop /sics/${name}/speed rcvwspeed "phytron::rcvwspeed $addr $axis"
    $controller poll /sics/${name}/speed 10
    $controller write /sics/${name}/speed

    $name makescriptfunc halt "phytron::halt $controller $addr $axis" user

    $name makescriptfunc refrun "phytron::refrun $name $controller $addr $axis $lowlim" user

    $name makescriptfunc sethardpos "phytron::defpos $controller $addr $axis" user
    hfactory /sics/${name}/sethardpos/value plain user float

    $name makescriptfunc connect "phytron::connect $controller $name" user
    hfactory /sics/${name}/connect/hostport plain user text

    $name makescriptfunc disconnect "phytron::connect $controller $name unconnected" user

    hfactory /sics/${name}/withencoder plain user int
    hupdate /sics/${name}/withencoder $enc
    hsetprop /sics/${name} @withencoderpath /sics/${name}/withencoder

    hupdate /sics/${name}/status idle
    $controller queue /sics/${name}/hardposition progress read
    $controller queue /sics/${name}/speed  progress read
} 
#===============================================================================================
# At MORPHEUS there is a special table where one motor needs a brake. This requires a digital I/O 
# to be disabled before driving and enabled after driving. The code below adds this feature to
# a phytron motor. This shite has propagated to the CCD camera on BOA too. 
#-----------------------------------------------------------------------------------------------
proc phytron::openset {addr out} {
    sct send [format "${addr}A%dS" $out]
    return  openans
}
#----------------------------------------------------------------------------------------------
proc phytron::openans {addr axis name} {
    after 100
    return [phytron::setpos $addr $axis $name]
}
#----------------------------------------------------------------------------------------------
proc phytron::outsend {addr axis out} {
    set data [phytron::check]
    if {[string first ACKE $data] >= 0} {
	sct update error
	clientput "Electronics error"
        return idle
    }
    sct send [format "${addr}A%dR" $out]
    return outend
}
#----------------------------------------------------------------------------------------------
proc phytron::outend {} {
    sct update idle
    return idle
}
#----------------------------------------------------------------------------------------------
proc phytron::configureM2 {motor axis out {addr 0}} {
    set path /sics/${motor}
    hsetprop $path/hardposition write phytron::openset $addr $out
    hsetprop $path/hardposition openans phytron::openans $addr $axis $motor

    hsetprop $path/status statend  phytron::outsend $addr $axis $out 
    hsetprop $path/status outend   phytron::outend
}
