This repository contains a few useful apps. All definitions are under the tabulator
namespace. All EPICS V7 operations are done using pvxs
.
Cloning
git clone --recursive https://github.com/brunoseivam/tabulator
Dependencies
libhdf5-dev
(Debian systems) or equivalent- EPICS @ 7.0.3.1 or later
- pvxs @ 344a96207f93583ff4804601c5563d3ec479cb2a or later
nttableApp
defines the NTTable normative type in terms of pvxs
. It also has some convenience methods. In a nutshell, an NTTable
object not only defines the NTTable type, but also part of its contents: the names of the columns, the content of the column labels and the types of each column. A pvxs::Value
obtained via NTTable::create
will come with a pre-populated labels
field.
commonApp
provides useful definitions for the entire project.
A convenient, header-only C++11 command line parser. Used in mergerApp
.
Convenience functions for timestamp manipulation and alignment.
Defines three classes that are used throughout the project.
-
TimeTable
: a wrapper aroundNTTable
that enforces that the first two columns are namedsecondsPastEpoch
andnanoseconds
, respectively, with equally named labels, both of which defined asuint32_t
. It also has convenience methods to obtain the specifications of the subsequent columns (name, label, type). -
TimeTableScalar
: inherits fromTimeTable
. It is intended to represent a sequence of readings from a scalar value, along with its metadata. It will enforce not only the same invariants asTimeTable
, but also that the subsequent columns are a combination of:
Name and Label | Type | Notes |
---|---|---|
value |
double |
Always present |
utag |
uint64_t |
(optional) Timestamp User Tag |
severity |
uint16_t |
(optional) Alarm severity |
condition |
uint16_t |
(optional) Alarm condition |
message |
string |
(optional) Alarm message |
TimeTableStat
: inherits fromTimeTable
. It is intended to represent a sequence of statistically compressed readings from a scalar value. It will enforce not only the same invariants asTimeTable
, but also that the subsequent columns are:
Name and Label | Type | Notes |
---|---|---|
VAL |
double |
Representative sample |
CNT |
double |
Number of compressed samples |
MIN |
double |
Minimum value in samples |
MAX |
double |
Maximum value in samples |
AVG |
double |
Mean value of samples |
RMS |
double |
Standard deviation of samples |
TimeTableValue
: a wrapper aroundpvxs::Value
, with convenience methods to get/set its columns. Its "type" will beTimeTable
orTimeTableScalar
.
This App contains a few useful simulators.
Simulates a number of scalar PVs via calc records. It provides databases to load 1, 16, 256, 1024 or 4096 individual instances. It simulates a sinusoid with amplitude 1.0 and by default the following alarm configuration:
LOLO
:-0.99
LOW
:-0.95
HIGH
:0.95
HIHI
:0.99
Simulates an NTTable with a configurable number of columns. It is meant to simulate aggregated readings from disparate but correlated PVs. The simulation happens in the device support of an asub
record. The database provides a few macros to configure it:
NUM_TABLES
: The number of output NTTable PVs to simulate. All rows in all tables will have the same value.NUM_SIGNALS
: The number of scalar PVs in each NTTable to simulate. All PVs will have the same value at the same timestamp, a sinusoidal equivalent to the one inioc-sim-scalar
(with the same alarm limits).CONFIG
: Which metadata columns to simulate. OR together:0x00
: Value (always included)0x01
: User Tag (always 0 if included)0x02
: Alarm severity0x04
: Alarm condition0x08
: Alarm message (always empty if included)
SCAN
: How often to produce updates.NUM_SAMPLES
: Ignored. See belowTIME_STEP_SEC
: The time difference between each simulated row, in seconds.NUM_ROWS
: The number of rows to produce at each update.LABEL_SEP
: The separator to use when building labels. Default:.
.COL_SEP
: The separator to use when building column names. Default:_
.
Example:
If the configuration is:
P=SIM:
,R=TABLE
,NUM_TABLES=1
,NUM_SIGNALS=2
,COLUMNS=0x02
,SCAN=1 second
,TIME_STEP_SEC=0.001
,NUM_ROWS=1000
,LABEL_SEP=.
,COL_SEP=_
Then every second a V7 NTTable named SIM:TABLE:0
with 1000 rows
will be produced. Its columns will be:
Column | Label | Type |
---|---|---|
secondsPastEpoch |
secondsPastEpoch |
uint32_t |
nanoseconds |
nanoseconds |
uint32_t |
pv0_value |
SIM:SIG:0.value |
double |
pv0_severity |
SIM:SIG:0.severity |
uint16_t |
pv1_value |
SIM:SIG:1.value |
double |
pv1_severity |
SIM:SIG:1.severity |
uint16_t |
The timestamps between rows will be 1 millisecond apart.
Simulates an NTTable with statistics samples. It is meant to simulate statistically compressed readings from disparate but correlated PVs. The simulation happens in the device support of an asub
record. The database provides a few macros to configure it:
NUM_TABLES
: The number of output NTTable PVs to simulate. All rows in all tables will have the same value.NUM_SIGNALS
: The number of "signals" to simulate. All "signals" will have the same value at the same timestamp, a compressed sinusoidal equivalent to the one inioc-sim-scalar
. Each row is equivalent to compressingNUM_SAMPLES
samples.CONFIG
: Indicates that this is a simulated statistics table. The only acceptable value is0x10
(or16
decimal).SCAN
: How often to produce updates.NUM_SAMPLES
: Number of compressed samples in each row.TIME_STEP_SEC
: The time difference between each simulated row, in seconds.NUM_ROWS
: The number of rows to produce at each update.
Example:
If the configuration is:
P=SIM:
,R=STAT
,NUM_TABLES=2
,NUM_SIGNALS=2
,COLUMNS=0x02
,SCAN=1 second
,TIME_STEP_SEC=0.001
,NUM_ROWS=1000
,NUM_SAMPLES=10
,LABEL_SEP=.
,COL_SEP=_
Then every second two V7 NTTable named SIM:STAT:0
and SIM:STAT:1
with 1000 rows
will be produced. Their columns will be:
Column | Label | Type |
---|---|---|
secondsPastEpoch |
secondsPastEpoch |
uint32_t |
nanoseconds |
nanoseconds |
uint32_t |
pv0_VAL |
SIM:SIG:0.VAL |
double |
pv0_CNT |
SIM:SIG:0.CNT |
uint32_t |
pv0_MIN |
SIM:SIG:0.MIN |
double |
pv0_MAX |
SIM:SIG:0.MAX |
double |
pv0_AVG |
SIM:SIG:0.AVG |
double |
pv0_RMS |
SIM:SIG:0.RMS |
double |
pv1_VAL |
SIM:SIG:1.VAL |
double |
pv1_CNT |
SIM:SIG:1.CNT |
uint32_t |
pv1_MIN |
SIM:SIG:1.MIN |
double |
pv1_MAX |
SIM:SIG:1.MAX |
double |
pv1_AVG |
SIM:SIG:1.AVG |
double |
pv1_RMS |
SIM:SIG:1.RMS |
double |
Column | Label | Type |
---|---|---|
secondsPastEpoch |
secondsPastEpoch |
uint32_t |
nanoseconds |
nanoseconds |
uint32_t |
pv0_VAL |
SIM:SIG:2.VAL |
double |
pv0_CNT |
SIM:SIG:2.CNT |
uint32_t |
pv0_MIN |
SIM:SIG:2.MIN |
double |
pv0_MAX |
SIM:SIG:2.MAX |
double |
pv0_AVG |
SIM:SIG:2.AVG |
double |
pv0_RMS |
SIM:SIG:2.RMS |
double |
pv1_VAL |
SIM:SIG:3.VAL |
double |
pv1_CNT |
SIM:SIG:3.CNT |
uint32_t |
pv1_MIN |
SIM:SIG:3.MIN |
double |
pv1_MAX |
SIM:SIG:3.MAX |
double |
pv1_AVG |
SIM:SIG:3.AVG |
double |
pv1_RMS |
SIM:SIG:3.RMS |
double |
The timestamps between rows will be 1 millisecond apart.
The stackerApp "stacks" readings from a single V3 scalar PV into an NTTable
. It does that as the device support of an asub
record. Its configuration parameters are:
INPP
,INPR
: combined, they define the input scalar PV that will be read:$(INPP)$(INPR)
.CONFIG
: which columns to include. OR together:0x00
: Timestamp + Value (always included)0x01
: User Tag (always 0 if included)0x02
: Alarm severity0x04
: Alarm condition0x08
: Alarm message (always empty if included)
PERIOD
: how long (in seconds) to accumulate before publishing a table.
ioc-stacker
contains an IOC that can load a database for stacking 1, 16, 256, 1024 or 4096 PVs. Mainly, this is used in conjunction with ioc-sim-scalar
so that we can "stack" up to thousands of individial scalars. This is not very performant. Its main use is as input to mergerApp, especially to test time-alignment since each stacked PV will have wildly different timestamps due to slow processing calc records from ioc-sim-scalar
.
A standalone executable (not an IOC) that merges together different input NTTables, time-aligned.
$ ./bin/linux-x86_64/merger
SYNOPSIS
./bin/linux-x86_64/merger --pvlist <pvlist> --period-sec <period_sec> [--timeout-sec
<timeout_sec>] --pvname <pvname> [--label-sep <label_sep>]
[--column-sep <col_sep>]
OPTIONS
--pvlist File with list of input NTTable PVs to be merged (newline-separated).
--period-sec
Update publication period, in seconds.
--timeout-sec
Time window to wait for laggards, in seconds. Default: 0 (wait forever).
--pvname Name of the output PV.
--label-sep Separator between PV name and column name in labels. Default: '.'.
--column-sep
Separator between PV identifier and original column name. Default: '_'.
BlueBrain/HighFive: C++ wrapper library for HDF5, included here as a submodule.
A standalone executable (not an IOC) that writes NTTables to HDF5 files.
$ ./bin/linux-x86_64/writer --help
SYNOPSIS
./bin/linux-x86_64/writer --input-pv <input_pv> --base-directory <base_directory>
--file-prefix <file_prefix> --root-group <root_group>
--timeout-sec <timeout_sec> [--max-duration-sec
<max_duration_sec>] [--max-size-mb <max_size_mb>] [--label-sep
<label_sep>] [--column-sep <col_sep>]
OPTIONS
--input-pv Name of the input PV
--base-directory
Path to the base directory for HDF5 files
--file-prefix
Prefix for generated HDF5 files
--root-group
Name of the HDF5 group at the root of the file structure
--timeout-sec
If no updates are received within timeout (in seconds), close the file and exit.
A value of 0 means wait forever
--max-duration-sec
Maximum time, in seconds, to collect data for in a single HDF5 file. If 0, don't
limit files by time. Default: 0
--max-size-mb
Maximum size, in MB, to collect data for in a single HDF5 file. If 0, don't
limit files by size. Default: 0
--label-sep separator between PV name and column name in labels. Default: '.'
--column-sep
separator between PV identifier and original column name. Default: '_'
This progam exits on any of these conditions:
- If output file already exists (error)
- After
timeout_sec
seconds since the last update - If
input_pv
disconnects
The input_pv
is assumed to conform to TimeTable
. The resulting HDF5 structure is as follows:
/meta/ Group
/meta/labels Dataset<string>: the list of labels: e.g. ["secondsPastEpoch", "nanoseconds", "SIM:STAT:000.MIN", "SIM:STAT:000.MAX", ...].
/meta/columns Dataset<string>: the list of columns: e.g. ["secondsPastEpoch", "nanoseconds", "pv000", "pv000_MIN", "pv000_MAX", ...].
/meta/pvxs_types Dataset<uint8_t>: the list of PVXS type codes for each column: e.g. [46, 46, 75, 75, ...].
/meta/pvnames Dataset<string>: the list of "signals": e.g. ["SIM:STAT:000", "SIM:STAT:001", ...]. Extracted from /meta/columns.
/meta/column_prefixes Dataset<string>: the list of column prefixes: e.g. ["pv000", "pv001", ...]. Extracted from /meta/labels.
Note: the input PV NTTable shape can be reconstructed from /meta/{labels,columns,pvxs_types}
/data/ Group
/data/secondsPastEpoch Dataset<T>: timestamp for each row
/data/nanoseconds Dataset<T>: timestamp for each row
/data/pv000/ Group
/data/pv000/MIN Dataset<T>: data for the "MIN" column for pv000
/data/pv000/... Dataset<T>: other data columns for pv000
/data/... Groups for other signals
...
Datasets are created with chunk size set to the number of rows of the first update.