Recently, I chatted with one of the engineers I work with about the problem of supporting several beamlines/projects with a slightly different version of the software for a given device. While this might sound like an easy task, it becomes a burden when the number of devices and projects grow.
We discussed several strategies for accomplishing this at scale, but I would like to start with the simplest solution first to learn its pros and cons. I wondered if there is a possibility of splitting the record definition between two files. One file could serve as a blueprint for a record and the other as a defining/altering for a specific project.
Consider the following database file standard.db
record(ai, pv) {
field(DESC, "Description of PV")
}
After loading it in the st.cmd
dbLoadRecords("../../db/standard.db")
the pv
record is present as follows
epics> dbpr pv 5
ACKS: NO_ALARM ACKT: YES ADEL: 0 AFTC: 0
AFVL: 0 ALST: 0 AMSG: AOFF: 0
ASG : ASLO: 1 ASP : PTR 0x0
BKLNK: ELL 0 [0x0 .. 0x0] BKPT: 00
DESC: Description of PV DISA: 0 DISP: 0
DISS: NO_ALARM DISV: 1 DPVT: PTR 0x0
DSET: PTR 0x1006e1620 DTYP: Soft Channel EGU :
EGUF: 0 EGUL: 0 EOFF: 0 ESLO: 1
EVNT: FLNK: CONSTANT HHSV: NO_ALARM HIGH: 0
HIHI: 0 HOPR: 0 HSV : NO_ALARM HYST: 0
INIT: 1 INP : CONSTANT LALM: 0 LBRK: 0
LCNT: 0 LINR: NO CONVERSION LLSV: NO_ALARM LOLO: 0
LOPR: 0 LOW : 0 LSET: PTR 0x6000039a40c0
LSV : NO_ALARM MDEL: 0 MLIS: ELL 0 [0x0 .. 0x0]
MLOK: 60 83 2b 02 00 60 00 00 MLST: 0 NAME: pv
NAMSG: NSEV: NO_ALARM NSTA: NO_ALARM OLDSIMM: NO
ORAW: 0 PACT: 0 PBRK: PTR 0x0 PHAS: 0
PINI: NO PPN : PTR 0x0 PPNR: PTR 0x0 PREC: 0
PRIO: LOW PROC: 0 PUTF: 0
RDES: PTR 0x6000019a8240 ROFF: 0 RPRO: 0
RSET: PTR 0x1006e0148 RVAL: 0 SCAN: Passive
SDIS: CONSTANT SDLY: -1 SEVR: INVALID SIML: CONSTANT
SIMM: NO SIMPVT: PTR 0x0 SIMS: NO_ALARM SIOL: CONSTANT
SMOO: 0 SPVT: PTR 0x0 SSCN: 65535 STAT: UDF
SVAL: 0 TIME: <undefined> TPRO: 0 TSE : 0
TSEL: CONSTANT UDF : 1 UDFS: INVALID UTAG: 0
VAL : 0
epics>
What if we would change its fields for a specific project without changing it for the remaining ones? I’m assuming that the standard.db
is shared between a few projects.
Suppose the HIHI
and LOLO
fields must be changed to reflect the unique setup of one of the projects.
Let’s create another database file, call it project.db
, where we define our record again with the changes related only to the project
record(ai, pv){
field(HIHI, 100)
field(LOLO, 20)
}
And load it after the standard.db
dbLoadRecords("../../db/standard.db")
dbLoadRecords("../../db/project.db")
Now, our pv
record looks as follows
epics> dbpr pv 5
ACKS: NO_ALARM ACKT: YES ADEL: 0 AFTC: 0
AFVL: 0 ALST: 0 AMSG: AOFF: 0
ASG : ASLO: 1 ASP : PTR 0x0
BKLNK: ELL 0 [0x0 .. 0x0] BKPT: 00
DESC: Description of PV DISA: 0 DISP: 0
DISS: NO_ALARM DISV: 1 DPVT: PTR 0x0
DSET: PTR 0x101305620 DTYP: Soft Channel EGU :
EGUF: 0 EGUL: 0 EOFF: 0 ESLO: 1
EVNT: FLNK: CONSTANT HHSV: NO_ALARM HIGH: 0
HIHI: 100 HOPR: 0 HSV : NO_ALARM HYST: 0
INIT: 1 INP : CONSTANT LALM: 0 LBRK: 0
LCNT: 0 LINR: NO CONVERSION LLSV: NO_ALARM LOLO: 20
LOPR: 0 LOW : 0 LSET: PTR 0x600000cac180
LSV : NO_ALARM MDEL: 0 MLIS: ELL 0 [0x0 .. 0x0]
MLOK: f0 c3 7a 01 00 60 00 00 MLST: 0 NAME: pv
NAMSG: NSEV: NO_ALARM NSTA: NO_ALARM OLDSIMM: NO
ORAW: 0 PACT: 0 PBRK: PTR 0x0 PHAS: 0
PINI: NO PPN : PTR 0x0 PPNR: PTR 0x0 PREC: 0
PRIO: LOW PROC: 0 PUTF: 0
RDES: PTR 0x600002ca8240 ROFF: 0 RPRO: 0
RSET: PTR 0x101304148 RVAL: 0 SCAN: Passive
SDIS: CONSTANT SDLY: -1 SEVR: INVALID SIML: CONSTANT
SIMM: NO SIMPVT: PTR 0x0 SIMS: NO_ALARM SIOL: CONSTANT
SMOO: 0 SPVT: PTR 0x0 SSCN: 65535 STAT: UDF
SVAL: 0 TIME: <undefined> TPRO: 0 TSE : 0
TSEL: CONSTANT UDF : 1 UDFS: INVALID UTAG: 0
VAL : 0
epics>
HIHI
and LOLO
of record defined in standard.db
get overwritten with the values defined in project.db
But what if we don’t allow the redeclaration of the record and flag it if it happens?
There is a variable dbRecordsOnceOnly
. It can be set in the st.cmd
var dbRecordsOnceOnly 1
An attempt to start the IOC will produce
var dbRecordsOnceOnly 1
## Load record instances
dbLoadRecords("../../db/standard.db")
dbLoadRecords("../../db/project.db")
ERROR: Record "pv" already defined and dbRecordsOnceOnly set.
Used record type "*" to append.
ERROR at or before ')' in file "../../db/project.db" line 1
dbReadCOM: Parser stack dirty w/o error. 1
ERROR failed to load '../../db/project.db'
You may wonder what does the Used record type “*” to append.
mean.
Following the Release notes of epics base
Database field setting updates
A database (.db) file loaded by an IOC does not have to repeat the record type of a record that has already been loaded. It may replace the first parameter of the record(type, name) statement with an asterisk character inside double-quotes, "" instead. Thus the following is a legal database file:
record(ao, "ao1") {}
record("*", "ao1") {
field(VAL, 10)
}
If you are keen to handle the problem I mentioned in the first paragraph at the IOC level, this is one of the options to make it happen.