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..d0534f9 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); + void *datasave=paddr->pfield; + if (prset->get_array_info) + { + /* array */ + 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; @@ -159,28 +160,26 @@ 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((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; } } @@ -304,8 +303,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 +321,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..32aae22 --- /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(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") + +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..4673b29 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 @@ -129,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") { @@ -163,3 +163,103 @@ 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 implemented yet. + db = None + + def testint64(self): + if _dbapi.epicsver[:4] < (3, 16, 1, 0): + # Long ints not implemented 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) + +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