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 <2019> 2020 2021 2022 2023 2024 2025 | 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 <2019> 2020 2021 2022 2023 2024 2025 |
<== 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: |