diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index c46ce43..8dba6e0 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -159,6 +159,7 @@ jobs: python: "3.7" container: "python:3.7" profile: deb10 + steps: - uses: actions/checkout@v3 with: diff --git a/devsupApp/src/dbrec.c b/devsupApp/src/dbrec.c index 7802fe5..e93589e 100644 --- a/devsupApp/src/dbrec.c +++ b/devsupApp/src/dbrec.c @@ -2,9 +2,7 @@ /* python has its own ideas about which version to support */ #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE - #include - #include #include #include @@ -125,20 +123,25 @@ static PyObject* pyRecord_setSevr(pyRecord *self, PyObject *args, PyObject *kws) { dbCommon *prec = self->entry.precnode->precord; - static char* names[] = {"sevr", "stat", NULL}; + static char* names[] = {"sevr", "stat", "amsg", NULL}; short sevr = INVALID_ALARM, stat=COMM_ALARM; + char* amsg = NULL; - if(!PyArg_ParseTupleAndKeywords(args, kws, "|hh", names, &sevr, &stat)) + if(!PyArg_ParseTupleAndKeywords(args, kws, "|hhz", names, &sevr, &stat, &amsg)) return NULL; if(sevrlastEpicsAlarmSev || statlastEpicsAlarmCond) { - PyErr_Format(PyExc_ValueError, "%s: Can't set alarms %d %d", prec->name, sevr, stat); + PyErr_Format(PyExc_ValueError, "%s: Can't set alarms %d %d %s", prec->name, sevr, stat, amsg); return NULL; } +#if EPICS_VERSION_INT {'name':'value'}\n" "Return a dictionary of all infos for this record."}, {"setSevr", (PyCFunction)pyRecord_setSevr, METH_VARARGS|METH_KEYWORDS, - "setSevr(sevr=INVALID_ALARM, stat=COMM_ALARM)\n" + "setSevr(sevr=INVALID_ALARM, stat=COMM_ALARM, amsg=None)\n" "Set alarm new alarm severity/status. Record must be locked!"}, {"setTime", (PyCFunction)pyRecord_setTime, METH_VARARGS, "Set record timestamp if TSE==-2. Record must be locked!"}, diff --git a/devsupApp/src/devsup/ptable.py b/devsupApp/src/devsup/ptable.py index 365cb91..0f5cf4b 100644 --- a/devsupApp/src/devsup/ptable.py +++ b/devsupApp/src/devsup/ptable.py @@ -10,7 +10,7 @@ _tables = {} from .db import IOScanListThread -from . import INVALID_ALARM, UDF_ALARM +from . import INVALID_ALARM, UDF_ALARM, NO_ALARM __all__ = [ 'Parameter', @@ -161,6 +161,8 @@ def __init__(self, table, name, scan): self.name = name self.table, self.scan, self._value = table, scan, None self.alarm, self.actions = 0, [] + self.stat = UDF_ALARM + self.amsg = None self._groups = set() def _get_value(self): return self._value @@ -221,6 +223,10 @@ def __init__(self, inst, rec, info): self.vdata = None if len(self.vfld)>1: self.vdata = self.vfld.getarray() + if rec.PINI == "YES": + # record processing will have updated the field. + sel.inst.stat = NO_ALARM + def detach(self, rec): pass def allowScan(self, rec): @@ -232,19 +238,19 @@ def process(self, rec, reason=None): """Read a value from the table into the record """ with self.inst.table.lock: - nval, alrm = self.inst.value, self.inst.alarm - self.inst.table.log.debug('%s -> %s (%s)', self.inst.name, rec.NAME, nval) + value, alarm, stat, amsg = self.inst.value, self.inst.alarm, self.inst.stat, self.inst.amsg + self.inst.table.log.debug('%s -> %s (%s)', self.inst.name, rec.NAME, value) - if nval is not None: + if value is not None: if self.vdata is None: - self.vfld.putval(nval) + self.vfld.putval(value) else: - if len(nval)>len(self.vdata): + if len(value)>len(self.vdata): nval = nval[:len(self.vdata)] - self.vdata[:len(nval)] = nval - self.vfld.putarraylen(len(nval)) - if alrm: - rec.setSevr(alrm) + self.vdata[:len(value)] = value + self.vfld.putarraylen(len(value)) + if alarm: + rec.setSevr(alarm, stat, amsg) else: # undefined value rec.setSevr(INVALID_ALARM, UDF_ALARM) @@ -271,8 +277,9 @@ def process(self, rec, reason=None): # Execute actions self.inst._exec(oval) - for G in self.inst._groups: - G._exec() + rec.setSevr(self.inst.alarm, self.inst.stat, self.inst.amsg) + for G in self.inst._groups: + G._exec() class TableBase(object): """Base class for all parameter tables. diff --git a/devsupApp/src/devsup/test/test_db.py b/devsupApp/src/devsup/test/test_db.py index 2dc33e0..9c9ddfa 100644 --- a/devsupApp/src/devsup/test/test_db.py +++ b/devsupApp/src/devsup/test/test_db.py @@ -163,3 +163,66 @@ def test_increment(self): with rec: self.assertEqual(rec.VAL, 1) self.assertEqual(rec.UDF, 0) + +class TestAlarm(IOCHelper): + db = """ + record(longin, "rec:inalarm:amsg") { + field(PINI, "YES") + } + record(longin, "rec:inalarm:plain") { + field(PINI, "YES") + } + record(longout, "rec:outalarm:amsg") { + field(PINI, "YES") + field(VAL, "0") + } + record(longout, "rec:outalarm:plain") { + field(PINI, "YES") + field(VAL, "0") + } + """ + def test_setin_severity_message(self): + rec = getRecord("rec:inalarm:amsg") + with rec: + self.assertEqual(rec.SEVR, _dbapi.NO_ALARM) + self.assertEqual(rec.STAT, _dbapi.NO_ALARM) + if _dbapi.epicsver[:4] >= (7, 0, 6, 0): + self.assertEqual(rec.AMSG, "") + self.assertEqual(rec.setSevr(_dbapi.MAJOR_ALARM, _dbapi.HIHI_ALARM, amsg="Meaningful input alarm message"), None) + self.assertEqual(rec.scan(sync=True), 0) + self.assertEqual(rec.STAT, _dbapi.HIHI_ALARM) + if _dbapi.epicsver[:4] >= (7, 0, 6, 0): + self.assertEqual(rec.AMSG, "Meaningful input alarm message") + + def test_setin_severity_without_message(self): + rec = getRecord("rec:inalarm:plain") + with rec: + self.assertEqual(rec.SEVR, _dbapi.NO_ALARM) + self.assertEqual(rec.STAT, _dbapi.NO_ALARM) + self.assertEqual(rec.setSevr(_dbapi.MAJOR_ALARM, _dbapi.COMM_ALARM), None) + self.assertEqual(rec.scan(sync=True), 0) + self.assertEqual(rec.SEVR, _dbapi.MAJOR_ALARM) + self.assertEqual(rec.STAT, _dbapi.COMM_ALARM) + + def test_setout_severity_message(self): + rec = getRecord("rec:outalarm:amsg") + with rec: + self.assertEqual(rec.SEVR, _dbapi.NO_ALARM) + self.assertEqual(rec.STAT, _dbapi.NO_ALARM) + if _dbapi.epicsver[:4] >= (7, 0, 6, 0): + self.assertEqual(rec.AMSG, "") + self.assertEqual(rec.setSevr(_dbapi.MAJOR_ALARM, _dbapi.HIHI_ALARM, amsg="Meaningful output alarm message"), None) + self.assertEqual(rec.scan(sync=True), 0) + self.assertEqual(rec.STAT, _dbapi.HIHI_ALARM) + if _dbapi.epicsver[:4] >= (7, 0, 6, 0): + self.assertEqual(rec.AMSG, "Meaningful output alarm message") + + def test_setout_severity_without_message(self): + rec = getRecord("rec:outalarm:plain") + with rec: + self.assertEqual(rec.SEVR, _dbapi.NO_ALARM) + self.assertEqual(rec.STAT, _dbapi.NO_ALARM) + self.assertEqual(rec.setSevr(_dbapi.MAJOR_ALARM, _dbapi.COMM_ALARM), None) + self.assertEqual(rec.scan(sync=True), 0) + self.assertEqual(rec.SEVR, _dbapi.MAJOR_ALARM) + self.assertEqual(rec.STAT, _dbapi.COMM_ALARM)