From 6bc7170fc280f15a129c5ff0b4c982d9d8ad001c Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Wed, 28 Oct 2026 20:05:55 +0000 Subject: [PATCH] Inverted the logic of 'raw' which previously meant 'not raw'. This was very confusing and counter-intuitive. This does not alter the functionality. This includes changing the default value of 'rawsupport' from 0 to 1. Notably test_db.py does not set 'raw' so relies on the default value. Removed pydevsupComSpec which was not used anywhere. Changed the names of init_record and init_record2 to init_record_in and init_record_out for greater clarity. Changed the names of process_record and process_record2 to process_record_out and process_record_in for greater clarity. Added some more detailed test logic to test_db.py. Most importantly, added clearing of the UDF field within ptable.py to ensure that records without VAL set and without other means of clearing UDF do not retain the UDF alarm state. --- archApp/bplreport.py | 2 +- devsupApp/src/dbdset.c | 44 ++++++++++++++++------------ devsupApp/src/devsup/interfaces.py | 2 +- devsupApp/src/devsup/ptable.py | 20 ++++++++++--- devsupApp/src/devsup/test/test_db.py | 24 +++++++-------- testApp/test5.py | 2 +- weatherApp/weather.py | 2 +- 7 files changed, 57 insertions(+), 39 deletions(-) diff --git a/archApp/bplreport.py b/archApp/bplreport.py index 14aca57..cdb6a4c 100644 --- a/archApp/bplreport.py +++ b/archApp/bplreport.py @@ -88,7 +88,7 @@ def _stopWorker(): print ('BPL worker stopped') class ReportItem(object): - raw = True + raw = False def __init__(self, rec, args): # " .. " opname, src = args.split(None,2)[:2] diff --git a/devsupApp/src/dbdset.c b/devsupApp/src/dbdset.c index 954d0c5..9baa15f 100644 --- a/devsupApp/src/dbdset.c +++ b/devsupApp/src/dbdset.c @@ -71,8 +71,11 @@ static long parse_link(dbCommon *prec, const char* src) Py_DECREF(ret); ret = PyObject_GetAttrString(priv->support, "raw"); - if(!ret) + if(!ret) { PyErr_Clear(); + /* Defaults to 1 if not set. */ + priv->rawsupport = 1; + } else if(ret && PyObject_IsTrue(ret)==1) priv->rawsupport = 1; Py_XDECREF(ret); @@ -180,15 +183,16 @@ static long report(int lvl) return 0; } -static long init_record(dbCommon *prec) +static long init_record_in(dbCommon *prec) { return 0; } -static long init_record2(dbCommon *prec) +static long init_record_out(dbCommon *prec) { pyDevice *priv = prec->dpvt; - if(priv && priv->rawsupport) + if(priv && !priv->rawsupport) + /* This means sucessful read but do not process to clear UDF */ return 2; return 0; } @@ -347,12 +351,21 @@ static long process_record(dbCommon *prec) return 0; } -static long process_record2(dbCommon *prec) +static long process_record_out(dbCommon *prec) { pyDevice *priv = prec->dpvt; long ret = process_record(prec); - if(ret==0 && priv && priv->rawsupport) + return ret; +} + +static long process_record_in(dbCommon *prec) +{ + pyDevice *priv = prec->dpvt; + long ret = process_record(prec); + if(ret==0 && priv && !priv->rawsupport) { + /* This means sucessful read but do not process to clear UDF */ ret = 2; + } return ret; } @@ -374,18 +387,14 @@ typedef struct { DEVSUPFUN linconv; } dset6; -static dset6 pydevsupComSpec = {{6, (DEVSUPFUN)&report, (DEVSUPFUN)&init, - (DEVSUPFUN)&init_record, - (DEVSUPFUN)&get_iointr_info}, - (DEVSUPFUN)&process_record}; static dset6 pydevsupComOut = {{6, (DEVSUPFUN)&report, (DEVSUPFUN)&init, - (DEVSUPFUN)&init_record2, + (DEVSUPFUN)&init_record_out, (DEVSUPFUN)&get_iointr_info}, - (DEVSUPFUN)&process_record}; + (DEVSUPFUN)&process_record_out}; static dset6 pydevsupComIn = {{6, (DEVSUPFUN)&report, (DEVSUPFUN)&init, - (DEVSUPFUN)&init_record, + (DEVSUPFUN)&init_record_in, (DEVSUPFUN)&get_iointr_info}, - (DEVSUPFUN)&process_record2}; + (DEVSUPFUN)&process_record_in}; static long python_asub(aSubRecord* prec) { @@ -455,9 +464,8 @@ rset* pvar_rset_aSubRSET; int isPyRecord(dbCommon *prec) { - if(prec->dset==(dset*)&pydevsupComSpec - || prec->dset==(dset*)&pydevsupComIn - || prec->dset==(dset*)&pydevsupComOut) + if(prec->dset==(dset*)&pydevsupComIn || + prec->dset==(dset*)&pydevsupComOut) return 1; if(prec->rset==pvar_rset_aSubRSET) { aSubRecord *psub = (aSubRecord*)prec; @@ -477,13 +485,11 @@ int canIOScanRecord(dbCommon *prec) static const dset* pydsets[] = { - &pydevsupComSpec.com, &pydevsupComIn.com, &pydevsupComOut.com, }; static const char* pydsetnames[] = { - "pydevsupComSpec", "pydevsupComIn", "pydevsupComOut", }; diff --git a/devsupApp/src/devsup/interfaces.py b/devsupApp/src/devsup/interfaces.py index 8c4ef7c..1d8c7a9 100644 --- a/devsupApp/src/devsup/interfaces.py +++ b/devsupApp/src/devsup/interfaces.py @@ -3,7 +3,7 @@ class DeviceSupport(Interface): - raw = Attribute('True if this support modifies VAL instead of RVAL') + raw = Attribute('False if this support modifies VAL instead of RVAL') def detach(record): """Disconnect from the record. diff --git a/devsupApp/src/devsup/ptable.py b/devsupApp/src/devsup/ptable.py index d4cc631..288b55f 100644 --- a/devsupApp/src/devsup/ptable.py +++ b/devsupApp/src/devsup/ptable.py @@ -225,11 +225,12 @@ def __init__(self, inst, rec, info): # info("pyfield", "RVAL") # in the db file record. # It is used as 'rawsupport' in dbdset.c. - self.raw = fname!='RVAL' + self.raw = fname=='RVAL' self.vfld = rec.field(fname) self.vdata = None if len(self.vfld)>1: self.vdata = self.vfld.getarray() + self.udf = rec.field("UDF") def detach(self, rec): pass @@ -244,10 +245,14 @@ def process(self, rec, reason=None): with self.inst.table.lock: 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 value is not None: if stat is None: stat = COMM_ALARM + if value is not None: + if self.udf.getval(): + # Some record types only clear the UDF field if RVAL processing is used. + self.udf.putval(0) + + if value is not None: if self.vdata is None: self.vfld.putval(value) else: @@ -284,7 +289,14 @@ def process(self, rec, reason=None): stat = self.inst.stat if stat is None: stat = COMM_ALARM - rec.setSevr(self.inst.alarm, stat, self.inst.amsg) + if self.inst.value is not None: + if self.udf.getval(): + # Some record types only clear the UDF field if RVAL processing is used. + self.udf.putval(0) + rec.setSevr(self.inst.alarm, stat, self.inst.amsg) + else: + # undefined value + rec.setSevr(INVALID_ALARM, UDF_ALARM) for G in self.inst._groups: G._exec() diff --git a/devsupApp/src/devsup/test/test_db.py b/devsupApp/src/devsup/test/test_db.py index 045cd88..7063096 100644 --- a/devsupApp/src/devsup/test/test_db.py +++ b/devsupApp/src/devsup/test/test_db.py @@ -35,7 +35,7 @@ def test_link(self): with tgt: self.assertEqual(tgt.VAL, 0) - src.scan(sync=True) # lock and dbProcess() on this thread + self.assertEqual(src.scan(sync=True), 0) # lock and dbProcess() on this thread with tgt: self.assertEqual(tgt.VAL, 42) @@ -122,13 +122,13 @@ def array_float_test(self, src, tgt=None): src.VAL = numpy.arange(5) assert_array_almost_equal(src.VAL, numpy.arange(5)) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_almost_equal(tgt.VAL, src.VAL) src.VAL = numpy.arange(10) assert_array_almost_equal(src.VAL, numpy.arange(10)) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_almost_equal(tgt.VAL, src.VAL) with self.assertRaises(ValueError): @@ -137,7 +137,7 @@ def array_float_test(self, src, tgt=None): src.VAL = [] assert_array_almost_equal(src.VAL, []) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_almost_equal(tgt.VAL, src.VAL) # in-place modification @@ -150,7 +150,7 @@ def array_float_test(self, src, tgt=None): assert_array_almost_equal(src.VAL, [0.0, 1.0, 42.0, 3.0, 4.0]) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_almost_equal(tgt.VAL, src.VAL) def array_long_test(self, src, tgt=None): @@ -162,13 +162,13 @@ def array_long_test(self, src, tgt=None): src.VAL = range(5) assert_array_equal(src.VAL, range(5)) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_equal(tgt.VAL, src.VAL) src.VAL = range(10) assert_array_equal(src.VAL, range(10)) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_equal(tgt.VAL, src.VAL) with self.assertRaises(ValueError): @@ -177,7 +177,7 @@ def array_long_test(self, src, tgt=None): src.VAL = [] assert_array_equal(src.VAL, []) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_equal(tgt.VAL, src.VAL) # in-place modification @@ -190,7 +190,7 @@ def array_long_test(self, src, tgt=None): assert_array_equal(src.VAL, [0, 1, 42, 3, 4]) if tgt is not None: - src.scan(sync=True) + self.assertEqual(src.scan(sync=True), 0) assert_array_equal(tgt.VAL, src.VAL) def test_wf_float(self): @@ -256,7 +256,7 @@ def test_increment(self): self.assertEqual(rec.VAL, 0) self.assertEqual(rec.UDF, 1) - rec.scan(sync=True) + self.assertEqual(rec.scan(sync=True), 0) with rec: self.assertEqual(rec.VAL, 1) @@ -362,7 +362,7 @@ def test_lsilso(self): lsi.VAL = "This is a really long string which should NOT be truncated" self.assertEqual(lsi.VAL, "This is a really long string which should NOT be truncated") - lso.scan() + self.assertEqual(lso.scan(sync=True), 0) time.sleep(0.01) # The linked value needs a small amount of time to update. with lso: @@ -401,7 +401,7 @@ def testint64(self): in64.VAL = 0x7FFFFFFFFFFFFFFF self.assertEqual(in64.VAL, 0x7FFFFFFFFFFFFFFF) - out64.scan() + self.assertEqual(out64.scan(sync=True), 0) time.sleep(0.01) # The linked value needs a small amount of time to update. with out64: diff --git a/testApp/test5.py b/testApp/test5.py index ab2dc53..5d95593 100644 --- a/testApp/test5.py +++ b/testApp/test5.py @@ -42,7 +42,7 @@ def update(self, rec, unit, val): class UnitSupport(object): - raw = True + raw = False def __init__(self, rec, args): worker, self.unit = args.split(None, 1) diff --git a/weatherApp/weather.py b/weatherApp/weather.py index 2f1b6cc..38f18ff 100644 --- a/weatherApp/weather.py +++ b/weatherApp/weather.py @@ -43,7 +43,7 @@ class DataWatcher(object): # disable automatic RVAL -> VAL conversion. # We will update VAL ourselves. - raw = True + raw = False def __init__(self, rec, args): station, name = args.split(None, 1)