/* python has its own ideas about which version to support */ #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE #include #ifdef HAVE_NUMPY #include #endif #include #include #include #include #include #include #include "pydevsup.h" #ifdef HAVE_NUMPY static const int dbf2np_map[DBF_MENU+1] = { NPY_STRING, // DBF_STRING NPY_BYTE, // DBF_CHAR NPY_UBYTE, // DBF_UCHAR NPY_INT16, // DBF_SHORT NPY_UINT16, // DBF_USHORT NPY_INT32, // DBF_LONG NPY_UINT32, // DBF_ULONG NPY_FLOAT32, // DBF_FLOAT NPY_FLOAT64, // DBF_DOUBLE NPY_INT16, // DBF_ENUM NPY_INT16, // DBF_MENU }; static PyArray_Descr* dbf2np[DBF_MENU+1]; #endif typedef struct { PyObject_HEAD DBADDR addr; } pyField; static int pyField_Init(pyField *self, PyObject *args, PyObject *kws) { const char *pvname; if(!PyArg_ParseTuple(args, "s", &pvname)) return -1; if(dbNameToAddr(pvname, &self->addr)) { PyErr_Format(PyExc_ValueError, "%s: Record field not found", pvname); return -1; } return 0; } static void exc_wrong_ftype(pyField *self) { PyErr_Format(PyExc_TypeError, "Operation on field type %d is not supported", self->addr.field_type); } static PyObject* pyField_name(pyField *self) { return Py_BuildValue("ss", self->addr.precord->name, self->addr.pfldDes->name); } static PyObject* pyField_fldinfo(pyField *self) { short dbf=self->addr.field_type; short fsize=self->addr.field_size; unsigned long nelm=self->addr.no_elements; return Py_BuildValue("hhk", dbf, fsize, nelm); } static PyObject* build_array(PyObject* obj, void *data, unsigned short ftype, unsigned long nelem, int flags) { #ifdef HAVE_NUMPY PyArray_Descr *desc; int ndims = 1; npy_intp dims[1] = {nelem}; if(ftype>DBF_MENU) { PyErr_SetString(PyExc_TypeError, "Can not map field type to numpy type"); return NULL; } desc = dbf2np[ftype]; if(ftype==DBF_STRING) { desc->elsize = MAX_STRING_SIZE; } Py_XINCREF(desc); return PyArray_NewFromDescr(&PyArray_Type, desc, ndims, dims, NULL, data, flags, (PyObject*)obj); #else PyErr_SetNone(PyExc_NotImplementedError); return NULL; #endif } static int assign_array(DBADDR *paddr, PyObject *arr) { #ifdef HAVE_NUMPY void *rawfield = paddr->pfield; rset *prset; PyObject *aval; unsigned elemsize = dbValueSize(paddr->field_type); unsigned long maxlen = paddr->no_elements, insize; PyArray_Descr *desc = dbf2np[paddr->field_type]; if(paddr->field_type==DBF_STRING && (PyArray_NDIM(arr)!=2 || PyArray_DIM(arr,0)>maxlen || PyArray_DIM(arr,1)!=MAX_STRING_SIZE)) { PyErr_Format(PyExc_ValueError, "String array has incorrect shape or is too large"); return 1; } else if(PyArray_NDIM(arr)!=1 || PyArray_DIM(arr,0)>maxlen) { PyErr_Format(PyExc_ValueError, "Array has incorrect shape or is too large"); return 1; } insize = PyArray_DIM(arr, 0); if(paddr->special==SPC_DBADDR && (prset=dbGetRset(paddr)) && 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; /* get_array_info can modify pfield in >3.15.0.1 */ paddr->pfield = datasave; } Py_XINCREF(desc); if(!(aval = PyArray_FromAny(arr, desc, 1, 2, NPY_CARRAY, arr))) return 1; if(elemsize!=PyArray_ITEMSIZE(aval)) { PyErr_Format(PyExc_AssertionError, "item size mismatch %u %u", elemsize, (unsigned)PyArray_ITEMSIZE(aval) ); return 1; } 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->put_array_info(paddr, insize)) { PyErr_Format(PyExc_ValueError, "Error setting array info for %s.%s", paddr->precord->name, paddr->pfldDes->name); return 1; } } return 0; #else PyErr_SetNone(PyExc_NotImplementedError); return 1; #endif } static PyObject* pyField_getval(pyField *self) { void *rawfield = self->addr.pfield; rset *prset; if(self->addr.special==SPC_DBADDR && (prset=dbGetRset(&self->addr)) && prset->get_array_info) { /* array */ char *datasave=self->addr.pfield; long noe, off; if(prset->get_array_info(&self->addr, &noe, &off)) return PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s", self->addr.precord->name, self->addr.pfldDes->name); else if(noe<1) { PyErr_SetString(PyExc_IndexError, "zero length array"); return NULL; } rawfield = self->addr.pfield; /* get_array_info can modify pfield in >3.15.0.1 */ self->addr.pfield = datasave; if(self->addr.no_elements>1) { return build_array((PyObject*)self, rawfield, self->addr.field_type, noe, NPY_CARRAY_RO); } } switch(self->addr.field_type) { #define OP(FTYPE, CTYPE, FN) case DBF_##FTYPE: return FN(*(CTYPE*)rawfield) OP(CHAR, epicsInt8, PyInt_FromLong); OP(UCHAR, epicsUInt8, PyInt_FromLong); OP(ENUM, epicsEnum16, PyInt_FromLong); OP(MENU, epicsEnum16, PyInt_FromLong); OP(SHORT, epicsInt16, PyInt_FromLong); OP(USHORT,epicsUInt16, PyInt_FromLong); OP(LONG, epicsInt32, PyInt_FromLong); OP(ULONG, epicsUInt32, PyInt_FromLong); OP(FLOAT, epicsFloat32,PyFloat_FromDouble); OP(DOUBLE,epicsFloat64,PyFloat_FromDouble); #undef OP case DBF_STRING: return PyString_FromString((char*)rawfield); default: exc_wrong_ftype(self); return NULL; } } static PyObject* pyField_putval(pyField *self, PyObject* args) { PyObject *val; void *rawfield; rset *prset; if(!PyArg_ParseTuple(args, "O", &val)) return NULL; if(self->addr.field_type>DBF_MENU) { PyErr_SetString(PyExc_TypeError, "field type write not supported"); return NULL; } if(val==Py_None) { PyErr_Format(PyExc_ValueError, "Can't assign None to %s.%s", self->addr.precord->name, self->addr.pfldDes->name); return NULL; } #ifdef HAVE_NUMPY if(PyArray_Check(val)) { /* assign from array */ if(assign_array(&self->addr, val)) return NULL; Py_RETURN_NONE; } #endif if(self->addr.special==SPC_DBADDR && (prset=dbGetRset(&self->addr)) && prset->get_array_info) { /* writing scalar to array */ char *datasave=self->addr.pfield; long noe, off; /* ignored */ if(prset->get_array_info(&self->addr, &noe, &off)) return PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s", self->addr.precord->name, self->addr.pfldDes->name); rawfield = self->addr.pfield; /* get_array_info can modify pfield in >3.15.0.1 */ self->addr.pfield = datasave; } else rawfield = self->addr.pfield; switch(self->addr.field_type) { #define OP(FTYPE, CTYPE, FN) case DBF_##FTYPE: *(CTYPE*)rawfield = FN(val); break OP(CHAR, epicsInt8, PyInt_AsLong); OP(UCHAR, epicsUInt8, PyInt_AsLong); OP(ENUM, epicsEnum16, PyInt_AsLong); OP(MENU, epicsEnum16, PyInt_AsLong); OP(SHORT, epicsInt16, PyInt_AsLong); OP(USHORT,epicsUInt16, PyInt_AsLong); OP(LONG, epicsInt32, PyInt_AsLong); OP(ULONG, epicsUInt32, PyInt_AsLong); OP(FLOAT, epicsFloat32,PyFloat_AsDouble); OP(DOUBLE,epicsFloat64,PyFloat_AsDouble); #undef OP case DBF_STRING: { const char *fld; char *dest=rawfield; #if PY_MAJOR_VERSION >= 3 PyObject *data = PyUnicode_AsASCIIString(val); if(!data) return NULL; fld = PyBytes_AS_STRING(data); #else fld = PyString_AsString(val); #endif if(fld) { strncpy(dest, fld, MAX_STRING_SIZE); dest[MAX_STRING_SIZE-1]='\0'; } else { dest[0] = '\0'; } #if PY_MAJOR_VERSION >= 3 Py_DECREF(data); #endif break; } default: exc_wrong_ftype(self); return NULL; } if(self->addr.special==SPC_DBADDR && (prset=dbGetRset(&self->addr)) && prset->get_array_info) { if(prset->put_array_info(&self->addr, 1)) return PyErr_Format(PyExc_ValueError, "Error setting array info for %s.%s", self->addr.precord->name, self->addr.pfldDes->name); } Py_RETURN_NONE; } static PyObject *pyField_getarray(pyField *self) { rset *prset; char *data; if(self->addr.special==SPC_DBADDR && (prset=dbGetRset(&self->addr)) && prset->get_array_info) { char *datasave=self->addr.pfield; long noe, off; /* ignored */ prset->get_array_info(&self->addr, &noe, &off); data = self->addr.pfield; /* get_array_info can modify pfield in >3.15.0.1 */ self->addr.pfield = datasave; } else data = self->addr.pfield; return build_array((PyObject*)self, data, self->addr.field_type, self->addr.no_elements, NPY_CARRAY); } static PyObject *pyField_getlen(pyField *self) { rset *prset; if(self->addr.special==SPC_DBADDR && (prset=dbGetRset(&self->addr)) && prset->get_array_info) { char *datasave=self->addr.pfield; long noe, off; prset->get_array_info(&self->addr, &noe, &off); /* get_array_info can modify pfield in >3.15.0.1 */ self->addr.pfield = datasave; return PyInt_FromLong(noe); } else return PyInt_FromLong(1); } static PyObject *pyField_setlen(pyField *self, PyObject *args) { rset *prset = dbGetRset(&self->addr); Py_ssize_t len; if(!PyArg_ParseTuple(args, "n", &len)) return NULL; if(self->addr.special!=SPC_DBADDR || !prset || !prset->put_array_info) { PyErr_SetString(PyExc_TypeError, "Not an array field"); return NULL; } if(len<1 || len > self->addr.no_elements) { PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [1,%lu)", (long)len, (unsigned long)self->addr.no_elements); return NULL; } prset->put_array_info(&self->addr, len); Py_RETURN_NONE; } static PyObject* pyField_getTime(pyField *self) { DBLINK *plink = self->addr.pfield; epicsTimeStamp ret; long sts, sec, nsec; if(self->addr.field_type!=DBF_INLINK) { PyErr_SetString(PyExc_TypeError, "getTime can only be used on DBF_INLINK fields"); return NULL; } sts = dbGetTimeStamp(plink, &ret); if(sts) { PyErr_SetString(PyExc_TypeError, "getTime failed"); return NULL; } sec = ret.secPastEpoch + POSIX_TIME_AT_EPICS_EPOCH; nsec = ret.nsec; return Py_BuildValue("ll", sec, nsec); } static PyObject* pyField_getAlarm(pyField *self) { DBLINK *plink = self->addr.pfield; long sts; epicsEnum16 stat, sevr; if(self->addr.field_type!=DBF_INLINK) { PyErr_SetString(PyExc_TypeError, "getAlarm can only be used on DBF_INLINK fields"); return NULL; } sts = dbGetAlarm(plink, &stat, &sevr); if(sts) { PyErr_SetString(PyExc_TypeError, "getAlarm failed"); return NULL; } return Py_BuildValue("ll", (long)sevr, (long)stat); } static PyObject *pyField_len(pyField *self) { return PyInt_FromLong(self->addr.no_elements); } static PyMethodDef pyField_methods[] = { {"name", (PyCFunction)pyField_name, METH_NOARGS, "Return Names (\"record\",\"field\")"}, {"fieldinfo", (PyCFunction)pyField_fldinfo, METH_NOARGS, "Field type info\nReturn (type, size, #elements"}, {"getval", (PyCFunction)pyField_getval, METH_NOARGS, "Returns scalar version of field value"}, {"putval", (PyCFunction)pyField_putval, METH_VARARGS, "Sets field value from a scalar"}, {"getarray", (PyCFunction)pyField_getarray, METH_NOARGS, "Return a numpy ndarray refering to this field for in-place operations."}, {"getarraylen", (PyCFunction)pyField_getlen, METH_NOARGS, "Return current number of valid elements for array fields."}, {"putarraylen", (PyCFunction)pyField_setlen, METH_VARARGS, "Set number of valid elements for array fields."}, {"getTime", (PyCFunction)pyField_getTime, METH_NOARGS, "Return link target timestamp as a tuple (sec, nsec)."}, {"getAlarm", (PyCFunction)pyField_getAlarm, METH_NOARGS, "Return link target alarm condtions as a tuple (severity, status)."}, {"__len__", (PyCFunction)pyField_len, METH_NOARGS, "Maximum number of elements storable in this field"}, {NULL, NULL, 0, NULL} }; #if PY_MAJOR_VERSION < 3 static Py_ssize_t pyField_buf_getcount(pyField *self, Py_ssize_t *totallen) { if(totallen) *totallen = self->addr.field_size * self->addr.no_elements; return 1; } static Py_ssize_t pyField_buf_getbuf(pyField *self, Py_ssize_t bufid, void **data) { if(bufid!=0) { PyErr_SetString(PyExc_SystemError, "Requested invalid segment"); return -1; } *data = self->addr.pfield; return self->addr.field_size * self->addr.no_elements; } static Py_ssize_t pyField_buf_getcharbuf(pyField *self, Py_ssize_t bufid, char **data) { if(bufid!=0) { PyErr_SetString(PyExc_SystemError, "Requested invalid segment"); return -1; } else if(self->addr.field_size!=1) { PyErr_SetString(PyExc_TypeError, "Field type must be CHAR or UCHAR"); return -1; } *data = self->addr.pfield; return self->addr.field_size * self->addr.no_elements; } #endif static int pyField_buf_getbufferproc(pyField *self, Py_buffer *buf, int flags) { return PyBuffer_FillInfo(buf, (PyObject*)self, self->addr.pfield, self->addr.field_size * self->addr.no_elements, 0, flags); } static PyBufferProcs pyField_buf_methods = { #if PY_MAJOR_VERSION < 3 (readbufferproc)pyField_buf_getbuf, (writebufferproc)pyField_buf_getbuf, (segcountproc)pyField_buf_getcount, (charbufferproc)pyField_buf_getcharbuf, #endif (getbufferproc)pyField_buf_getbufferproc, (releasebufferproc)NULL, }; static PyTypeObject pyField_type = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, #endif "_dbapi._Field", sizeof(pyField), }; int pyField_prepare(PyObject *module) { size_t i; import_array1(-1); pyField_type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE; #if PY_MAJOR_VERSION < 3 pyField_type.tp_flags |= Py_TPFLAGS_HAVE_GETCHARBUFFER|Py_TPFLAGS_HAVE_NEWBUFFER; #endif pyField_type.tp_methods = pyField_methods; pyField_type.tp_as_buffer = &pyField_buf_methods; pyField_type.tp_init = (initproc)pyField_Init; pyField_type.tp_new = PyType_GenericNew; if(PyType_Ready(&pyField_type)<0) return -1; PyObject *typeobj=(PyObject*)&pyField_type; Py_INCREF(typeobj); if(PyModule_AddObject(module, "_Field", typeobj)) { Py_DECREF(typeobj); return -1; } #ifdef HAVE_NUMPY for(i=0; i<=DBF_MENU; i++) { dbf2np[i] = PyArray_DescrFromType(dbf2np_map[i]); assert(dbf2np[i]); } #endif return 0; } void pyField_cleanup(void) { size_t i; for(i=0; i<=DBF_MENU; i++) { Py_XDECREF(dbf2np[i]); dbf2np[i] = NULL; } }