From 9bb4fac5ad60646f70c4ab0773005c68752d32bd Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Wed, 29 Jul 2026 20:51:06 +0100 Subject: [PATCH 1/5] Separated from devel branch. This version enables use of long integers and strings. --- .ci | 2 +- devsupApp/src/Makefile | 3 + devsupApp/src/dbfield.c | 12 ++-- devsupApp/src/devsup/__init__.py | 37 ++++--------- devsupApp/src/devsup/_dbapi.dbd | 22 ++++++++ devsupApp/src/devsup/_int64.dbd | 2 + devsupApp/src/devsup/_lsilso.dbd | 2 + devsupApp/src/devsup/test/test_db.py | 82 ++++++++++++++++++++++++++++ 8 files changed, 131 insertions(+), 31 deletions(-) create mode 100644 devsupApp/src/devsup/_dbapi.dbd create mode 100644 devsupApp/src/devsup/_int64.dbd create mode 100644 devsupApp/src/devsup/_lsilso.dbd diff --git a/.ci b/.ci index 4e4f33e..a32a1da 160000 --- a/.ci +++ b/.ci @@ -1 +1 @@ -Subproject commit 4e4f33e54f59343c77342200a95800073661e7ac +Subproject commit a32a1dab0586dde3f5b2990506d54950e61ce25b diff --git a/devsupApp/src/Makefile b/devsupApp/src/Makefile index 9f1b468..8e4ff2d 100644 --- a/devsupApp/src/Makefile +++ b/devsupApp/src/Makefile @@ -31,6 +31,9 @@ _dbapi_SRCS += pyDevSupCommon_registerRecordDeviceDriver.cpp _dbapi_LIBS += $(EPICS_BASE_IOC_LIBS) PY += devsup/__init__.py +PY += devsup/_dbapi.dbd +PY += devsup/_int64.dbd +PY += devsup/_lsilso.dbd PY += devsup/db.py PY += devsup/dset.py PY += devsup/hooks.py diff --git a/devsupApp/src/dbfield.c b/devsupApp/src/dbfield.c index 94da410..f2fc1f9 100644 --- a/devsupApp/src/dbfield.c +++ b/devsupApp/src/dbfield.c @@ -304,8 +304,8 @@ static PyObject* pyField_putval(pyField *self, PyObject* args) OP(LONG, epicsInt32, PyInt_AsLong); OP(ULONG, epicsUInt32, PyInt_AsLong); #ifdef HAVE_INT64 - OP(INT64, epicsInt32, PyLong_AsLongLong); - OP(UINT64, epicsUInt32, PyLong_AsLongLong); + OP(INT64, epicsInt64, PyLong_AsLongLong); + OP(UINT64, epicsUInt64, PyLong_AsLongLong); #endif OP(FLOAT, epicsFloat32,PyFloat_AsDouble); OP(DOUBLE,epicsFloat64,PyFloat_AsDouble); @@ -322,11 +322,15 @@ static PyObject* pyField_putval(pyField *self, PyObject* args) fld = PyString_AsString(val); #endif if(fld) { - strncpy(dest, fld, MAX_STRING_SIZE); - dest[MAX_STRING_SIZE-1]='\0'; + strncpy(dest, fld, self->addr.field_size); + dest[self->addr.field_size-1]='\0'; } else { dest[0] = '\0'; } + if (self->addr.special == SPC_MOD) + /* This is needed for long string support. */ + if (prset = dbGetRset(&self->addr)) + prset->special(&self->addr, 1); #if PY_MAJOR_VERSION >= 3 Py_DECREF(data); #endif diff --git a/devsupApp/src/devsup/__init__.py b/devsupApp/src/devsup/__init__.py index dd8da5a..822ff7b 100644 --- a/devsupApp/src/devsup/__init__.py +++ b/devsupApp/src/devsup/__init__.py @@ -52,32 +52,17 @@ def _init(iocMain=False): path=os.path.join(XEPICS_BASE, "dbd")) _dbapi._dbd_rrd_base() - with tempfile.NamedTemporaryFile() as F: - F.write(""" -device(longin, INST_IO, pydevsupComIn, "Python Device") -device(longout, INST_IO, pydevsupComOut, "Python Device") - -device(ai, INST_IO, pydevsupComIn, "Python Device") -device(ao, INST_IO, pydevsupComOut, "Python Device") - -device(stringin, INST_IO, pydevsupComIn, "Python Device") -device(stringout, INST_IO, pydevsupComOut, "Python Device") - -device(bi, INST_IO, pydevsupComIn, "Python Device") -device(bo, INST_IO, pydevsupComOut, "Python Device") - -device(mbbi, INST_IO, pydevsupComIn, "Python Device") -device(mbbo, INST_IO, pydevsupComOut, "Python Device") - -device(mbbiDirect, INST_IO, pydevsupComIn, "Python Device") -device(mbboDirect, INST_IO, pydevsupComOut, "Python Device") - -device(waveform, INST_IO, pydevsupComIn, "Python Device") -device(aai, INST_IO, pydevsupComIn, "Python Device") -device(aao, INST_IO, pydevsupComOut, "Python Device") -""".encode('ascii')) - F.flush() - _dbapi.dbReadDatabase(F.name) + dirname = os.path.dirname(__file__) + dbd_name = dirname + "/_dbapi.dbd" + _dbapi.dbReadDatabase(dbd_name) + if epicsver >= (3, 15, 0, 2): + # Long strings are impletemented. + dbd_name = dirname + "/_lsilso.dbd" + _dbapi.dbReadDatabase(dbd_name) + if epicsver >= (3, 16, 1, 0): + # Long ints are impletemented. + dbd_name = dirname + "/_int64.dbd" + _dbapi.dbReadDatabase(dbd_name) _dbapi._dbd_setup() def _fini(iocMain=False): diff --git a/devsupApp/src/devsup/_dbapi.dbd b/devsupApp/src/devsup/_dbapi.dbd new file mode 100644 index 0000000..0d791aa --- /dev/null +++ b/devsupApp/src/devsup/_dbapi.dbd @@ -0,0 +1,22 @@ + +device(longin, INST_IO, pydevsupComIn, "Python Device") +device(longout, INST_IO, pydevsupComOut, "Python Device") + +device(ai, INST_IO, pydevsupComIn, "Python Device") +device(ao, INST_IO, pydevsupComOut, "Python Device") + +device(stringin, INST_IO, pydevsupComIn, "Python Device") +device(stringout, INST_IO, pydevsupComOut, "Python Device") + +device(bi, INST_IO, pydevsupComIn, "Python Device") +device(bo, INST_IO, pydevsupComOut, "Python Device") + +device(mbbi, INST_IO, pydevsupComIn, "Python Device") +device(mbbo, INST_IO, pydevsupComOut, "Python Device") + +device(mbbiDirect, INST_IO, pydevsupComIn, "Python Device") +device(mbboDirect, INST_IO, pydevsupComOut, "Python Device") + +device(waveform, INST_IO, pydevsupComIn, "Python Device") +device(aai, INST_IO, pydevsupComIn, "Python Device") +device(aao, INST_IO, pydevsupComOut, "Python Device") diff --git a/devsupApp/src/devsup/_int64.dbd b/devsupApp/src/devsup/_int64.dbd new file mode 100644 index 0000000..120fb64 --- /dev/null +++ b/devsupApp/src/devsup/_int64.dbd @@ -0,0 +1,2 @@ +device(int64in, INST_IO, pydevsupComIn, "Python Device") +device(int64out, INST_IO, pydevsupComOut, "Python Device") diff --git a/devsupApp/src/devsup/_lsilso.dbd b/devsupApp/src/devsup/_lsilso.dbd new file mode 100644 index 0000000..761ef5b --- /dev/null +++ b/devsupApp/src/devsup/_lsilso.dbd @@ -0,0 +1,2 @@ +device(lsi, INST_IO, pydevsupComIn, "Python Device") +device(lso, INST_IO, pydevsupComOut, "Python Device") \ No newline at end of file diff --git a/devsupApp/src/devsup/test/test_db.py b/devsupApp/src/devsup/test/test_db.py index 2dc33e0..3280334 100644 --- a/devsupApp/src/devsup/test/test_db.py +++ b/devsupApp/src/devsup/test/test_db.py @@ -2,6 +2,7 @@ import os import unittest import tempfile +import time import numpy from numpy.testing import assert_array_almost_equal, assert_array_equal @@ -163,3 +164,84 @@ def test_increment(self): with rec: self.assertEqual(rec.VAL, 1) self.assertEqual(rec.UDF, 0) +class TestLongStringField(IOCHelper): + db = """ + record(lsi, "rec:lsi") { + field(SIZV, 128) + field(SCAN, "I/O Intr") + } + record(lso, "rec:lso") { + field(SIZV, 128) + field(DOL, "rec:lsi.VAL$") + field(OMSL, "closed_loop") + } + """ + if _dbapi.epicsver[:4] < (3, 15, 0, 2): + # Long strings not impletemented yet. + db = None + + def test_lsilso(self): + if _dbapi.epicsver[:4] < (3, 15, 0, 2): + # Long strings not impletemented yet. + return + + lsi = getRecord("rec:lsi") + lso = getRecord("rec:lso") + + with lsi: + self.assertEqual(lsi.VAL, "") + + lsi.VAL = "test" + self.assertEqual(lsi.VAL, "test") + + lsi.VAL = "" + self.assertEqual(lsi.VAL, "") + + # does not truncate + 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() + time.sleep(0.01) # The linked value needs a small amount of time to update. + + with lso: + self.assertEqual(lso.VAL, lsi.VAL) + +class TestInt64Field(IOCHelper): + db = """ + record(int64in, "rec:in64") { + field(SCAN, "I/O Intr") + } + record(int64out, "rec:out64") { + field(DOL, "rec:in64.VAL") + field(OMSL, "closed_loop") + } + """ + if _dbapi.epicsver[:4] < (3, 16, 1, 0): + # Long ints not impletemented yet. + db = None + + def testint64(self): + if _dbapi.epicsver[:4] < (3, 16, 1, 0): + # Long ints not impletemented yet. + return + in64 = getRecord("rec:in64") + out64 = getRecord("rec:out64") + + with in64: + self.assertEqual(in64.VAL, 0) + + in64.VAL = 42 + self.assertEqual(in64.VAL, 42) + + in64.VAL = 0x7FFFFFFFFFFFFFFE + self.assertEqual(in64.VAL, 0x7FFFFFFFFFFFFFFE) + + in64.VAL = 0x7FFFFFFFFFFFFFFF + self.assertEqual(in64.VAL, 0x7FFFFFFFFFFFFFFF) + + out64.scan() + time.sleep(0.01) # The linked value needs a small amount of time to update. + + with out64: + self.assertEqual(out64.VAL, in64.VAL) From 48c15172a6a4d4ed01aac0d64ee6e99175c48b4b Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Thu, 30 Jul 2026 14:02:08 +0100 Subject: [PATCH 2/5] Corrected typo. --- devsupApp/src/devsup/test/test_db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devsupApp/src/devsup/test/test_db.py b/devsupApp/src/devsup/test/test_db.py index 3280334..1becd62 100644 --- a/devsupApp/src/devsup/test/test_db.py +++ b/devsupApp/src/devsup/test/test_db.py @@ -218,12 +218,12 @@ class TestInt64Field(IOCHelper): } """ if _dbapi.epicsver[:4] < (3, 16, 1, 0): - # Long ints not impletemented yet. + # Long ints not implemented yet. db = None def testint64(self): if _dbapi.epicsver[:4] < (3, 16, 1, 0): - # Long ints not impletemented yet. + # Long ints not implemented yet. return in64 = getRecord("rec:in64") out64 = getRecord("rec:out64") From 085a2def6ed57c37c89710630db518aa8ca44a68 Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Thu, 30 Jul 2026 16:03:03 +0100 Subject: [PATCH 3/5] Added the calcout record because @andrewstarritt wants it. --- devsupApp/src/devsup/_dbapi.dbd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devsupApp/src/devsup/_dbapi.dbd b/devsupApp/src/devsup/_dbapi.dbd index 0d791aa..32aae22 100644 --- a/devsupApp/src/devsup/_dbapi.dbd +++ b/devsupApp/src/devsup/_dbapi.dbd @@ -1,13 +1,13 @@ device(longin, INST_IO, pydevsupComIn, "Python Device") device(longout, INST_IO, pydevsupComOut, "Python Device") +device(calcout, INST_IO, pydevsupComOut, "Python Device") device(ai, INST_IO, pydevsupComIn, "Python Device") device(ao, INST_IO, pydevsupComOut, "Python Device") device(stringin, INST_IO, pydevsupComIn, "Python Device") device(stringout, INST_IO, pydevsupComOut, "Python Device") - device(bi, INST_IO, pydevsupComIn, "Python Device") device(bo, INST_IO, pydevsupComOut, "Python Device") From 4b5fbeb939fac2484c9f14c9beb67a8d0283b7bb Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Mon, 3 Aug 2026 20:04:00 +0100 Subject: [PATCH 4/5] Added TestCalcOutRecord - it had only been added to my devel branch. Removed use of assign-within-condition. --- devsupApp/src/dbfield.c | 51 ++++++++++++++-------------- devsupApp/src/devsup/test/test_db.py | 20 ++++++++++- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/devsupApp/src/dbfield.c b/devsupApp/src/dbfield.c index f2fc1f9..7a65039 100644 --- a/devsupApp/src/dbfield.c +++ b/devsupApp/src/dbfield.c @@ -3,7 +3,6 @@ #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE -#include #ifdef HAVE_NUMPY #include #endif @@ -117,9 +116,9 @@ static int assign_array(DBADDR *paddr, PyObject *arr) { #ifdef HAVE_NUMPY void *rawfield = paddr->pfield; - rset *prset; - PyObject *aval; - PyArrayObject *array = (PyArrayObject *)arr; + rset *prset = NULL; + PyArrayObject *aval = NULL; + PyArrayObject * array = (PyArrayObject *)arr; unsigned elemsize = dbValueSize(paddr->field_type); unsigned long maxlen = paddr->no_elements, insize; PyArray_Descr *desc = dbf2np[paddr->field_type]; @@ -139,18 +138,20 @@ static int assign_array(DBADDR *paddr, PyObject *arr) insize = PyArray_DIM(array, 0); - if(paddr->special==SPC_DBADDR && - (prset=dbGetRset(paddr)) && - prset->get_array_info) + if(paddr->special==SPC_DBADDR) { - /* array */ - char *datasave=paddr->pfield; - long noe, off; - if(prset->get_array_info(paddr, &noe, &off)) { - PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s", - paddr->precord->name, - paddr->pfldDes->name); - return 1; + prset = prset=dbGetRset(paddr); + if (prset->get_array_info) + { + /* array */ + char *datasave=paddr->pfield; + long noe, off; + if(prset->get_array_info(paddr, &noe, &off)) { + PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s", + paddr->precord->name, + paddr->pfldDes->name); + return 1; + } } rawfield = paddr->pfield; @@ -162,25 +163,23 @@ static int assign_array(DBADDR *paddr, PyObject *arr) if(!(aval = PyArray_FromAny(arr, desc, 1, 2, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE, arr))) return 1; - if(elemsize!=PyArray_ITEMSIZE((PyArrayObject *)aval)) { + if(elemsize!=PyArray_ITEMSIZE(aval)) { PyErr_Format(PyExc_AssertionError, "item size mismatch %u %u", - elemsize, (unsigned)PyArray_ITEMSIZE((PyArrayObject *)aval)); - Py_DECREF(aval); + elemsize, (unsigned)PyArray_ITEMSIZE(aval) ); return 1; } - memcpy(rawfield, PyArray_GETPTR1((PyArrayObject *)aval, 0), insize*elemsize); + memcpy(rawfield, PyArray_GETPTR1(aval, 0), insize*elemsize); Py_DECREF(aval); - if(paddr->special==SPC_DBADDR && - (prset=dbGetRset(paddr)) && - prset->get_array_info) + if(prset) { - if(prset->put_array_info(paddr, insize)) { - PyErr_Format(PyExc_ValueError, "Error setting array info for %s.%s", - paddr->precord->name, - paddr->pfldDes->name); + if (prset->put_array_info) + if(prset->put_array_info(paddr, insize)) { + PyErr_Format(PyExc_ValueError, "Error setting array info for %s.%s", + paddr->precord->name, + paddr->pfldDes->name); return 1; } } diff --git a/devsupApp/src/devsup/test/test_db.py b/devsupApp/src/devsup/test/test_db.py index 1becd62..4673b29 100644 --- a/devsupApp/src/devsup/test/test_db.py +++ b/devsupApp/src/devsup/test/test_db.py @@ -130,7 +130,6 @@ def test_wf_string(self): assert_array_equal(rec.VAL, numpy.asarray(["zero", "", "one", "This is a really long string which shoul", "", "last"], dtype='S40')) - class TestDset(IOCHelper): db = """ record(longin, "rec:li") { @@ -164,6 +163,7 @@ def test_increment(self): with rec: self.assertEqual(rec.VAL, 1) self.assertEqual(rec.UDF, 0) + class TestLongStringField(IOCHelper): db = """ record(lsi, "rec:lsi") { @@ -245,3 +245,21 @@ def testint64(self): with out64: self.assertEqual(out64.VAL, in64.VAL) + +class TestCalcOutRecord(IOCHelper): + db = """ + record(calcout, "rec:calcout") { + field(OOPT, "On Change") + field(INPA, "0") + field(INPB, "0") + field(CALC, "A+B") + } + """ + def test_calcoutrecord(self): + rec = getRecord('rec:calcout') + self.assertEqual(rec.VAL, 0) + rec.A = 40 + rec.B = 2 + self.assertEqual(rec.scan(sync=True), 0) + self.assertEqual(rec.VAL, 42) + \ No newline at end of file From 94d97dc0ee0c31a361f15c16a7a6cee8d7a8cff1 Mon Sep 17 00:00:00 2001 From: Peter Heesterman Date: Mon, 3 Aug 2026 20:17:58 +0100 Subject: [PATCH 5/5] Corrected mistake. --- devsupApp/src/dbfield.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devsupApp/src/dbfield.c b/devsupApp/src/dbfield.c index 7a65039..d0534f9 100644 --- a/devsupApp/src/dbfield.c +++ b/devsupApp/src/dbfield.c @@ -141,10 +141,10 @@ static int assign_array(DBADDR *paddr, PyObject *arr) if(paddr->special==SPC_DBADDR) { prset = prset=dbGetRset(paddr); + void *datasave=paddr->pfield; if (prset->get_array_info) { /* array */ - char *datasave=paddr->pfield; long noe, off; if(prset->get_array_info(paddr, &noe, &off)) { PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s", @@ -160,7 +160,7 @@ static int assign_array(DBADDR *paddr, PyObject *arr) } Py_XINCREF(desc); - if(!(aval = PyArray_FromAny(arr, desc, 1, 2, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE, arr))) + if(!(aval = (PyArrayObject *)PyArray_FromAny(arr, desc, 1, 2, NPY_ARRAY_C_CONTIGUOUS | NPY_ARRAY_ALIGNED | NPY_ARRAY_WRITEABLE, arr))) return 1; if(elemsize!=PyArray_ITEMSIZE(aval)) {