Posted to tcl by auriocus at Mon Jun 20 22:53:18 GMT 2016view pretty

set mydir [file dirname [info script]]
lappend auto_path $mydir
package require AsynCA
package require snit
package require ukaz

source [file join $mydir ddlparser.tcl]
 
DDLParser ddl [ddl_preprocess /home/cgollwit/Programmieren/Messprogramm3/ddl/kmc-include.ddl]

proc getPV {devname attrib} {
	string trim [ddl get device $devname attributes $attrib PV]
}


proc connectPV {devname attrib} {
	variable conntrigger
	set myPV [getPV $devname $attrib]
	if {$myPV eq {}} { return }
	
	lassign [AsynCA::connectwait $myPV] pv cmd
	return $cmd
}

snit::type motor {
	
	component gotopv
	component actpospv
	component stoppv
	component offpv 
	variable actpos
	variable movedone
	variable myname
	component movedonepv

	# store the moving state of all motors
	typevariable movingmotors {}

	typevariable allmotors {}

	typemethod movingmotors {} {
		dict keys [dict filter $movingmotors value 1]
	}

	typemethod waitforall {} {
		# wait until all motors are complete with moving
		while {[llength [$type movingmotors]] > 0} {
			vwait [mytypevar movingmotors]
		}
	}

	typemethod motorpositions {} {
		# read all motors and return dict
		set result {}
		dict for {m cmd} $allmotors {
			# lazy serial way. Could be sped up with AsynCA::readmultiple
			dict set result $m [$cmd read]
		}
		return $result
	}

	constructor {name} {
		install gotopv using connectPV $name gotoPos
		install actpospv using connectPV $name actPos
		install stoppv using connectPV $name stop
		install offpv using connectPV $name offset

		#install movedonepv using ddl get device $name attributes moveDone PV

		set myname $name

		if {$gotopv eq {}} {
			error "Don't know how to move >$name<"
		}
		
		if {$actpospv eq {}} {
			error "Don't know how to read the actPos of >$name<"
		}

		dict set allmotors $name $self

	}

	destructor {
		catch {$gotopv destroy}
		catch {$actpospv destroy}
		catch {$stoppv destroy}
	}	

	method putcallback {where cmd} {
		$gotopv put $where -command $cmd
	}

	method goto {where} {
		$gotopv put $where -command [mymethod moveready]
		dict set movingmotors $myname 1
	}
	
	method moveready {args} {
		dict set movingmotors $myname 0
	}

	method ismoving {} {
		dict get $movingmotors $myname
	}

	method read {} {
		$actpospv get -command [mymethod readback]
		vwait [myvar actpos]
		return $actpos
	}

	method readback {pos meta} {
		set actpos $pos
	}

	method wait {} {
		while {[$self ismoving]} {
			vwait [mytypevar movingmotors]
		}
	}

	method stop {} {
		if {$stoppv eq {}} {
			return -code error "Don't know how to stop"
		}
		$stoppv put 1
	}

	method define {pos1 {pos2 {}}} {
		# move the offset such that currenjt pos1 is equal to pos2
		if {$pos2 eq {}} {
			set pos2 $pos1
			set pos1 [$self read]
		}
		
		set curroff [AsynCA::read $offpv]
		set newoff [expr {$curroff+($pos2-$pos1)}]
		AsynCA::putwait $offpv $newoff

	}

	delegate method monitor to actpospv
}



snit::type detector {
	variable triggerpv
	variable scanmodepv 

	constructor {name} {
		set triggerpv [connectPV $name trigger]
		set scanmodepv [connectPV $name scan]
	}

	destructor {
		catch {$scanmodepv delete}
		catch {$triggerpv delete}
	}

	method trigger {args} {
		if {$triggerpv ne {}} {
			puts "Trigger device"
			$triggerpv put 1 {*}$args
			return 1
		} else {
			puts "Can't trigger"
			return 0
		}
	}

	method scanmode {mode args} {
		if {$scanmodepv ne {}} {
			$scanmodepv put $mode {*}$args
		}
	}
}

snit::type channel {
	variable value
	variable scanmodepv
	variable triggerpv
	component valuepv
	component mama

	typevariable detectors {}
	
	constructor {name} {
		set valuepv [connectPV $name value]
		if {$valuepv eq {}} { error "Don't know how to read >$name<" }
		
		set triggerpv [connectPV $name trigger]
		set scanmodepv [connectPV $name scan]
		
		set mamaname [ddl get device $name mama]
		puts "$name has $mamaname"

		if {$mamaname ne {}} {
			if {[dict exists $detectors $mamaname]} {
				install mama using dict get $detectors $mamaname
			} else {
				install mama using detector $mamaname $mamaname
				dict set detectors $mamaname $mama
			}
		}

	}

	destructor {
		catch {$triggerpv delete}
		catch {$scanmodepv delete}
		catch {$valuepv delete}
	}

	method trigger {args} {
		if {$triggerpv eq {}} {
			if {$mama eq {}} {
				puts "Trigger ignored (can't trigger)"
				return 0
			} else {
				puts "Trigger mama $mama"
				return [$mama trigger {*}$args]
			}
		} else {
			puts "Trigger me"
			$triggerpv put 1 {*}$args
			return 1
		}
	}

	method scanmode {mode args} {
		if {$scanmodepv eq {}} {
			if {$mama ne {}} {
				$mama scanmode $mode {*}$args
			} else {
				return -code error "Can't set scanmode"
			}
		} else {
			$scanmodepv put $mode {*}$args
		}
	}

	method getcallback {cmd} {
		$valuepv get -command $cmd
	}
	
	method read {} {
		$valuepv get -command [mymethod readback]
		vwait [myvar value]
		return $value
	}

	method readback {val meta} {
		set value $val
	}

	method getmama {} {
		return $mama
	}
	
	delegate method monitor to valuepv
	
	typevariable pending {}
	typevariable abort
	typevariable results
	typevariable resultvar
	

	typemethod readall {timeout args} {
		# trigger all detectors, wait for the result
		# should detect if the same mama is to be triggered
		set resultvar {}

		foreach c $args {
			# might be easier with coroutines ?
			if {![$c trigger -command [mytypemethod triggerready $c]]} {
				# if the channel can't be triggered, start an asynchronous get immediately
				$c getcallback [mytypemethod valueready $c]
			}
			dict set pending $c 1
			dict set resultvar $c {}
		}

		set abort false	
		set timeoutid [after $timeout [mytypemethod cancelreading]]

		while {([llength [$type pendingchannels]]>0) && !$abort} {
			vwait [mytypevar pending]
		}

		if {!$abort} { 
			after cancel $timeoutid
		}

		return [dict values $resultvar]
	}
	
	typemethod cancelreading {} {
		# signal the loop to break
		puts "Timeout fired"
		set pending $pending
		set abort true
	}

	typemethod triggerready {c val meta} {
		puts "$c updated, $pending"
		if {[dict get $pending $c]==1} {
			#puts "$c marked done"
			$c getcallback [mytypemethod valueready $c]
		}
	}

	typemethod valueready {c val meta} {
		puts "Got update $c $val"
		dict set pending $c 0
		dict set resultvar $c $val
	}

	typemethod pendingchannels {} {	
		dict keys [dict filter $pending value 1]
	}


}

# service routines for typical scanning procedures
proc setup_protocol {template} {
	variable ftemplate $template
	variable lastfnr 0
}

proc newprotofile {args} {
	set opts {Comment {} Motor {} Detector Value}
	set opts [dict merge $opts $args]

	variable protofd
	closeprotofile

	variable ftemplate
	variable lastfnr

	incr lastfnr
	
	while {true} {
		set fname [format $ftemplate $lastfnr]
		if {![file exists $fname]} { break }
		incr lastfnr
	}
	
	set protofd [open $fname w]
	fconfigure $protofd -buffering line

	dict with opts {
		if {$Comment ne {}} {
			puts $protofd "# Comment = $Comment"
		}
		if {$Motor ne {}} {
			puts $protofd "# Plot:"
			puts $protofd "#   Motor = $Motor"
			puts $protofd "#   Detector = $Detector"
		}
	}

	puts $protofd "# MotorPositions:"
	dict for {m v} [motor motorpositions] {
		puts $protofd "#   $m = $v"
	}

	return $protofd
}

proc closeprotofile {} {
	variable protofd
	if {[info exists protofd]} {
		catch {close $protofd}
		unset protofd
	}
}


proc read_signal {detspec} {
	set detectors [split $detspec /]
	set result [channel readall 5000 {*}$detectors]
	puts "$detectors, [llength $detectors]"
	switch [llength $detectors] {
		1 { return [lindex $result 0] }
		2 { 
			lassign $result nom denom
			if { $denom != 0.0 } {
				return [expr {$nom/$denom}]
			} else {
				return NaN
			}
		}
	}
	error "Wrong detspec"
}


set flcounter 0
proc flyscan {motor to detspec} {
	set top [toplevel .fl[incr ::flcounter]]
	set graph [ukaz::graph $top.g]
	pack $graph -expand yes -fill both
	$graph set xlabel $motor
	$graph set ylabel $detspec

	set data {}
	set plotid [$graph plot $data with linespoints]
	$motor goto $to
	while {[$motor ismoving]} {
		set value [read_signal $detspec]
		puts $value
		lappend data [$motor read] $value
		$graph update $plotid data $data
	}
}

proc xrangeN {a b steps} {
	set result [list $a]
	for {set i 1} {$i<$steps} {incr i} {
		lappend result [expr {$a+($b-$a)/double($steps-1.0)*$i}]
	}
	return $result
}


proc xrange {a b step} {
	if {$step == 0.0} {
		return -code error "Zero stepsize"
	}
	
	if {($a < $b)} {
		set step [expr {abs($step)}]
		set sign 1.0
	} else {
		set step [expr {-abs($step)}]
		set sign -1.0
	}

	set epsilon 1e-10
	
	set i 1
	set x $a
	set result {}
	while {$x*$sign < $b*$sign*(1+$epsilon) } {
		lappend result $x
		set x [expr {$a+$i*$step}]
		incr i
	}
	return $result
}

proc pause {ms} {
	after $ms {set _ 1}
	vwait ::_
}

proc stepscanN {motor detspec a b N args} {
	stepscan_list $motor $detspec [xrangeN $a $b $N] {*}$args
}

proc stepscan {motor detspec a b step} {
	stepscan_list $motor $detspec [xrange $a $b $step] {*}$args
}

proc stepscan_list {motor detspec list args} {
	# parse extra args
	set stdopt {-wait 0}
	set opts [dict merge $stdopt $args]
	
	if {[dict size $opts] != [dict size $stdopt]} {
		set wrongopt [dict filter $args script {k v} {expr ![dict exists $stdopt $k]}]
		return -code error "Wrong options $wrongopt"
	}

	set wtime [dict get $opts -wait]

	set top [toplevel .gr[incr ::grcounter]]
	set graph [ukaz::graph $top.g]
	pack $graph -expand yes -fill both
	$graph set xlabel $motor
	$graph set ylabel $detspec

	set data {}
	set plotid [$graph plot $data with linespoints]
	
	# skip first point
	$motor goto [lindex $list 0]
	$motor wait
	
	if {$wtime != 0} { pause $wtime }

	read_signal $detspec

	set fd [newprotofile Motor $motor Detector $detspec]
	puts $fd "# $motor $detspec Time"

	set t0 [clock microseconds]
	foreach pos $list {
		$motor goto $pos
		$motor wait
		if {$wtime != 0} { pause $wtime }
		
		set value [read_signal $detspec]
		set rbv [$motor read]
		set time [expr {([clock microseconds]-$t0)/1e6}]
		lappend data $rbv $value
		$graph update $plotid data $data
		puts $fd "$rbv $value $time"
	}
	
	closeprotofile
	return $data
}


proc FindCenter {fdata {thresh 0.5}} {

	# compute min/max position 
	set maxx 0; set maxy -Inf; set miny +Inf; set ind 0
	foreach {x y} $fdata {
		if {$y > $maxy} { 
			set maxy $y
			set maxx $x
			set maxi $ind
		}
		if {$y < $miny} {
			set miny $y
		}
		incr ind
	}
	
	set indmax $ind

	# go from max position to the left and right
	# until we hit the threshold
	set rightx {}
	set leftx {}
	set ythresh [expr {$miny*$thresh + $maxy*(1.0-$thresh)}]
	set xold $maxx; set yold $maxy

	for {set ind $maxi} {$ind < $indmax} {incr ind 1} {
		set xcur [lindex $fdata [expr {2*$ind}]]
		set ycur [lindex $fdata [expr {2*$ind+1}]]
		if {$ycur < $ythresh} {
			# interpolate for position 
			set rightx [expr {$xold + ($xcur-$xold)*double($ythresh-$yold)/double($ycur-$yold)}]
			break
		}
		set xold $xcur
		set yold $ycur
	}
	
	set emergencyx1 $xcur

	set xold $maxx; set yold $maxy
	for {set ind $maxi} {$ind >= 0} {incr ind -1} {
		set xcur [lindex $fdata [expr {2*$ind}]]
		set ycur [lindex $fdata [expr {2*$ind+1}]]
		if {$ycur < $ythresh} {
			# interpolate for position 
			set leftx [expr {$xold + ($xcur-$xold)*double($ythresh-$yold)/double($ycur-$yold)}]
			break
		}
		set xold $xcur
		set yold $ycur
	}
	
	set emergencyx2 $xcur

	set cx [expr {($emergencyx1+$emergencyx2)/2}]

	if {$leftx != {} && $rightx != {}} {
		set width [expr {$rightx-$leftx}]
		set cx [expr {($leftx+$rightx)/2}]
	} else {
		puts stderr "Warning: No center found - faking it"
	}

	puts "Center: $cx"
	return $cx

}

proc derive {fdata {sign 1.0}} {
	set fdata [lsort -real -stride 2 -unique $fdata]
	set result {}
	set rest [lassign $fdata oldx oldy]
	foreach {x y} $rest {
		set dx [expr {$oldx-$x}]
		set dy [expr {$oldy-$y}]
		lappend result [expr {($x+$oldx)/2.0}] [expr {(double($dy)/$dx)*$sign}]
		set oldx $x
		set oldy $y
	}
	
	return $result
}