EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024 
<== Date ==> <== Thread ==>

Subject: Re: [EXTERNAL] record logic tips
From: "Pearson, Matthew R. via Tech-talk" <[email protected]>
To: "Davis, Mark" <[email protected]>
Cc: "[email protected]" <[email protected]>
Date: Mon, 21 Oct 2019 20:15:04 +0000
Hi Mark,

I think I have something that will work for you. This is my most recent version of a 16 position ‘positioner’ menu:

There are two database templates. The first one includes the second one multiple times, once for each position:

############################################################################

# Positioner template for 16 positions.
# This is used for the sample stages and typically will be populated with pre-defined 
# sets of positions. 
#
# It makes use of the common positioner records in positioner_position.template.
#
# Macros:
# S - base PV name (eg. CG3:Mot:Sample)
# MOT - real motor PV (eg. CG3:Mot:scx)
# P0 to P15 - the 16 positions (optional, default is -1000.0)
# D0 to D15 - the 16 descriptions (optional, default is '')
# PREC - display precision
# TOLERANCE - used to determine position
# EGU - mm or deg
# POS_ASG - the ASG level for the position and descriptions (default is DEFAULT)
# AUTOSAVE - set this to " " to enable autosave of the positions and descriptions

# ///
# /// Instantiate the position records (0-15)
# ///
substitute "POS=0"
substitute "NEXTPOS=1"
substitute "POSITION=$(P0=-1000.0)"
substitute "DESC=$(D0='')"
include "positioner_position.template"
substitute "POS=1"
substitute "NEXTPOS=2"
substitute "POSITION=$(P1=-1000.0)"
substitute "DESC=$(D1='')"
include "positioner_position.template"
substitute "POS=2"
substitute "NEXTPOS=3"
substitute "POSITION=$(P2=-1000.0)"
substitute "DESC=$(D2='')"
include "positioner_position.template"
substitute "POS=3"
substitute "NEXTPOS=4"
substitute "POSITION=$(P3=-1000.0)"
substitute "DESC=$(D3='')"
include "positioner_position.template"
substitute "POS=4"
substitute "NEXTPOS=5"
substitute "POSITION=$(P4=-1000.0)"
substitute "DESC=$(D4='')"
include "positioner_position.template"
substitute "POS=5"
substitute "NEXTPOS=6"
substitute "POSITION=$(P5=-1000.0)"
substitute "DESC=$(D5='')"
include "positioner_position.template"
substitute "POS=6"
substitute "NEXTPOS=7"
substitute "POSITION=$(P6=-1000.0)"
substitute "DESC=$(D6='')"
include "positioner_position.template"
substitute "POS=7"
substitute "NEXTPOS=8"
substitute "POSITION=$(P7=-1000.0)"
substitute "DESC=$(D7='')"
include "positioner_position.template"
substitute "POS=8"
substitute "NEXTPOS=9"
substitute "POSITION=$(P8=-1000.0)"
substitute "DESC=$(D8='')"
include "positioner_position.template"
substitute "POS=9"
substitute "NEXTPOS=10"
substitute "POSITION=$(P9=-1000.0)"
substitute "DESC=$(D9='')"
include "positioner_position.template"
substitute "POS=10"
substitute "NEXTPOS=11"
substitute "POSITION=$(P10=-1000.0)"
substitute "DESC=$(D10='')"
include "positioner_position.template"
substitute "POS=11"
substitute "NEXTPOS=12"
substitute "POSITION=$(P11=-1000.0)"
substitute "DESC=$(D11='')"
include "positioner_position.template"
substitute "POS=12"
substitute "NEXTPOS=13"
substitute "POSITION=$(P12=-1000.0)"
substitute "DESC=$(D12='')"
include "positioner_position.template"
substitute "POS=13"
substitute "NEXTPOS=14"
substitute "POSITION=$(P13=-1000.0)"
substitute "DESC=$(D13='')"
include "positioner_position.template"
substitute "POS=14"
substitute "NEXTPOS=15"
substitute "POSITION=$(P14=-1000.0)"
substitute "DESC=$(D14='')"
include "positioner_position.template"
substitute "POS=15"
substitute "NEXTPOS=16"
substitute "POSITION=$(P15=-1000.0)"
substitute "DESC=$(D15='')"
include "positioner_position.template"

# ///
# /// Record to check which position the motor is at.
# /// -1 will mean 'Undefined'
# ///
record(longin, "$(S):Pos") {
  field(LOW, "-1")
  field(LSV, "MINOR")
  info(archive, "Monitor, 00:00:01, VAL")
}

# ///
# /// Record to hold the description of the current position 
# ///
record(stringin, "$(S):Desc") {
  field(PINI, "YES")
  field(VAL, " ")
}

# ///
# /// Description for this current positioner set. This may be 
# /// manually set or set when populating the positioner from a 
# /// table_position template.
# ///
record(stringout, "$(S):Description") {
  field(PINI, "YES")
  field(VAL, " ")
  info(autosaveFields, "VAL")
}

# ///
# /// Calculate which position (and the corresponding description) we are at (if any)
# ///
record(bo, "$(S):Recalculate") {
  field(FLNK, "$(S):TriggerCalcs")
}
record(bi, "$(S):TriggerCalcs") {
  field(INP, "$(MOT).RBV CP MS")
  field(PINI, "YES")
  field(FLNK, "$(S):SetTempPos")
}
record(longout, "$(S):SetTempPos") {
  field(VAL, "-1")
  field(OUT, "$(S):TempPos PP")
  field(FLNK, "$(S):SetTempDesc")
}
record(stringout, "$(S):SetTempDesc") {
  field(VAL, " ")
  field(OUT, "$(S):TempDesc PP")
  field(FLNK, "$(S):Pos0Calc")
}
record(longin, "$(S):TempPos") {
  field(VAL, "-1")
}
record(stringin, "$(S):TempDesc") {
  field(VAL, " ")
}
record(calcout, "$(S):Pos15Calc") {
  field(DESC, "Trigger WritePos")
  field(FLNK, "$(S):WritePos")  
}
record(dfanout, "$(S):WritePos") {
  field(DOL, "$(S):TempPos MS")
  field(OMSL, "closed_loop")
  field(OUTA, "$(S):Pos.VAL PP")
  field(FLNK, "$(S):WriteDesc")
}
record(stringout, "$(S):WriteDesc") {
  field(OMSL, "closed_loop")
  field(DOL, "$(S):TempDesc MS")
  field(OUT, "$(S):Desc.VAL PP")
}

# ///
# /// Select among preset positions and move $(MOT) there
# /// 
record(mbbo, "$(S):Menu") {
  field(ZRVL, "0")
  field(ONVL, "1")
  field(TWVL, "2")
  field(THVL, "3")
  field(FRVL, "4")
  field(FVVL, "5")
  field(SXVL, "6")
  field(SVVL, "7")
  field(EIVL, "8")
  field(NIVL, "9")
  field(TEVL, "10")
  field(ELVL, "11")
  field(TVVL, "12")
  field(TTVL, "13")
  field(FTVL, "14")
  field(FFVL, "15")
  field(ZRST, "Out")
  field(ONST, "Pos 1")
  field(TWST, "Pos 2")
  field(THST, "Pos 3")
  field(FRST, "Pos 4")
  field(FVST, "Pos 5")
  field(SXST, "Pos 6")
  field(SVST, "Pos 7")
  field(EIST, "Pos 8")
  field(NIST, "Pos 9")
  field(TEST, "Pos 10")
  field(ELST, "Pos 11")
  field(TVST, "Pos 12")
  field(TTST, "Pos 13")
  field(FTST, "Pos 14")
  field(FFST, "Pos 15")
  field(FLNK, "$(S):MenuSel")
  info(autosaveFields_pass0, "VAL")
  info(archive, "Monitor, 00:00:01, VAL")
}
record(calcout, "$(S):MenuSel") {
  field(INPA, "$(S):Menu NPP MS")
  field(CALC, "(A>=0)&&(A<=9)?A+1:0")
  field(DOPT, "Use CALC")
  field(OOPT, "When Non-zero")
  field(OUT, "$(S):PosGoList.SELN PP")
  field(FLNK, "$(S):MenuSel2")
}
record(calcout, "$(S):MenuSel2") {
  field(INPA, "$(S):Menu NPP MS")
  field(CALC, "(A>=10)&&(A<=15)?(A-9):0")
  field(DOPT, "Use CALC")
  field(OOPT, "When Non-zero")
  field(OUT, "$(S):PosGoList2.SELN PP")
}
record(sseq, "$(S):PosGoList") {
  field(SELM, "Specified")
  field(WAIT1, "Wait")
  field(WAIT2, "Wait")
  field(WAIT3, "Wait")
  field(WAIT4, "Wait")
  field(WAIT5, "Wait")
  field(WAIT6, "Wait")
  field(WAIT7, "Wait")
  field(WAIT8, "Wait")
  field(WAIT9, "Wait")
  field(WAITA, "Wait")
  field(DOL1, "1")
  field(DOL2, "1")
  field(DOL3, "1")
  field(DOL4, "1")
  field(DOL5, "1")
  field(DOL6, "1")
  field(DOL7, "1")
  field(DOL8, "1")
  field(DOL9, "1")
  field(DOLA, "1")
  field(LNK1, "$(S):Pos0Go.PROC CA")
  field(LNK2, "$(S):Pos1Go.PROC CA")
  field(LNK3, "$(S):Pos2Go.PROC CA")
  field(LNK4, "$(S):Pos3Go.PROC CA")
  field(LNK5, "$(S):Pos4Go.PROC CA")
  field(LNK6, "$(S):Pos5Go.PROC CA")
  field(LNK7, "$(S):Pos6Go.PROC CA")
  field(LNK8, "$(S):Pos7Go.PROC CA")
  field(LNK9, "$(S):Pos8Go.PROC CA")
  field(LNKA, "$(S):Pos9Go.PROC CA")
  field(FLNK, "$(S):Recalculate")
}
record(sseq, "$(S):PosGoList2") {
  field(SELM, "Specified")
  field(WAIT1, "Wait")
  field(WAIT2, "Wait")
  field(WAIT3, "Wait")
  field(WAIT4, "Wait")
  field(WAIT5, "Wait")
  field(WAIT6, "Wait")
  field(DOL1, "1")
  field(DOL2, "1")
  field(DOL3, "1")
  field(DOL4, "1")
  field(DOL5, "1")
  field(DOL6, "1")
  field(LNK1, "$(S):Pos10Go.PROC CA")
  field(LNK2, "$(S):Pos11Go.PROC CA")
  field(LNK3, "$(S):Pos12Go.PROC CA")
  field(LNK4, "$(S):Pos13Go.PROC CA")
  field(LNK5, "$(S):Pos14Go.PROC CA")
  field(LNK6, "$(S):Pos15Go.PROC CA")
  field(FLNK, "$(S):Recalculate")
}


and the second template is:


# This template is meant to be instantiated in a higher level template
# that builds a multiple position table. 
# It simply has records that repeat for each position in the table.
# We have to override the FLNK in the last PosCalc record in the higher level template.

# ///
# /// Table position record and description
# ///
record(ao, "$(S):Pos$(POS)") {
  field(DESC, "Pos for p$(POS)")
  field(VAL, "$(POSITION=-1000)")
  field(PREC, "$(PREC)")
  field(PINI, "YES")
  field(EGU, "$(EGU)")
  field(ASG, "$(POS_ASG=)")
$(AUTOSAVE=#) info(autosaveFields, "VAL")
  info(archive, "Monitor, 00:00:01, VAL")
}
record(stringout, "$(S):Desc$(POS)") {
  field(DESC, "Desc for p$(POS)")
  field(VAL, "$(DESC= )")
  field(PINI, "YES")
  field(ASG, "$(POS_ASG=)")
$(AUTOSAVE=#) info(autosaveFields, "VAL")
}

# ///
# /// Record to provide an 'in-position' LED
# ///
record(bi, "$(S):InPos$(POS)") {
  field(VAL, "0")
  field(ZNAM, "Not In Position")
  field(ONAM, "In Position")
  info(archive, "Monitor, 00:00:01, VAL")
}

# ///
# /// Records to move motor to preset Positions
# ///
record(calcout, "$(S):Pos$(POS)Go") {
  field(INPA, "$(S):Pos$(POS).VAL NPP MS")
  field(CALC, "A")
  field(OUT, "$(MOT).VAL PP")  
}

# ///
# /// Set the in position flag and also write the current position
# /// number into TempPosition. Also copy the current description into
# /// TempDescription.
# ///
record(bo, "$(S):Pos$(POS)Set") {
  field(VAL, "1")
  field(OUT, "$(S):InPos$(POS) PP")
  field(FLNK, "$(S):InPos$(POS)SetTemp")
}
record(calcout, "$(S):InPos$(POS)SetTemp") {
  field(INPA, "$(S):Pos$(POS)Set.VAL")
  field(CALC, "A")
  field(DOPT, "Use OCAL")
  field(OOPT, "When Non-zero")
  field(OCAL, "$(POS)")
  field(OUT, "$(S):TempPos PP")
  field(FLNK, "$(S):InPos$(POS)SetTempDesc")
}
record(calcout, "$(S):InPos$(POS)SetTempDesc") {
  field(INPA, "$(S):Pos$(POS)Set.VAL")
  field(CALC, "A")
  field(DOPT, "Use CALC")
  field(OOPT, "When Non-zero")
  field(OUT, "$(S):InPos$(POS)WriteTempDesc.PROC PP")
}
record(stringout, "$(S):InPos$(POS)WriteTempDesc") {
  field(OMSL, "closed_loop")
  field(DOL, "$(S):Desc$(POS)")
  field(OUT, "$(S):TempDesc PP")
}

# ///
# /// This position calculation is done as part of the TriggerCalcs
# ///
record(calcout, "$(S):Pos$(POS)Calc") {
  field(INPA, "$(MOT).RBV NPP")
  field(INPB, "$(TOLERANCE)")
  field(INPC, "$(S):Pos$(POS)")
  field(DOPT, "Use CALC")
  field(OOPT, "Every Time")
  field(CALC, "((A>(C-B))&&(A<(C+B)))?1:0")
  field(OUT, "$(S):Pos$(POS)Set.VAL PP")
  field(FLNK, "$(S):Pos$(NEXTPOS)Calc")  
}



The second template is basically all the records common to each position. In the first template I just override the FLNK of the last instance, to provide an end point to the processing chain. 

To instantiate all this in a substitution file, it’s simply:

file positioner_16.template
{
pattern {S, MOT, PREC, TOLERANCE, EGU, AUTOSAVE, POS_ASG}
            {CG2:Mot:SamplePos, CG2:Mot:Sample:SCX, 3, 0.1, mm, " ", DEFAULT}
}

where CG2:Mot:SamplePos is the base part of the PV names for all the positioner table logic, and CG2:Mot:Sample:SCX is the motor record that will be controlled via the positioner logic. 

You have the choice to not use autosave, in which case you would list all the positions and descriptions in the substitutions file (or hard code in the first template file). Or you can use autosave and let the users change the positions and descriptions. This can easily be expanded to more than 16 positions, but then the first template needs to be modified so that the mbbo Menu record changes to a longout record, and the sseq records that select the position would need to be expanded. 

I’ve also attached a screenshot showing what it looks like (there’s a separate screen allowing edits to the positions and descriptions). 

On the screenshot, the ‘Current Position’ is a integer (not the motor position) that matches the numbers written to the ‘Move To’ menu, and the logic supports put_callback so it plays nice with clients that immediately check ‘Current Position’ after the end of the move. The ‘Current Description’ is a string record that shows whatever the description is for the current position. 

The ‘Current Position’ is updated whenever the motor moves, and at the end of the move. If someone manually edits one of the table positions, then the ‘Current Position’ can be recalculated by writing 1 to the ‘Recalculate’ record in the top level template. 

Cheers,
Matt



Data Acquisition and Controls Engineer
Spallation Neutron Source
Oak Ridge National Lab


On Oct 21, 2019, at 2:04 PM, Davis, Mark via Tech-talk <[email protected]> wrote:

Hi all,

I have a new task that I figured those of you with much more experience 
crafting clever record logic could help with:


The components that need to be supported:

     A record for some device to which setpoints stored in other 
records are to be written (e.g. a motor record).

     A string value that provides a description of the current readback 
of the device record.

     Pairs of numeric and string values.   For each pair, the number 
represents one of the setpoints an operator can chose to have written to 
the device record and the string represents a user-readable description 
of what the associated number represents (e.g. the thickness or type of 
material to place in the path of the beam, the size of a whole in a 
metal plate, etc).

     The operators can change the string (and possibly the numeric) 
values whenever they want.  Changes must be persistent (i.e. changes are 
saved to a file and restored when the IOC restarts, probably using 
autosave).

     Logic that monitors the current readback of the device record. 
Whenever the position is close to (within some specified deadband) of 
the numeric value in one of the numeric/string pairs, it copies the 
associated string to the one that describes the current readback.  When 
the readback is NOT close to one of the numeric values, it will write 
something like "Invalid position" to the description string.

     And of course it has to be relatively simple for the person 
configuring the IOC to change the # of numeric/string pairs supported 
for a device.


No doubt I can cobble something together that will do the job, but I 
figured someone out there will have already dealt with a similar need 
and can provide something much simpler, more flexible, and less 
cumbersome than what I am likely to create on my first attempt.

Any tips or suggestions would be much appreciated.

Thanks,
Mark Davis
NSCL/FRIB




References:
record logic tips Davis, Mark via Tech-talk

Navigate by Date:
Prev: Re: record logic tips Peterson, Kevin M. via Tech-talk
Next: RE: Streamdevice segmentation fault Brown, Garth via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024 
Navigate by Thread:
Prev: Re: record logic tips Mooney, Tim M. via Tech-talk
Next: RE: using epics-base with yocto Seeberger, John T CIV USN NAS PAX MD (USA) via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  <20192020  2021  2022  2023  2024 
ANJ, 22 Oct 2019 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·