= KAGRA VIS Operations Manual - vistools.py = `vistools.py` is a Python module and command-line utility for manipulating the suspensions. It lives in `/opt/rtcds/userapps/release/vis/k1/scripts` (although it may get moved to `/opt/rtcds/userapps/release/vis/common/scripts` and there is a symlink to it in `/opt/rtcds/userapps/release/vis/k1/guardian`. Nearby there may be a test version `vistoolstest.py`. == vistools.py usage modes == `vistools.py` has three modes of use: * [[#python|In a generic Python script.]] * [[#guardian|In Guardian.]] * [[#CLI|As a command line utility]]. <> === vistools.py as a Python module === `vistools.py` is primarily an ordinary Python module which provides the class `Vis`. Each instance of `Vis` represents a single suspension and can be created with any of the following forms: {{{ BS = vistools.Vis('BS') # an Ezca instance with prefix 'K1:' is created internally BS = vistools.Vis('VIS-BS') # channel name prefix style BS = vistools.Vis('VIS_BS') # Guardian file name style BS = vistools.Vis(('BS',ezca)) # ezca should be an existing ezca.Ezca instance with prefix 'K1:'; 'BS' can be 'VIS-BS' or 'VIS_BS' }}} A `Vis` object has a large number of methods which are mostly organized and named by blocks of filters of the same function (e.g., DAMP) at different levels of the suspension: {{{ BS.masterSwitchWrite('ON') # turns the master switch on BS.dampGainWrite(1.0) # sets all gain values in all DAMP blocks to 1.0. BS.dampGainWrite(1.0,levels=['IP']) # sets all gain values in the IP DAMP block to 1.0. BS.dampGainWrite(1.0,levels=['IP'],chans=['L','T']) # sets the gain values for the L and T channels of the IP DAMP block to 1.0 }}} Do `dir(vistools.Vis)` for a complete listing and `help(vistools.Vis.methodName)` for more details on individual methods. Not all methods are implemented for every block (feel free to add more based on existing patterns) but DAMP has a complete set which illustrate the naming scheme for filter block commands: * `dampPvs`: Return a list of PVs for DAMP blocks. * `dampInputSwitchWrite`: Write 'ON' or 'OFF' to the INPUT switch in DAMP blocks. * `dampInputSwitchRead`: Read the INPUT switch in DAMP blocks. * `dampOutputSwitchWrite`: Write 'ON' or 'OFF' to the OUTPUT switch in DAMP blocks. * `dampOutputSwitchRead`: Read the OUTPUT switch in DAMP blocks. * `dampOffsetSwitchWrite`: Write 'ON' or 'OFF' to the OFFSET switch in DAMP blocks. * `dampHoldSwitchWrite`: Write 'ON' or 'OFF' to the HOLD switch in DAMP blocks. * `dampOffsetWrite`: Write a value or list of values to the OFFSET field in DAMP blocks. * `dampGainRead`: Read the gain value in DAMP blocks. * `dampGainWrite`: Write a value or list of values to the GAIN field in DAMP blocks. * `dampFilterModuleEnableWrite`: Write 'ON' or 'OFF' to the filter switches in DAMP blocks. * `dampRampWrite`: Write a value or list of values to the RAMP field in DAMP blocks. * `dampGainRampingRead`: Read the gain ramping state (GRAMP) in DAMP blocks. * `dampOffsetRampingRead`: Read the offset ramping state (ORAMP) in DAMP blocks. * `dampRampingRead`: Read the overall ramping state (GRAMP or ORAMP) in DAMP blocks. * `dampPressButton`: Simulate a press of the CLEAR HISTORY ('CLEAR') or LOAD COEFFICIENTS ('LOAD') button in DAMP blocks. Similarly, OSEM2EUL has a complete set for matrix blocks: * `o2ePvs`: Return PV names for all or selected matrix elements. * `o2eDefs`: Return default values for all or selected matrix elements. * `o2eRead`: Read all or selected matrix elements. * `o2eWriteValue`: Write a common value into all or selected matrix elements. * `o2eWriteArray`: Write an array into all or selected matrix elements * `lmWriteDefaults`: Write default values for all or selected matrix elements <> === vistools.py from within Guardian === The usage from within Guardian is a bit complicated. According to some versions of the Guardian documentation, there is supposed to be a global `Ezca` instance called `ezca` which can be used for channel access. And according to some versions of the documentation, it is supposed to have prefix `K1:VIS_BS_` or the like (which gets prepended to channel name fragments passed to it). If you run the Guardian in interactive mode, e.g., {{{ guardian -i VIS_BS }}} then there is indeed a global ezca object, but it has prefix just `K1:`, so the appropriate setup is {{{ vis=vistools.Vis((SYSTEM,ezca)) }}} However the environment seen by an actual Guardian script is different again: the `Ezca` object is not global but only available within the `GuardState` classes that define states. Worse, creating a second one at global scope causes channel access errors. Therefore, the initialization of `vistools.py` has to be done inside a state definition. The solution used by the Type B Guardian extends the decorator `watchdog_check` to also check whether the `Vis` object has been initialized and do it if necessary: {{{ visObj = None def checkvis(): global visObj if visObj == None: visObj = vistools.Vis((SYSTEM,ezca)) class watchdog_check(GuardStateDecorator): """Decorator to check watchdog""" def pre_exec(self): global visObj checkvis() if visObj.trippedWds()!=[] or visObj.trippedBioWds()!=[]: return 'TRIPPED' # ... class SAFE(GuardState): index = 30 @watchdog_check def main(self): global visObj notify('In SAFE') }}} <> === vistools.py as a command-line utility === == vistools.py Internal Organization == `vistools.py` uses two large nested dictionary structures, `visTypes` and `visData` to define what groups of channels are available. A typical entry in `visTypes` specifies a particular suspension (e.g., BS) in terms of a generic type (e.g., 'TYPEB') plus watchdog and BIO information: {{{ visTypes = { ..., ('K1','BS') : {'type': 'TYPEB', 'watchdogs': typebwd, 'bio' : typebbio}, ... } }}} where {{{ typebwd = {'IOP':'DACKILL','IP':'IP_WDMON', 'F0':'F0_WDMON','F1':'F1_WDMON','BF':'BF_WDMON', 'IM':'IM_WDMON','TM':'TM_WDMON'} typebbio = {'GAS':'BIO_GAS_MON','IP':'BIO_IP_MON','GAS':'BIO_GAS_MON','IMV':'BIO_IMV_MON','TM':'BIO_TM_MON'} }}} Then, a typical entry in `visData` specifies a generic suspension type, which is further structured by level ('IP', 'F0', 'F1' etc) and optionally by sensor actuator group ('osemConfig', 'olConfig' etc). (The concept of a sensor-actuator group was more important historically when `vistools.py` was less smart about ignoring missing keys and every level had to have the same set of keys, even though many of them had value `None` - all the OSEM-related blocks could be declared as non-existent with a single entry `'osemConfig':None`.) {{{ visData = { 'master' : 'MASTERSWITCH', # a switch not associated with any particular level 'commissioning' : 'COMMISH_STATUS', # another global switch 'levelorder': ['IP','F0','F1','BF','SF','IM','TM'], # define a default level ordering 'levels' : { # define the various levels in the suspension ... 'IM':{ # define the intermediate mass level 'dofs' : ['L', 'T', 'V', 'R', 'P', 'Y'], # a list of related channels 'isichans' : ['X', 'Y', 'RZ', 'Z', 'RX', 'RY'], # another list of related channels ... 'cart2eul' : { # a cdsMuxMatrix block 'blockname':'CART2EUL', # the Simulink block name, used for constructing channel names 'inames':'isichans', # input channels; reference to the list defined above 'onames':'dofs', # output channels; reference to the list defined above 'default':[...] # default values for matrix elements }, 'osemConfig' : { # a sensor-actuator group 'chans' : ['V1', 'V2', 'V3', 'H1', 'H2', 'H3'], # a list of input channels 'dofs' : ['L', 'T', 'V', 'R', 'P', 'Y'], # a list of output channels 'inf' : {'blockname':'OSEMINF', 'names':'chans'}, # a block with a group of cdsFilt units 'eul2osem' : { # a cdsMuxMatrix block 'blockname':'EUL2OSEM','inames':'dofs', 'onames':'chans', 'default':[[...],[...]],...]) # each sublist represents an _output_! }, ... }, ... }, }, .... } }}} Most of the read/write commands are based on one of the following building block methods, which provide a standard set of utilities: * `switch([self,] pv, setting, enable, verbose=False, pair='value', withprefix='bare', matlab=False, dorw=2)` * `write([self,] pv, value, verbose=False, pair='none', withprefix='bare', matlab=False, dorw=2)` * `writelist([self,] pvs, values, verbose=False, pair='none', withprefix='bare', matlab=False, dorw=2)` * `read([self,] pv, verbose=False, pair='value', withprefix='bare', matlab=False, dorw=2)` * `readlist([self,] pvs, verbose=False, pair='value', withprefix='bare', matlab=False, dorw=2)` The typical arguments are as follows: * `pv` or `pvs`: a process variable (PV) or list of PVs to be read/written to/from. * `value`/`values`: a numeric value or list of values to be written (write methods only) * `setting`: a switch value ('ON' or 'OFF') to be written (switch methods only) * `levels`: a list of levels to restrict the request or change the default order, e.g, `['IP','F0','F1','BF']` * `chans`: a list of channels within a block to restrict the request or change the default order, e.g., `['L','T']` for IP DAMP. * `verbose`: if `True`, print debugging information, typically the channel names written to * `withprefix`: can be `'full'`, `'halffull'`, `'halfbare'` or `'bare'`; selects how much of the PV name to return (see next argument) * `pair`: can be `'pv'`, `'both'`, `'value'` or `'none'`; selects whether to return the channel name along with the read/written value * `matlab`: if `True`, returns lists with Matlab-style syntax (1/0 instead of True/False; {} instead of [] for lists) * `dorw`: 0 -> no live channel access; 1 -> no live write channel access (read only); 2 -> live reading and writing Then there is a higher level group of methods that work with all or selected PVs of a particular type: * `genNumWrite(self, pvfn, suffix, value, levels=[], chans=[], verbose=False, pair='none', withprefix='bare', matlab=False, dorw=2)` * `genNumRead(self, pvfn, suffix, levels=[], chans=[], verbose=False, pair='value', withprefix='bare', matlab=False, dorw=2)` * `genSwitchRead(self, pvfn, bits, levels=[], chans=[], verbose=False, pair='value', withprefix='bare', matlab=False, dorw=2)` * `genFilterModuleEnableRead(self, pvfn, filters=[], levels=[], chans=[], verbose=False, pair='none', withprefix='bare', matlab=False, dorw=2)` * `genFilterModuleEnableWrite(self, pvfn, enable, filters=[], levels=[], chans=[], verbose=False, pair='none', withprefix='bare', matlab=False, dorw=2)` * `genSwitchWrite(self, pvfn, suffix, enable, levels=[], chans=[], verbose=False, pair='none', withprefix='bare', matlab = False, dorw=2)` All of these take an argument `pvfn` which is a custom method for a particular block with signature `pvfn([self,] levels=levels,chans=chans)` or the like and returns a list of appropriate PVs for that block. == vistools.py Style Guide == `vistools.py` should have groups of 4 spaces for indentation. To select this in `gedit`, select Automatic Indentation off, Tab Width 4, Use Spaces on in the Tab Width menu in the lower window frame:<
> [[attachment:Gedit Setup.png|{{attachment:Gedit Setup.png||width=300}}]]