From f70f1e671bb27d9928ff80400e7abfbd567b4a48 Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Thu, 30 Jul 2026 13:23:09 +0100 Subject: [PATCH 1/5] Copied from old fork. --- .github/workflows/ci-scripts-build.yml | 1 + devsupApp/src/dbrec.c | 15 +++--- devsupApp/src/devsup/ptable.py | 31 ++++++++----- devsupApp/src/devsup/test/test_db.py | 63 ++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 18 deletions(-) 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) From 1b2fec06da5aa3767c917fd14804a1cc84b426ec Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Wed, 12 Aug 2026 16:47:58 +0100 Subject: [PATCH 2/5] Accepted changes to ci-scripts-build.yml made by Windows support pull request. Accepted changes to dbrec.c made byhttps://github.com/epics-modules/pyDevSup/pull/48 --- .github/workflows/ci-scripts-build.yml | 33 ++++++++++++++++++++++++++ devsupApp/src/dbrec.c | 21 +++++++++------- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 8dba6e0..a2056aa 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -160,6 +160,38 @@ jobs: container: "python:3.7" profile: deb10 + - os: windows-latest + cmp: vs2026 + configuration: default + base: "3.15" + python: "3.11" + profile: latest + test: yes + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "7.0" + python: "3.11" + profile: latest + test: yes + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "7.0" + python: "3.12" + profile: latest + test: yes + + - os: windows-latest + cmp: vs2026 + configuration: default + base: "7.0" + python: "3.13" + profile: latest + test: yes + steps: - uses: actions/checkout@v3 with: @@ -170,6 +202,7 @@ jobs: with: python-version: ${{ matrix.python }} - name: More Setup Python + shell: bash run: | python --version python -m pip --version diff --git a/devsupApp/src/dbrec.c b/devsupApp/src/dbrec.c index e93589e..a35b544 100644 --- a/devsupApp/src/dbrec.c +++ b/devsupApp/src/dbrec.c @@ -2,7 +2,9 @@ /* python has its own ideas about which version to support */ #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE + #include + #include #include #include @@ -125,7 +127,7 @@ static PyObject* pyRecord_setSevr(pyRecord *self, PyObject *args, PyObject *kws) static char* names[] = {"sevr", "stat", "amsg", NULL}; short sevr = INVALID_ALARM, stat=COMM_ALARM; - char* amsg = NULL; + const char *amsg = NULL; if(!PyArg_ParseTupleAndKeywords(args, kws, "|hhz", names, &sevr, &stat, &amsg)) return NULL; @@ -133,15 +135,17 @@ static PyObject* pyRecord_setSevr(pyRecord *self, PyObject *args, PyObject *kws) if(sevrlastEpicsAlarmSev || statlastEpicsAlarmCond) { - PyErr_Format(PyExc_ValueError, "%s: Can't set alarms %d %d %s", prec->name, sevr, stat, amsg); + PyErr_Format(PyExc_ValueError, "%s: Can't set alarms %d %d", prec->name, sevr, stat); return NULL; } - -#if EPICS_VERSION_INT= 7.0.6."}, {"setTime", (PyCFunction)pyRecord_setTime, METH_VARARGS, "Set record timestamp if TSE==-2. Record must be locked!"}, {"scan", (PyCFunction)pyRecord_scan, METH_VARARGS|METH_KEYWORDS, From e4de9a86499b34bed4b2c1777b9782edb24cb70f Mon Sep 17 00:00:00 2001 From: pheest Date: Wed, 19 Aug 2026 20:16:35 +0100 Subject: [PATCH 3/5] Correct mistakes moted by tynanford. There does not appear to be a need for G._exec to be outside of the lock block. --- devsupApp/src/devsup/ptable.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devsupApp/src/devsup/ptable.py b/devsupApp/src/devsup/ptable.py index 0f5cf4b..09a88e5 100644 --- a/devsupApp/src/devsup/ptable.py +++ b/devsupApp/src/devsup/ptable.py @@ -225,7 +225,7 @@ def __init__(self, inst, rec, info): self.vdata = self.vfld.getarray() if rec.PINI == "YES": # record processing will have updated the field. - sel.inst.stat = NO_ALARM + self.inst.stat = NO_ALARM def detach(self, rec): pass @@ -246,7 +246,7 @@ def process(self, rec, reason=None): self.vfld.putval(value) else: if len(value)>len(self.vdata): - nval = nval[:len(self.vdata)] + value = nval[:len(self.vdata)] self.vdata[:len(value)] = value self.vfld.putarraylen(len(value)) if alarm: @@ -278,8 +278,8 @@ def process(self, rec, reason=None): # Execute actions self.inst._exec(oval) rec.setSevr(self.inst.alarm, self.inst.stat, self.inst.amsg) - for G in self.inst._groups: - G._exec() + for G in self.inst._groups: + G._exec() class TableBase(object): """Base class for all parameter tables. From 1f47d20cb29cb616d8ea2ad42e618821301f316f Mon Sep 17 00:00:00 2001 From: pheest Date: Thu, 20 Aug 2026 04:42:30 +0100 Subject: [PATCH 4/5] Fix more than one instance --- devsupApp/src/devsup/ptable.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/devsupApp/src/devsup/ptable.py b/devsupApp/src/devsup/ptable.py index 09a88e5..9c38968 100644 --- a/devsupApp/src/devsup/ptable.py +++ b/devsupApp/src/devsup/ptable.py @@ -132,7 +132,7 @@ def allvalid(self, fn): "Decorator to run an action when all parameters have valid values" return _add_action(self, (all, lambda p:p.isvalid), fn) def anynotvalid(self, fn): - "Decorator to run an action when any parameters has an invalid value" + "Decorator to run an action when any parameters has an iid value" return _add_action(self, (any, lambda p:not p.isvalid), fn) def oncondition(self, fmap, freduce=all): """Decorator for a custom condition. @@ -172,9 +172,9 @@ def _set_value(self, val): value = property(_get_value, _set_value, doc="The current parameter value") @property def isvalid(self): - """Is the parameter value valid (not None and no INVALID_ALARM) + """Is the parameter value valid (not None and no IID_ALARM) """ - return self.alarm < INVALID_ALARM and self._value is not None + return self.alarm < IID_ALARM and self._value is not None def notify(self): """Notify attached records of parameter value change. A no-op unless Parameter(iointr=True) @@ -246,7 +246,7 @@ def process(self, rec, reason=None): self.vfld.putval(value) else: if len(value)>len(self.vdata): - value = nval[:len(self.vdata)] + value = value[:len(self.vdata)] self.vdata[:len(value)] = value self.vfld.putarraylen(len(value)) if alarm: @@ -267,13 +267,13 @@ def process(self, rec, reason=None): # sync record to table self.inst.table.log.debug('%s <- %s (%s)', self.inst.name, rec.NAME, rec.VAL) if self.vdata is None: - nval = self.vfld.getval() + value = self.vfld.getval() else: # A copy is made which can be used without locking the record - nval = self.vdata[:self.vfld.getarraylen()].copy() + value = self.vdata[:self.vfld.getarraylen()].copy() with self.inst.table.lock: - oval, self.inst.value = self.inst.value, nval + oval, self.inst.value = self.inst.value, value # Execute actions self.inst._exec(oval) From ba69d4cf9922ca97666641d18717b02f53ae4fd3 Mon Sep 17 00:00:00 2001 From: pheest Date: Thu, 20 Aug 2026 16:11:25 +0100 Subject: [PATCH 5/5] Correct possible editing mistake --- devsupApp/src/devsup/ptable.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/devsupApp/src/devsup/ptable.py b/devsupApp/src/devsup/ptable.py index 9c38968..db3adc6 100644 --- a/devsupApp/src/devsup/ptable.py +++ b/devsupApp/src/devsup/ptable.py @@ -132,7 +132,7 @@ def allvalid(self, fn): "Decorator to run an action when all parameters have valid values" return _add_action(self, (all, lambda p:p.isvalid), fn) def anynotvalid(self, fn): - "Decorator to run an action when any parameters has an iid value" + "Decorator to run an action when any parameters has an invalid value" return _add_action(self, (any, lambda p:not p.isvalid), fn) def oncondition(self, fmap, freduce=all): """Decorator for a custom condition. @@ -172,9 +172,9 @@ def _set_value(self, val): value = property(_get_value, _set_value, doc="The current parameter value") @property def isvalid(self): - """Is the parameter value valid (not None and no IID_ALARM) + """Is the parameter value valid (not None and no INVALID_ALARM) """ - return self.alarm < IID_ALARM and self._value is not None + return self.alarm < INVALID_ALARM and self._value is not None def notify(self): """Notify attached records of parameter value change. A no-op unless Parameter(iointr=True)