This is a collection of scripts used at one time or another for the ICARUS connectivity test.
The scripts have been tested with python version 2.7.10 and 2.7.15.
The scripts typically use National Instrument VISA and CERN ROOT. Both should be available under python at the same time. This might require a compilation of CERN ROOT from scratch. In appendix, there is some report of success in installing the required software in a Linux distribution.
A small setup
script is provided, which allows to run the scripts in this repository from any directory.
A typical setup is:
source ~/root/bin/this_root.sh
source ./setup
To test that everything works as it should, make sure that this does not yield any error:
python -c 'import ROOT, visa'
A (local) network connection needs to be established between the node (say, laptop) and the oscilloscope. To test the communication, try:
import visa
manager = visa.ResourceManager()
scope = manager.open_resource('TCPIP0::192.168.230.29::INSTR')
print(scope.identify())
which in my case prints:
TEKTRONIX,TDS 3054C,0,CF:91.1CT FV:v4.05 TDS3FFT:v1.00 TDS3TRG:v1.00
(this means success!)
The first stage of the connectivity test happens before the regular ICARUS data acquisition system is in place. A test box injects square waves into the detector, reads the differentiated response back, and stores the waveforms representing that response. The fundamental scripts to interact with the oscilloscope to do that have been written by Sergi Castells.
The reading procedure is tedious enough, and ChimneyReader
is an attempt to make it easier by wrapping Sergi's scripts and streamlining a standard test pattern (start from the highest connection down, from the lower position up).
ChimneyReader
is a bookkeeping object that drives through the test.
The operator interacts with it by creating an instance of it and invoking its callables directly from the python interpreter shell.
From version 4, this object requires a configuration file (or more).
An example of the start of a data acquisition on chimney EE17
:
from testDriver import ChimneyReader # make `ChimneyReader` available
reader = ChimneyReader( # create a `ChimneyReader` object
[ # configured with two configuration files:
"config/TDS3054C-base.ini", # a basic setting one,
"config/TDS3054C-192.168.230.29.ini", # and one overriding the `scope IP address
],
fake=True # for this example, we don't connect to the `scope
)
reader.start("EE17") # declare we start a new chimney (can be done in constructor too)
reader.next() # take the first connection + position
reader.next() # take the second connection + position
reader.next() # take the third connection + position; let's assume we did a mistake...
reader.removeLast() # remove the last connection + position, prepare to take it again
reader.next() # take the third connection + position again
#...
This is the output of the start of the sequence above:
Python 2.7.15 (default, May 21 2018, 17:53:03) [GCC] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from testDriver import ChimneyReader # make `ChimneyReader` available
>>> reader = ChimneyReader( # create a `ChimneyReader` object
... [ # configured with two configuration files:
... "config/TDS3054C-base.ini", # a basic setting one,
... "config/TDS3054C-192.168.230.29.ini", # and one overriding the `scope IP address
... ],
... fake=True # for this example, we don't connect to the `scope
... )
INFO:root:Configuration file: 'config/TDS3054C-base.ini'
INFO:root:Configuration file: 'config/TDS3054C-192.168.230.29.ini'
>>> reader.start("EE17") # declare we start a new chimney (can be done in constructor too)
INFO:root:Output for this chimney will be written into: 'CHIMNEY_EE17_inprogress'
INFO:root:next(): Test pulse chimney EE17 connection V01 position 1
INFO:root:Hint:
* remove pulser and ribbon cables and switch the board to slot 1
* direct test box pulser output to the pulse input for V01
=> test all the different pulse inputs to find the best one
* plug the left signal ribbon into the test box
* turn to position 1
>>> reader.next() # take the first connection + position
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH1_CHIMNEY_EE17_CONN_V01_POS_1_1.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH2_CHIMNEY_EE17_CONN_V01_POS_1_1.csv'
[...]
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH3_CHIMNEY_EE17_CONN_V01_POS_1_10.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH4_CHIMNEY_EE17_CONN_V01_POS_1_10.csv'
Rendering: CH1.......... CH2.......... CH3.......... CH4.......... done.
INFO:root:next(): Test pulse chimney EE17 connection V01 position 2
INFO:root:Hint:
* just turn to position 2
True
>>> reader.next() # take the second connection + position
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH1_CHIMNEY_EE17_CONN_V01_POS_2_11.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH2_CHIMNEY_EE17_CONN_V01_POS_2_11.csv'
[...]
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH3_CHIMNEY_EE17_CONN_V01_POS_2_20.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH4_CHIMNEY_EE17_CONN_V01_POS_2_20.csv'
Rendering: CH1.......... CH2.......... CH3.......... CH4.......... done.
INFO:root:next(): Test pulse chimney EE17 connection V01 position 3
INFO:root:Hint:
* just turn to position 3
True
>>> reader.next() # take the third connection + position; let's assume we did a mistake...
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH1_CHIMNEY_EE17_CONN_V01_POS_3_21.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH2_CHIMNEY_EE17_CONN_V01_POS_3_21.csv'
[...]
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH3_CHIMNEY_EE17_CONN_V01_POS_3_30.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH4_CHIMNEY_EE17_CONN_V01_POS_3_30.csv'
Rendering: CH1.......... CH2.......... CH3.......... CH4.......... done.
INFO:root:next(): Test pulse chimney EE17 connection V01 position 4
INFO:root:Hint:
* just turn to position 4
True
>>> reader.removeLast() # remove the last connection + position, prepare to take it again
Remove 40 files from Test PULSE chimney EE17 connection V01 position 3? [Y/N] y
INFO:root:next(): Test pulse chimney EE17 connection V01 position 3
INFO:root:Hint:
* just turn to position 3
True
>>> reader.next() # take the third connection + position again
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH1_CHIMNEY_EE17_CONN_V01_POS_3_21.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH2_CHIMNEY_EE17_CONN_V01_POS_3_21.csv'
[...]
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH3_CHIMNEY_EE17_CONN_V01_POS_3_30.csv'
INFO:root:Written 10000 points into 'CHIMNEY_EE17_inprogress/PULSEwaveform_CH4_CHIMNEY_EE17_CONN_V01_POS_3_30.csv'
Rendering: CH1.......... CH2.......... CH3.......... CH4.......... done.
INFO:root:next(): Test pulse chimney EE17 connection V01 position 4
INFO:root:Hint:
* just turn to position 4
True
Note that both start()
and next()
print information about which connection and position will be tested the next time next()
is invoked.
By default, hints on the actions to perform next are also printed on screen.
Some ChimneyReader
useful callables:
start()
: start the data taking of a new chimneynext()
: data acquisition for the next connection/position in the sequenceprintNext()
: prints which connection/position the next call tonext()
is going to processremoveLast()
: removes the last acquired connection/position, and sets up to aquire it again withnext()
skipToNext()
,skipToPrev()
: prepare the next position, on the previous one, to be acquired withnext()
jumpTo(cable, position, test)
: jump directly to the specified cable, position and testlastList()
: list (like in "returns a python list") of all data files expected to have been created in the last data acquisitionplotLast()
: produces plots of the last connection/position (it's also automatically done bynext()
)verify()
: verifies the acquired datagenerateArchivalScript()
: generates the script to archive the acquired data
Once all the channels of a chimney have been recorded, the data needs to be transferred to a publicly available area. The following two steps are strongly recommended:
- verify that all the files are effectively there, and that there is no corruption in the data
- archive the data
The ChimneyReader
object provides a function verify()
to perform the
verification at a good degree, up to verifying that each file is legible and
with the right format and amount of information.
The object does not provide a service to archive the files, mostly because
that is a job that should happen asynchronously. It does provide a script that
can be run to perform the archival, though. The standard procedure is to
generate the script, and then run it on a different shell. Do this only after
verification has succeeded!
reader.verify()
reader.generateArchivalScript(user='username')
The first step, verification, can take 5 minutes on a machine with a fast
disk. This should be less time than it is needed to wrap up and set up the next
chimney, so that should be fine. But if the verification takes too long, it can
be simplified by decreasing its thoroughness, that is literally by running
reader.verify(thoroughness=3)
(which does not try to see if it is numbers that
are stored into the CSV files) or reader.verify(thoroughness=2)
(which does
not even check that the files have the expected number of points, and that is
fast beyond any excuse).
If the verification succeeds, verify()
will rename the output directory
marking it as not "in progress" any more, and making the CSV files read-only.
It will also create a small text file with some metadata of the acquisition.
It is on the renamed directory that generateArchivalScript()
works. The script
always attempts to archive all the files that it knows should be
there, and if some of them are not there, the ones present will be archived,
the others will produce error messages, and the script will exit with a non-zero
status. Rerunning the script will not copy again the files that were already
successfully archived.
The optional argument user
allows a default user different from the one
specified in the configuration file. This "user" is used for authentication into
the remote host where the archive is stored.
The following command line can be used as first entry in a python shell to
initialise the data taking for the chimney specified in resume()
(in the
example, EE00
, which is not a real chimney):
test type | oscilloscope | command |
---|---|---|
bias only | scope1 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeWorkbenchTestHV_scope1.ini"); reader.resume("EE00") |
wires only | scope1 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeChimneyFastTest_scope1.ini"); reader.resume("EE00") |
horizontal wires | scope1 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeHorWireTest_scope1.ini"); reader.resume("EE00") |
bias and wires | scope1 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeChimneyTest_scope1.ini"); reader.resume("EE00") |
bias only | scope2 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeWorkbenchTestHV_scope2.ini"); reader.resume("EE00") |
wires only | scope2 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeChimneyFastTest_scope2.ini"); reader.resume("EE00") |
horizontal wires | scope2 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeHorWireTest_scope2.ini"); reader.resume("EE00") |
bias and wires | scope2 | from testDriver import ChimneyReader ; reader = ChimneyReader("config/FlangeChimneyTest_scope2.ini"); reader.resume("EE00") |
Note that resume()
can also in place of start()
(if no data has been taken
yet, the two are equivalent).
None known so far.
The installation of the required interface to the oscilloscope includes two components:
- National Instruments drivers ("VISA")
- python interface to the oscilloscope (via the drivers)
Instructions on installing National Instrument VISA drivers in Linux OS are at http://www.ni.com/product-documentation/54754/en. They work for selected Linux distributions, all RPM-based. The idea is that you download the location of additional software repositories from National Instruments, and install from them with the native package manage of your Linux distribution.
Effectively what I did for OpenSUSE was:
- download the "driver" package (from http://www.ni.com/download/ni-linux-device-drivers-2018/7664/en); on early November 2018, I ended up downloading drivers from October 2018
- unzip the file, and ask my distribution to install the proper RPM (
sudo zypper install ./rpm_OpenSUSE423.rpm
) - open openSUSE package manager interface (from YaST2); a new National Instrument repository is listed and enabled already
- selected the NI VISA runtime, development package and documentation (probably selecting the metapackage
ni-visa
will do, and overdo as well); a number of packages lacked checksum verification (meaning the reference checksum was not found): I chose to ignore the problem - 200 MB later, we are set to go.
More-or-less useful links:
- NIVISA: introduction to what NI-VISA is and is for
Documentation on the Python interface can be found on [PyVISA][GitHub].
The recommended approach is to install the interface via pip
: pip install pyvisa
.
In my distribution (openSUSE), pip
is in fact the Python 3 version of it, and I had to make sure I was using pip2
instead:
pip2 install --user pyvisa
I also requested the package to be installed in my user area rather than system-wide. If installing on a system you manage with multiple users, then you might want to install it system-wide instead (sudo pip2 install pyvisa
).
It is not clear to me if the oscilloscope needs to be listed in /etc/ni-visa/visaconf.ini
.
I ended up adding it via visaconf
executable, which required some LabView packages to be installed, which in openSUSE was a problem since they had no checksum.
But this is what it ended up adding to the configuration file:
[TCPIP-RSRCS]
SynchronizeAllSocket=0
SynchronizeAllVxi11=1
NumOfResources=1
Name0="TCPIP0::192.168.230.29::INSTR"
Enabled0=1
Static0=1
[ALIASES]
Alias0="'SLAC-borrowed-TDS3054C','TCPIP0::192.168.230.29::INSTR'"
NumAliases=1
where 192.168.230.29
is the address I assigned to the oscilloscope.
The installation of the required interface to the oscilloscope includes the same two components as in the Linux counterpart:
- National Instruments drivers ("VISA")
- python interface to the oscilloscope (via the drivers)
Here we try to use Python as shipped with OSX. This procedure was tested with OSX 10.13 ("High Sierra") in the installation provided by Fermilab.
It features Python 2.7.10 as python
.
The first step was to install ROOT from sources. This might not be necessary (maybe binary were also fine). I tried here ROOT 6.16.
I am not reporting here how to build and install ROOT from sources, but it's important to point out that in the process I needed to install CMake (I used binary distribution from cmake.org
) and OSX command line development tools, which provided, among other things, GIT.
To install the Python VISA interface (pyvisa
) I went with
easy_install --user pyvisa
Supporting its name, easy_install
did what was needed. Phew.
I also downloaded and installed NI-VISA, which contributes a kernel extension (version 18.0, 71 MB), and the NI-VISA Run-Time Engine from National Instruments for OSX (also version 18.0, ~8.5 MB). Without the kernel correctly loaded, an "Abort trap 6" message will conclude any PyVISA session.
It is also necessary to "register" the instrument(s) with the VISA configuration utility, which in OSX is interactive (no need to edit text files).