Merge remote-tracking branch 'origin/devel'
* origin/devel: Python init: don't install sig handlers. Fix #5 Fix pyIocApp's Makefile testing cleanup cleanup and inithook minor update doc Base 3.14 py3 travis-ci update test field access and dset start PDB unittest rework to separate out python module separate softIocPy build Makefile updates from p4p # Conflicts: # devsupApp/src/dbapi.c # devsupApp/src/pydevsup.h
This commit is contained in:
@ -1,80 +1,36 @@
|
||||
TOP=../..
|
||||
|
||||
include $(TOP)/configure/CONFIG
|
||||
PYMODULE = NO
|
||||
include $(TOP)/configure/CONFIG_PY
|
||||
#----------------------------------------
|
||||
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
||||
#=============================
|
||||
|
||||
#=============================
|
||||
# Build the IOC application
|
||||
INSTALL_SHRLIB = $(PY_INSTALL_DIR)/devsup
|
||||
|
||||
LIBRARY = pyDevSup$(PY_LD_VER)
|
||||
LOADABLE_LIBRARY_HOST += _dbapi
|
||||
|
||||
SHRLIB_VERSION = 0
|
||||
TARGETS += $(COMMON_DIR)/pyDevSupCommon.dbd
|
||||
DBDDEPENDS_FILES += pyDevSupCommon.dbd$(DEP)
|
||||
|
||||
DBD += pyDevSup.dbd
|
||||
pyDevSupCommon_DBD += base.dbd
|
||||
|
||||
pyDevSup$(PY_LD_VER)_SYS_LIBS += python$(PY_LD_VER)
|
||||
dbapi_CPPFLAGS += -DXEPICS_ARCH=\"$(T_A)\"
|
||||
dbapi_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
dbapi_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
||||
dbapi_CPPFLAGS += -DPYDIR=\"python$(PY_VER)\"
|
||||
|
||||
setup_CPPFLAGS += -DXEPICS_ARCH=\"$(T_A)\"
|
||||
setup_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
setup_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
||||
setup_CPPFLAGS += -DPYDIR=\"python$(PY_VER)\"
|
||||
_dbapi_SRCS += dbapi.c
|
||||
_dbapi_SRCS += dbrec.c
|
||||
_dbapi_SRCS += dbfield.c
|
||||
_dbapi_SRCS += dbdset.c
|
||||
_dbapi_SRCS += utest.c
|
||||
|
||||
devsupMain_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||
|
||||
pyDevSup$(PY_LD_VER)_SRCS += setup.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbbase.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbrec.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbfield.c
|
||||
pyDevSup$(PY_LD_VER)_SRCS += dbdset.c
|
||||
|
||||
pyDevSup$(PY_LD_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
|
||||
PROD_IOC = softIocPy$(PY_VER)
|
||||
PRODNAME = $(addsuffix $(EXE),$(PROD))
|
||||
|
||||
# softIocPy.dbd will be created and installed
|
||||
DBD += softIocPy.dbd
|
||||
|
||||
# softIocPy.dbd will be made up from these files:
|
||||
softIocPy_DBD += base.dbd
|
||||
softIocPy_DBD += pyDevSup.dbd
|
||||
|
||||
softIocPy_DBD += system.dbd
|
||||
|
||||
softIocPy$(PY_VER)_LIBS += pyDevSup$(PY_LD_VER)
|
||||
|
||||
# softIocPy_registerRecordDeviceDriver.cpp derives from softIocPy.dbd
|
||||
softIocPy$(PY_VER)_SRCS += softIocPy_registerRecordDeviceDriver.cpp
|
||||
|
||||
# Build the main IOC entry point on workstation OSs.
|
||||
softIocPy$(PY_VER)_SRCS_DEFAULT += devsupMain.cpp
|
||||
|
||||
ifneq ($(DEVIOCSTATS),)
|
||||
softIocPy_DBD += devIocStats.dbd
|
||||
softIocPy$(PY_VER)_LIBS += devIocStats
|
||||
endif
|
||||
|
||||
ifneq ($(AUTOSAVE),)
|
||||
softIocPy_DBD += asSupport.dbd
|
||||
softIocPy$(PY_VER)_LIBS += autosave
|
||||
endif
|
||||
|
||||
ifneq ($(CAPUTLOG),)
|
||||
softIocPy_DBD += caPutLog.dbd
|
||||
softIocPy$(PY_VER)_LIBS += caPutLog
|
||||
endif
|
||||
|
||||
# Finally link to the EPICS Base libraries
|
||||
softIocPy$(PY_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
_dbapi_SRCS += pyDevSupCommon_registerRecordDeviceDriver.cpp
|
||||
|
||||
_dbapi_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||
|
||||
PY += devsup/__init__.py
|
||||
PY += devsup/_nullapi.py
|
||||
PY += devsup/db.py
|
||||
PY += devsup/dset.py
|
||||
PY += devsup/hooks.py
|
||||
@ -83,6 +39,10 @@ PY += devsup/util.py
|
||||
PY += devsup/disect.py
|
||||
PY += devsup/ptable.py
|
||||
|
||||
PY += devsup/test/__init__.py
|
||||
PY += devsup/test/util.py
|
||||
PY += devsup/test/test_db.py
|
||||
|
||||
#===========================
|
||||
|
||||
include $(TOP)/configure/RULES
|
||||
@ -98,3 +58,20 @@ pyconfig:
|
||||
@echo "Library path: $(PY_LIBDIRS)"
|
||||
@echo "USR_CPPFLAGS: $(USR_CPPFLAGS)"
|
||||
@echo "USR_LDFLAGS: $(USR_LDFLAGS)"
|
||||
|
||||
ifneq (,$(T_A))
|
||||
nose:
|
||||
PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(PYTHON) -m nose -P devsup $(NOSEFLAGS)
|
||||
|
||||
# bounce back down to the sphinx generated Makefile
|
||||
# aren't Makefiles fun...
|
||||
sphinx:
|
||||
PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(MAKE) -C $(TOP)/documentation html
|
||||
|
||||
sh:
|
||||
echo "export PYTHONPATH=\$${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" > $(OUTPUT)
|
||||
|
||||
ipython:
|
||||
PYTHONPATH="${PYTHONPATH}:$(abspath $(TOP))/python$(PY_LD_VER)/$(EPICS_HOST_ARCH)" $(PYTHON) -c "import sys; sys.argv[0] = '$(PYTHON)'; from IPython.terminal.ipapp import launch_new_instance; launch_new_instance()"
|
||||
|
||||
endif
|
||||
|
@ -1,6 +1,3 @@
|
||||
/* Global interpreter setup
|
||||
*/
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
@ -21,11 +18,15 @@
|
||||
#include <epicsThread.h>
|
||||
#include <epicsExit.h>
|
||||
#include <alarm.h>
|
||||
#include <iocsh.h>
|
||||
#include <iocInit.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
initHookState pyInitLastState;
|
||||
|
||||
extern int pyDevSupCommon_registerRecordDeviceDriver(DBBASE *pbase);
|
||||
|
||||
typedef struct {
|
||||
const initHookState state;
|
||||
const char * const name;
|
||||
@ -104,6 +105,20 @@ void pyfile(const char* file)
|
||||
PyGILState_Release(state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const iocshArg argCode = {"python code", iocshArgString};
|
||||
static const iocshArg argFile = {"file", iocshArgString};
|
||||
|
||||
static const iocshArg* const codeArgs[] = {&argCode};
|
||||
static const iocshArg* const fileArgs[] = {&argFile};
|
||||
|
||||
static const iocshFuncDef codeDef = {"py", 1, codeArgs};
|
||||
static const iocshFuncDef fileDef = {"pyfile", 1, fileArgs};
|
||||
|
||||
static void codeRun(const iocshArgBuf *args){py(args[0].sval);}
|
||||
static void fileRun(const iocshArgBuf *args){pyfile(args[0].sval);}
|
||||
|
||||
initHookState pyInitLastState = (initHookState)-1;
|
||||
|
||||
static void pyhook(initHookState state)
|
||||
@ -139,28 +154,160 @@ fail:
|
||||
PyGILState_Release(gilstate);
|
||||
}
|
||||
|
||||
static
|
||||
PyObject* py_announce(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
static char* names[] = {"state", NULL};
|
||||
int state;
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "i", names, &state))
|
||||
return NULL;
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
initHookAnnounce((initHookState)state);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_iocsh(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
int ret;
|
||||
static char* names[] = {"script", "cmd", NULL};
|
||||
char *script=NULL, *cmd=NULL;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|ss", names, &script, &cmd))
|
||||
return NULL;
|
||||
|
||||
if(!(!script ^ !cmd)) {
|
||||
PyErr_SetString(PyExc_ValueError, "iocsh requires a script file name or command string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(script)
|
||||
ret = iocsh(script);
|
||||
else
|
||||
ret = iocshCmd(cmd);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
return PyInt_FromLong(ret);
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_dbReadDatabase(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
long status;
|
||||
static char* names[] = {"name", "fp", "path", "sub", NULL};
|
||||
char *fname=NULL, *path=NULL, *sub=NULL;
|
||||
int fd=-1;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|siss", names, &fname, &fd, &path, &sub))
|
||||
return NULL;
|
||||
|
||||
if(!((!fname) ^ (fd<0))) {
|
||||
PyErr_SetString(PyExc_ValueError, "dbReadDatabase requires a file name or descriptor");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(fname) {
|
||||
status = dbReadDatabase(&pdbbase, fname, path, sub);
|
||||
} else {
|
||||
FILE *ff = fdopen(fd, "r");
|
||||
status = dbReadDatabaseFP(&pdbbase, ff, path, sub);
|
||||
// dbReadDatabaseFP() has called fclose()
|
||||
}
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
if(status) {
|
||||
char buf[30];
|
||||
errSymLookup(status, buf, sizeof(buf));
|
||||
PyErr_SetString(PyExc_RuntimeError, buf);
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_iocInit(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
static char* names[] = {"isolate", NULL};
|
||||
PyObject *pyisolate = Py_True;
|
||||
int isolate, ret;
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|O", names, &pyisolate))
|
||||
return NULL;
|
||||
|
||||
isolate = PyObject_IsTrue(pyisolate);
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
ret = isolate ? iocBuildIsolated() : iocBuild();
|
||||
if(!ret)
|
||||
ret = iocRun();
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
if(ret)
|
||||
return PyErr_Format(PyExc_RuntimeError, "Error %d", ret);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_pyDevSupCommon(PyObject *unused)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
pyDevSupCommon_registerRecordDeviceDriver(pdbbase);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static struct PyMethodDef dbapimethod[] = {
|
||||
{"initHookAnnounce", (PyCFunction)py_announce, METH_VARARGS|METH_KEYWORDS,
|
||||
"initHookAnnounce(state)\n"},
|
||||
{"iocsh", (PyCFunction)py_iocsh, METH_VARARGS|METH_KEYWORDS,
|
||||
"Execute IOC shell script or command"},
|
||||
{"dbReadDatabase", (PyCFunction)py_dbReadDatabase, METH_VARARGS|METH_KEYWORDS,
|
||||
"Load EPICS database file"},
|
||||
{"iocInit", (PyCFunction)py_iocInit, METH_NOARGS,
|
||||
"Initialize IOC"},
|
||||
{"_dbd_setup", (PyCFunction)pyDBD_setup, METH_NOARGS, ""},
|
||||
{"_dbd_rrd_base", (PyCFunction)py_pyDevSupCommon, METH_NOARGS, ""},
|
||||
{"_dbd_cleanup", (PyCFunction)pyDBD_cleanup, METH_NOARGS, ""},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef dbapimodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_dbapi",
|
||||
"devsup._dbapi",
|
||||
NULL,
|
||||
-1,
|
||||
NULL
|
||||
&dbapimethod
|
||||
};
|
||||
#endif
|
||||
|
||||
/* initialize "magic" builtin module */
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyMODINIT_FUNC PyInit__dbapi(void)
|
||||
#else
|
||||
PyMODINIT_FUNC init_dbapi(void)
|
||||
#endif
|
||||
{
|
||||
PyObject *mod = NULL, *hookdict;
|
||||
PyObject *mod = NULL, *hookdict, *vertup;
|
||||
pystate *st;
|
||||
|
||||
pyDevReasonID = epicsThreadPrivateCreate();
|
||||
|
||||
iocshRegister(&codeDef, &codeRun);
|
||||
iocshRegister(&fileDef, &fileRun);
|
||||
initHookRegister(&pyhook);
|
||||
|
||||
import_array();
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
mod = PyModule_Create(&dbapimodule);
|
||||
#else
|
||||
mod = Py_InitModule("_dbapi", NULL);
|
||||
mod = Py_InitModule("devsup._dbapi", dbapimethod);
|
||||
#endif
|
||||
if(!mod)
|
||||
goto fail;
|
||||
@ -180,43 +327,6 @@ PyMODINIT_FUNC init_dbapi(void)
|
||||
}
|
||||
}
|
||||
|
||||
if(pyField_prepare(mod))
|
||||
goto fail;
|
||||
if(pyRecord_prepare(mod))
|
||||
goto fail;
|
||||
|
||||
MODINIT_RET(mod);
|
||||
|
||||
fail:
|
||||
fprintf(stderr, "Failed to initialize builtin _dbapi module!\n");
|
||||
Py_XDECREF(mod);
|
||||
MODINIT_RET(NULL);
|
||||
}
|
||||
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef constantsmodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_dbconstants",
|
||||
NULL,
|
||||
-1,
|
||||
NULL
|
||||
};
|
||||
#endif
|
||||
|
||||
/* initialize "magic" builtin module */
|
||||
PyMODINIT_FUNC init_dbconstants(void)
|
||||
{
|
||||
PyObject *mod = NULL, *vertup;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
mod = PyModule_Create(&constantsmodule);
|
||||
#else
|
||||
mod = Py_InitModule("_dbconstants", NULL);
|
||||
#endif
|
||||
if(!mod)
|
||||
MODINIT_RET(NULL);
|
||||
|
||||
PyModule_AddIntMacro(mod, NO_ALARM);
|
||||
PyModule_AddIntMacro(mod, MINOR_ALARM);
|
||||
PyModule_AddIntMacro(mod, MAJOR_ALARM);
|
||||
@ -279,150 +389,17 @@ PyMODINIT_FUNC init_dbconstants(void)
|
||||
if(vertup)
|
||||
PyModule_AddObject(mod, "pydevver", vertup);
|
||||
|
||||
if(pyField_prepare(mod))
|
||||
goto fail;
|
||||
if(pyRecord_prepare(mod))
|
||||
goto fail;
|
||||
if(pyUTest_prepare(mod))
|
||||
goto fail;
|
||||
|
||||
MODINIT_RET(mod);
|
||||
}
|
||||
|
||||
static void cleanupPy(void *junk)
|
||||
{
|
||||
PyThreadState *state = PyGILState_GetThisThreadState();
|
||||
|
||||
PyEval_RestoreThread(state);
|
||||
|
||||
/* special "fake" hook for shutdown */
|
||||
pyhook((initHookState)9999);
|
||||
|
||||
pyDBD_cleanup();
|
||||
|
||||
pyField_cleanup();
|
||||
|
||||
Py_Finalize();
|
||||
|
||||
epicsThreadPrivateDelete(pyDevReasonID);
|
||||
}
|
||||
|
||||
/* Initialize the interpreter environment
|
||||
*/
|
||||
static void setupPyInit(void)
|
||||
{
|
||||
PyImport_AppendInittab("_dbapi", init_dbapi);
|
||||
PyImport_AppendInittab("_dbconstants", init_dbconstants);
|
||||
PyImport_AppendInittab("_dbbase", init_dbbase);
|
||||
|
||||
Py_Initialize();
|
||||
PyEval_InitThreads();
|
||||
|
||||
(void)PyEval_SaveThread();
|
||||
|
||||
epicsAtExit(&cleanupPy, NULL);
|
||||
}
|
||||
|
||||
static void extendPath(PyObject *list,
|
||||
const char *base,
|
||||
const char *archdir)
|
||||
{
|
||||
PyObject *mod, *ret;
|
||||
|
||||
mod = PyImport_ImportModule("os.path");
|
||||
if(!mod)
|
||||
return;
|
||||
|
||||
ret = PyObject_CallMethod(mod, "join", "sss", base, PYDIR, archdir);
|
||||
if(ret && !PySequence_Contains(list, ret)) {
|
||||
PyList_Insert(list, 0, ret);
|
||||
}
|
||||
Py_XDECREF(ret);
|
||||
Py_DECREF(mod);
|
||||
if(PyErr_Occurred()) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void insertDefaultPath(PyObject *list)
|
||||
{
|
||||
const char *basedir, *pydevdir, *top, *arch;
|
||||
|
||||
basedir = getenv("EPICS_BASE");
|
||||
if(!basedir)
|
||||
basedir = XEPICS_BASE;
|
||||
pydevdir = getenv("PYDEV_BASE");
|
||||
if(!pydevdir)
|
||||
pydevdir = XPYDEV_BASE;
|
||||
top = getenv("TOP");
|
||||
arch = getenv("ARCH");
|
||||
if(!arch)
|
||||
arch = XEPICS_ARCH;
|
||||
|
||||
assert(PyList_Check(list));
|
||||
assert(PySequence_Check(list));
|
||||
extendPath(list, basedir, arch);
|
||||
extendPath(list, pydevdir, arch);
|
||||
if(top)
|
||||
extendPath(list, top, arch);
|
||||
}
|
||||
|
||||
static void setupPyPath(void)
|
||||
{
|
||||
PyObject *mod, *path = NULL;
|
||||
|
||||
mod = PyImport_ImportModule("sys");
|
||||
if(mod)
|
||||
path = PyObject_GetAttrString(mod, "path");
|
||||
fail:
|
||||
fprintf(stderr, "Failed to initialize builtin _dbapi module!\n");
|
||||
Py_XDECREF(mod);
|
||||
|
||||
if(path) {
|
||||
PyObject *cur;
|
||||
char cwd[PATH_MAX];
|
||||
|
||||
insertDefaultPath(path);
|
||||
|
||||
/* prepend current directory */
|
||||
if(getcwd(cwd, sizeof(cwd)-1)) {
|
||||
cwd[sizeof(cwd)-1] = '\0';
|
||||
cur = PyString_FromString(cwd);
|
||||
if(cur)
|
||||
PyList_Insert(path, 0, cur);
|
||||
Py_XDECREF(cur);
|
||||
}
|
||||
}
|
||||
Py_XDECREF(path);
|
||||
MODINIT_RET(NULL);
|
||||
}
|
||||
|
||||
|
||||
#include <iocsh.h>
|
||||
|
||||
static const iocshArg argCode = {"python code", iocshArgString};
|
||||
static const iocshArg argFile = {"file", iocshArgString};
|
||||
|
||||
static const iocshArg* const codeArgs[] = {&argCode};
|
||||
static const iocshArg* const fileArgs[] = {&argFile};
|
||||
|
||||
static const iocshFuncDef codeDef = {"py", 1, codeArgs};
|
||||
static const iocshFuncDef fileDef = {"pyfile", 1, fileArgs};
|
||||
|
||||
static void codeRun(const iocshArgBuf *args){py(args[0].sval);}
|
||||
static void fileRun(const iocshArgBuf *args){pyfile(args[0].sval);}
|
||||
|
||||
static void pySetupReg(void)
|
||||
{
|
||||
PyGILState_STATE state;
|
||||
|
||||
pyDevReasonID = epicsThreadPrivateCreate();
|
||||
|
||||
setupPyInit();
|
||||
iocshRegister(&codeDef, &codeRun);
|
||||
iocshRegister(&fileDef, &fileRun);
|
||||
initHookRegister(&pyhook);
|
||||
|
||||
state = PyGILState_Ensure();
|
||||
init_dbapi();
|
||||
setupPyPath();
|
||||
if(PyErr_Occurred()) {
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
PyGILState_Release(state);
|
||||
}
|
||||
|
||||
#include <epicsExport.h>
|
||||
epicsExportRegistrar(pySetupReg);
|
@ -1,136 +0,0 @@
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <dbCommon.h>
|
||||
#include <dbStaticLib.h>
|
||||
#include <dbAccess.h>
|
||||
#include <initHooks.h>
|
||||
#include <iocsh.h>
|
||||
#include <iocInit.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
static
|
||||
PyObject *py_iocsh(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
int ret;
|
||||
static char* names[] = {"script", "cmd", NULL};
|
||||
char *script=NULL, *cmd=NULL;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|ss", names, &script, &cmd))
|
||||
return NULL;
|
||||
|
||||
if(!(!script ^ !cmd)) {
|
||||
PyErr_SetString(PyExc_ValueError, "iocsh requires a script file name or command string");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(script)
|
||||
ret = iocsh(script);
|
||||
else
|
||||
ret = iocshCmd(cmd);
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
return PyInt_FromLong(ret);
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_dbReadDatabase(PyObject *unused, PyObject *args, PyObject *kws)
|
||||
{
|
||||
long status;
|
||||
static char* names[] = {"name", "fp", "path", "sub", NULL};
|
||||
char *fname=NULL, *path=NULL, *sub=NULL;
|
||||
int fd=-1;
|
||||
|
||||
if(!PyArg_ParseTupleAndKeywords(args, kws, "|siss", names, &fname, &fd, &path, &sub))
|
||||
return NULL;
|
||||
|
||||
if(!((!fname) ^ (fd<0))) {
|
||||
PyErr_SetString(PyExc_ValueError, "dbReadDatabase requires a file name or descriptor");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
if(fname)
|
||||
status = dbReadDatabase(&pdbbase, fname, path, sub);
|
||||
else {
|
||||
FILE *ff = fdopen(fd, "r");
|
||||
status = dbReadDatabaseFP(&pdbbase, ff, path, sub);
|
||||
fclose(ff);
|
||||
}
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
if(status) {
|
||||
char buf[30];
|
||||
errSymLookup(status, buf, sizeof(buf));
|
||||
PyErr_SetString(PyExc_RuntimeError, buf);
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static
|
||||
PyObject *py_iocInit(PyObject *unused)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
iocInit();
|
||||
} Py_END_ALLOW_THREADS
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static struct PyMethodDef dbbasemethods[] = {
|
||||
{"iocsh", (PyCFunction)py_iocsh, METH_VARARGS|METH_KEYWORDS,
|
||||
"Execute IOC shell script or command"},
|
||||
{"dbReadDatabase", (PyCFunction)py_dbReadDatabase, METH_VARARGS|METH_KEYWORDS,
|
||||
"Load EPICS database file"},
|
||||
{"iocInit", (PyCFunction)py_iocInit, METH_NOARGS,
|
||||
"Initialize IOC"},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
static struct PyModuleDef dbbasemodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_dbbase",
|
||||
NULL,
|
||||
-1,
|
||||
&dbbasemethods
|
||||
};
|
||||
#endif
|
||||
|
||||
/* initialize "magic" builtin module */
|
||||
PyMODINIT_FUNC init_dbbase(void)
|
||||
{
|
||||
PyObject *mod = NULL, *obj = NULL;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
mod = PyModule_Create(&dbbasemodule);
|
||||
#else
|
||||
mod = Py_InitModule("_dbbase", dbbasemethods);
|
||||
#endif
|
||||
if(!mod)
|
||||
goto fail;
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3 || (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION>=7)
|
||||
obj = PyCapsule_New(pdbbase, "pdbbase", NULL);
|
||||
#else
|
||||
obj = PyCObject_FromVoidPtrAndDesc(pdbbase, "pdbbase", NULL);
|
||||
#endif
|
||||
if(!obj)
|
||||
goto fail;
|
||||
PyModule_AddObject(mod, "pdbbase", obj);
|
||||
|
||||
MODINIT_RET(mod);
|
||||
fail:
|
||||
Py_XDECREF(obj);
|
||||
Py_XDECREF(mod);
|
||||
fprintf(stderr, "Failed to initialize builtin _dbbase module!\n");
|
||||
MODINIT_RET(NULL);
|
||||
}
|
@ -19,6 +19,8 @@
|
||||
#include <dbScan.h>
|
||||
#include <cantProceed.h>
|
||||
#include <registryFunction.h>
|
||||
#include <iocshRegisterCommon.h>
|
||||
#include <registryCommon.h>
|
||||
#include <aSubRecord.h>
|
||||
|
||||
#include "pydevsup.h"
|
||||
@ -471,8 +473,28 @@ int canIOScanRecord(dbCommon *prec)
|
||||
return !!priv->scanobj;
|
||||
}
|
||||
|
||||
static
|
||||
const dset* pydsets[] = {
|
||||
&pydevsupComSpec.com,
|
||||
&pydevsupComIn.com,
|
||||
&pydevsupComOut.com,
|
||||
};
|
||||
|
||||
static const char* pydsetnames[] = {
|
||||
"pydevsupComSpec",
|
||||
"pydevsupComIn",
|
||||
"pydevsupComOut",
|
||||
};
|
||||
|
||||
PyObject* pyDBD_setup(PyObject *unused)
|
||||
{
|
||||
registerDevices(pdbbase, NELEMENTS(pydsets), pydsetnames, pydsets);
|
||||
registryFunctionAdd("python_asub", (REGISTRYFUNCTION)&python_asub);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
/* Called with GIL locked */
|
||||
void pyDBD_cleanup(void)
|
||||
PyObject* pyDBD_cleanup(PyObject *unused)
|
||||
{
|
||||
ELLNODE *cur;
|
||||
inshutdown = 1;
|
||||
@ -497,12 +519,5 @@ void pyDBD_cleanup(void)
|
||||
|
||||
free(priv);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#include <epicsExport.h>
|
||||
|
||||
epicsExportAddress(dset, pydevsupComSpec);
|
||||
epicsExportAddress(dset, pydevsupComIn);
|
||||
epicsExportAddress(dset, pydevsupComOut);
|
||||
|
||||
epicsRegisterFunction(python_asub);
|
||||
|
@ -192,10 +192,6 @@ static PyObject* pyField_getval(pyField *self)
|
||||
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 */
|
||||
@ -388,8 +384,8 @@ static PyObject *pyField_setlen(pyField *self, PyObject *args)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(len<1 || len > self->addr.no_elements) {
|
||||
PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [1,%lu)",
|
||||
if(len > self->addr.no_elements) {
|
||||
PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [0,%lu)",
|
||||
(long)len, (unsigned long)self->addr.no_elements);
|
||||
return NULL;
|
||||
}
|
||||
@ -449,25 +445,28 @@ static PyObject *pyField_len(pyField *self)
|
||||
|
||||
static PyMethodDef pyField_methods[] = {
|
||||
{"name", (PyCFunction)pyField_name, METH_NOARGS,
|
||||
"Return Names (\"record\",\"field\")"},
|
||||
"name() -> (recname, fldname)\n"},
|
||||
{"fieldinfo", (PyCFunction)pyField_fldinfo, METH_NOARGS,
|
||||
"Field type info\nReturn (type, size, #elements"},
|
||||
"fieldinfo() -> (dbf, elem_size, elem_count"},
|
||||
{"getval", (PyCFunction)pyField_getval, METH_NOARGS,
|
||||
"Returns scalar version of field value"},
|
||||
"getval() -> object\n"},
|
||||
{"putval", (PyCFunction)pyField_putval, METH_VARARGS,
|
||||
"Sets field value from a scalar"},
|
||||
"putval(object)\n"},
|
||||
{"getarray", (PyCFunction)pyField_getarray, METH_NOARGS,
|
||||
"getarray() -> numpy.ndarray\n"
|
||||
"Return a numpy ndarray refering to this field for in-place operations."},
|
||||
{"getarraylen", (PyCFunction)pyField_getlen, METH_NOARGS,
|
||||
"getarraylen() -> int\n"
|
||||
"Return current number of valid elements for array fields."},
|
||||
{"putarraylen", (PyCFunction)pyField_setlen, METH_VARARGS,
|
||||
"putarraylen(int)\n"
|
||||
"Set number of valid elements for array fields."},
|
||||
{"getTime", (PyCFunction)pyField_getTime, METH_NOARGS,
|
||||
"Return link target timestamp as a tuple (sec, nsec)."},
|
||||
"getTime() -> (sec, nsec)."},
|
||||
{"getAlarm", (PyCFunction)pyField_getAlarm, METH_NOARGS,
|
||||
"Return link target alarm condtions as a tuple (severity, status)."},
|
||||
"getAlarm() -> (severity, status)."},
|
||||
{"__len__", (PyCFunction)pyField_len, METH_NOARGS,
|
||||
"Maximum number of elements storable in this field"},
|
||||
"Maximum number of elements storable in this field."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
@ -569,12 +568,3 @@ int pyField_prepare(PyObject *module)
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pyField_cleanup(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for(i=0; i<=DBF_MENU; i++) {
|
||||
Py_XDECREF(dbf2np[i]);
|
||||
dbf2np[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
@ -293,23 +293,32 @@ static PyObject *pyRecord_exit(pyRecord *self, PyObject *args)
|
||||
|
||||
static PyMethodDef pyRecord_methods[] = {
|
||||
{"name", (PyCFunction)pyRecord_name, METH_NOARGS,
|
||||
"Return record name string"},
|
||||
"name() -> str\n\n"
|
||||
"Record name. ::\n"
|
||||
"\n"
|
||||
" R = getRecord(\"my:record:name\")\n"
|
||||
" assert R.name()==\"my:record:name\"\n"},
|
||||
{"rtype", (PyCFunction)pyRecord_rtype, METH_NOARGS,
|
||||
"rtype() -> str\n"
|
||||
"Return record type name string"},
|
||||
{"isPyRecord", (PyCFunction)pyRecord_ispyrec, METH_NOARGS,
|
||||
"isPyRecord() -> bool\n"
|
||||
"Is this record using Python Device."},
|
||||
{"info", (PyCFunction)pyRecord_info, METH_VARARGS,
|
||||
"Lookup info name\ninfo(name, def=None)"},
|
||||
"info(key [,default]) -> str\n"
|
||||
"Lookup info by name\n"
|
||||
":rtype: str\n"
|
||||
":throws: KeyError\n"},
|
||||
{"infos", (PyCFunction)pyRecord_infos, METH_NOARGS,
|
||||
"infos() -> {'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"
|
||||
"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!"},
|
||||
{"scan", (PyCFunction)pyRecord_scan, METH_VARARGS|METH_KEYWORDS,
|
||||
"scan(sync=False)\nScan this record. If sync is False then"
|
||||
"a scan request is queued. If sync is True then the record"
|
||||
"is scannined immidately on the current thread."},
|
||||
"scan(sync=False, reason=None, force=0)\n"},
|
||||
{"asyncStart", (PyCFunction)pyRecord_asyncStart, METH_NOARGS,
|
||||
"Begin an asynchronous action. Record must be locked!"},
|
||||
{"asyncFinish", (PyCFunction)pyRecord_asyncFinish, METH_VARARGS|METH_KEYWORDS,
|
||||
|
@ -1,28 +1,86 @@
|
||||
try:
|
||||
import _dbapi
|
||||
HAVE_DBAPI = True
|
||||
except ImportError:
|
||||
import devsup._nullapi as _dbapi
|
||||
HAVE_DBAPI = False
|
||||
import os
|
||||
import atexit
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from _dbconstants import *
|
||||
except ImportError:
|
||||
EPICS_VERSION_STRING = "EPICS 0.0.0.0-0"
|
||||
EPICS_DEV_SNAPSHOT = ""
|
||||
EPICS_SITE_VERSION = "0"
|
||||
EPICS_VERSION = 0
|
||||
EPICS_REVISION = 0
|
||||
EPICS_MODIFICATION = 0
|
||||
EPICS_PATCH_LEVEL = 0
|
||||
from . import _dbapi
|
||||
|
||||
XEPICS_ARCH = "nullos-nullarch"
|
||||
XPYDEV_BASE = "invaliddir"
|
||||
XEPICS_BASE = "invaliddir"
|
||||
|
||||
epicsver = (0,0,0,0,"0","")
|
||||
pydevver = (0,0)
|
||||
|
||||
INVALID_ALARM = UDF_ALARM = 0
|
||||
from ._dbapi import (EPICS_VERSION_STRING,
|
||||
EPICS_DEV_SNAPSHOT,
|
||||
EPICS_SITE_VERSION,
|
||||
EPICS_VERSION,
|
||||
EPICS_REVISION,
|
||||
EPICS_MODIFICATION,
|
||||
EPICS_PATCH_LEVEL,
|
||||
XEPICS_ARCH,
|
||||
XPYDEV_BASE,
|
||||
XEPICS_BASE,
|
||||
epicsver,
|
||||
pydevver,
|
||||
NO_ALARM,
|
||||
MINOR_ALARM,
|
||||
MAJOR_ALARM,
|
||||
READ_ALARM,
|
||||
WRITE_ALARM,
|
||||
HIHI_ALARM,
|
||||
HIGH_ALARM,
|
||||
LOLO_ALARM,
|
||||
LOW_ALARM,
|
||||
STATE_ALARM,
|
||||
COS_ALARM,
|
||||
COMM_ALARM,
|
||||
TIMEOUT_ALARM,
|
||||
HW_LIMIT_ALARM,
|
||||
CALC_ALARM,
|
||||
SCAN_ALARM,
|
||||
LINK_ALARM,
|
||||
SOFT_ALARM,
|
||||
BAD_SUB_ALARM,
|
||||
UDF_ALARM,
|
||||
DISABLE_ALARM,
|
||||
SIMM_ALARM,
|
||||
READ_ACCESS_ALARM,
|
||||
WRITE_ACCESS_ALARM,
|
||||
INVALID_ALARM,
|
||||
)
|
||||
|
||||
__all__ = []
|
||||
|
||||
def _init(iocMain=False):
|
||||
if not iocMain:
|
||||
# we haven't read/register base.dbd
|
||||
_dbapi.dbReadDatabase(os.path.join(XEPICS_BASE, "dbd", "base.dbd"),
|
||||
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)
|
||||
_dbapi._dbd_setup()
|
||||
|
||||
def _fini(iocMain=False):
|
||||
if iocMain:
|
||||
_dbapi.initHookAnnounce(9999) # our magic/fake AtExit hook
|
||||
_dbapi._dbd_cleanup()
|
||||
|
@ -1,205 +0,0 @@
|
||||
|
||||
class _Record(object):
|
||||
"""Handle for record operations
|
||||
|
||||
r = _Record("rec:name")
|
||||
"""
|
||||
|
||||
def __init__(self, rec):
|
||||
pass
|
||||
def name(self):
|
||||
"""Record name string.
|
||||
|
||||
>>> R = getRecord("my:record:name")
|
||||
>>> R.name()
|
||||
"my:record:name"
|
||||
"""
|
||||
def rtype(self):
|
||||
"""Record type name string.
|
||||
|
||||
>>> R = getRecord("my:record:name")
|
||||
>>> R.type()
|
||||
"longin"
|
||||
"""
|
||||
def isPyRecord(self):
|
||||
"""Is this record using Python device support.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
def info(self, key):
|
||||
"""info(key [,default])
|
||||
|
||||
:rtype: str
|
||||
:throws: KeyError
|
||||
|
||||
Lookup record info tag. If no default
|
||||
is provided then an exception is raised
|
||||
if the info key does not exist.
|
||||
"""
|
||||
def infos(self):
|
||||
"""Return a dictionary of all info tags
|
||||
for this record
|
||||
"""
|
||||
|
||||
def setSevr(self, sevr=3, stat=15):
|
||||
"""setSevr(sevr=INVALID_ALARM, stat=COMM_ALARM)
|
||||
|
||||
Signal a new alarm condition. The effect of this
|
||||
call depends on the current alarm condition.
|
||||
|
||||
See :c:func:`recGblSetSevr` in EPICS Base.
|
||||
"""
|
||||
|
||||
def scan(self, sync=False, reason=None, force=0):
|
||||
"""Scan this record.
|
||||
|
||||
:param sync: scan in current thread (``True``), or queue to a worker (``False``).
|
||||
:param reason: Reason object passed to :meth:`process <DeviceSupport.process>` (sync=True only)
|
||||
:param force: Record processing condtion (0=Passive, 1=Force, 2=I/O Intr)
|
||||
:throws: ``RuntimeError`` when ``sync=True``, but ``force`` prevents scanning.
|
||||
|
||||
If ``sync`` is False then a scan request is queued to run in another thread..
|
||||
If ``sync`` is True then the record is scanned immediately on the current thread.
|
||||
|
||||
For ``reason`` argument must be used in conjunction with ``sync=True``
|
||||
on records with Python device support. This provides a means
|
||||
of providing extra contextual information to the record's
|
||||
:meth:`process <DeviceSupport.process>` method.
|
||||
|
||||
``force`` is used to decide if the record will actually be processed,
|
||||
``force=0`` will only process records with SCAN=Passive.
|
||||
``force=1`` will process any record if at all possible.
|
||||
``force=2`` will only process records with Python device support and
|
||||
SCAN=I/O Intr.
|
||||
|
||||
.. important::
|
||||
It is **never** safe to use ``sync=True`` while holding record locks,
|
||||
including from within a *process* method.
|
||||
"""
|
||||
|
||||
def asyncStart(self):
|
||||
"""Start asynchronous processing
|
||||
|
||||
This method may be called from a device support
|
||||
:meth:`process <DeviceSupport.process>` method
|
||||
to indicate that processing will continue
|
||||
later.
|
||||
|
||||
.. important::
|
||||
This method is **only** safe to call within a *process* method.
|
||||
"""
|
||||
def asyncFinish(self, reason=None):
|
||||
"""Indicate that asynchronous processing can complete
|
||||
|
||||
Similar to :meth:`scan`. Used to conclude asynchronous
|
||||
process started with :meth:`asyncStart`.
|
||||
|
||||
Processing is completed on the current thread.
|
||||
|
||||
.. important::
|
||||
This method should **never** be called within
|
||||
a :meth:`process <DeviceSupport.process>` method,
|
||||
or any other context where a Record lock is held.
|
||||
Doing so will result in a deadlock.
|
||||
|
||||
Typically a *reason* will be passed to *process* as a way
|
||||
of indicating that this is the completion of an async action. ::
|
||||
|
||||
AsyncDone = object()
|
||||
class MySup(object):
|
||||
def process(record, reason):
|
||||
if reason is AsyncDone:
|
||||
record.VAL = ... # store result
|
||||
else:
|
||||
threading.Timer(1.0, record.asyncFinish, kwargs={'reason':AsyncDone})
|
||||
record.asyncStart()
|
||||
"""
|
||||
|
||||
class _Field(object):
|
||||
"""Handle for field operations
|
||||
|
||||
f = Field("rec:name.HOPR")
|
||||
|
||||
Field objects implement the buffer protocol.
|
||||
"""
|
||||
def __init__(self, fld):
|
||||
pass
|
||||
def name(self):
|
||||
"""Fetch the record and field names.
|
||||
|
||||
>>> FLD = getRecord("rec").field("FLD")
|
||||
>>> FLD.name()
|
||||
("rec", "FLD")
|
||||
"""
|
||||
def fieldinfo(self):
|
||||
"""(type, size, #elements) = fieldinfo()
|
||||
|
||||
Type is DBF type code
|
||||
size is number of bytes to start a single element
|
||||
#elements is the maximum number of elements the field can hold
|
||||
"""
|
||||
|
||||
def getval(self):
|
||||
"""Fetch the current field value as a scalar or numpy.ndarray.
|
||||
|
||||
:rtype: int, float, str, or ndarray
|
||||
|
||||
Returned type depends of field DBF type.
|
||||
An ``int`` is returned for CHAR, SHORT, LONG, and ENUM.
|
||||
A ``float`` is returned for FLOAT and DOUBLE.
|
||||
A ``str`` is returned for STRING.
|
||||
A ``numpy.ndarray`` is returned for array fields.
|
||||
This array is read-only and has the size of the present valid values.
|
||||
|
||||
.. important::
|
||||
It is only safe to read this ndarray while the record
|
||||
lock is held (ie within :meth:`process <DeviceSupport.process>`).
|
||||
"""
|
||||
|
||||
def putval(self, val):
|
||||
"""Update the field value
|
||||
|
||||
Must be an Int, Float, str, or numpy.ndarray.
|
||||
Strings will be truncated to 39 characters.
|
||||
Arrays must have a size less than or equal to the max element count.
|
||||
Arrays are converted as necessary to the field's native type.
|
||||
"""
|
||||
|
||||
def getarray(self):
|
||||
"""Return a numpy ndarray refering to this field for in-place operations.
|
||||
|
||||
The dtype of the ndarray will correspond to the field's DBF type.
|
||||
Its size will be the **maximum** number of elements.
|
||||
|
||||
.. important::
|
||||
It is only safe to read or write to this ndarray while the record
|
||||
lock is held (ie within :meth:`process <DeviceSupport.process>`).
|
||||
"""
|
||||
|
||||
def getarraylen(self):
|
||||
"""Return the number of active elements for the field.
|
||||
|
||||
>>> F = Field(...)
|
||||
>>> assert len(F)>=F.getarraylen()
|
||||
"""
|
||||
|
||||
def putarraylen(self, len):
|
||||
"""Set the number of active elements in field's array.
|
||||
|
||||
Requires that the underlying field be an array.
|
||||
Must be greater than one and less than or equal to the maximum length of the field.
|
||||
"""
|
||||
|
||||
def getAlarm(self):
|
||||
"""Returns a tuple (severity, status) with the condition of the linked field.
|
||||
|
||||
Only works for fields of type DBF_INLINK.
|
||||
"""
|
||||
|
||||
def __len__(self):
|
||||
"""Returns the maximum number of elements which may be stored in the field.
|
||||
|
||||
This is always 1 for scalar fields.
|
||||
"""
|
||||
|
||||
_hooks = {}
|
@ -3,10 +3,7 @@ import threading, sys, traceback, time
|
||||
|
||||
from devsup.util import Worker, importmod
|
||||
|
||||
try:
|
||||
import _dbapi
|
||||
except ImportError:
|
||||
import _nullapi as _dbapi
|
||||
from . import _dbapi
|
||||
|
||||
_rec_cache = {}
|
||||
_no_such_field = object()
|
||||
@ -263,6 +260,76 @@ class Record(_dbapi._Record):
|
||||
|
||||
super(Record, self).setTime(sec, nsec)
|
||||
|
||||
def scan(self, *args, **kws):
|
||||
"""scan(sync=False, reason=None, force=0)
|
||||
Scan this record.
|
||||
|
||||
:param sync: scan in current thread (``True``), or queue to a worker (``False``).
|
||||
:param reason: Reason object passed to :meth:`process <DeviceSupport.process>` (sync=True only)
|
||||
:param force: Record processing condtion (0=Passive, 1=Force, 2=I/O Intr)
|
||||
:throws: ``RuntimeError`` when ``sync=True``, but ``force`` prevents scanning.
|
||||
|
||||
If ``sync`` is False then a scan request is queued to run in another thread..
|
||||
If ``sync`` is True then the record is scanned immediately on the current thread.
|
||||
|
||||
For ``reason`` argument must be used in conjunction with ``sync=True``
|
||||
on records with Python device support. This provides a means
|
||||
of providing extra contextual information to the record's
|
||||
:meth:`process <DeviceSupport.process>` method.
|
||||
|
||||
``force`` is used to decide if the record will actually be processed,
|
||||
``force=0`` will only process records with SCAN=Passive.
|
||||
``force=1`` will process any record if at all possible.
|
||||
``force=2`` will only process records with Python device support and
|
||||
SCAN=I/O Intr.
|
||||
|
||||
.. important::
|
||||
It is **never** safe to use ``sync=True`` while holding record locks,
|
||||
including from within a *process* method.
|
||||
"""
|
||||
return _dbapi._Record.scan(self, *args, **kws)
|
||||
|
||||
def asyncStart(self):
|
||||
"""Start asynchronous processing
|
||||
|
||||
This method may be called from a device support
|
||||
:meth:`process <DeviceSupport.process>` method
|
||||
to indicate that processing will continue
|
||||
later.
|
||||
|
||||
.. important::
|
||||
This method is **only** safe to call within a *process* method.
|
||||
"""
|
||||
return _dbapi._Record.asyncStart(self)
|
||||
|
||||
def asyncFinish(self, reason=None):
|
||||
"""Indicate that asynchronous processing can complete
|
||||
|
||||
Similar to :meth:`scan`. Used to conclude asynchronous
|
||||
process started with :meth:`asyncStart`.
|
||||
|
||||
Processing is completed on the current thread.
|
||||
|
||||
.. important::
|
||||
This method should **never** be called within
|
||||
a :meth:`process <DeviceSupport.process>` method,
|
||||
or any other context where a Record lock is held.
|
||||
Doing so will result in a deadlock.
|
||||
|
||||
Typically a *reason* will be passed to *process* as a way
|
||||
of indicating that this is the completion of an async action. ::
|
||||
|
||||
AsyncDone = object()
|
||||
class MySup(object):
|
||||
def process(record, reason):
|
||||
if reason is AsyncDone:
|
||||
record.VAL = ... # store result
|
||||
else:
|
||||
threading.Timer(1.0, record.asyncFinish, kwargs={'reason':AsyncDone})
|
||||
record.asyncStart()
|
||||
"""
|
||||
return _dbapi._Record.asyncFinish(self, reason=reason)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
F = self.field(name)
|
||||
@ -341,5 +408,8 @@ def processLink(name, lstr):
|
||||
modname, args = parts[0], parts[1] if len(parts)>1 else None
|
||||
else:
|
||||
args = lstr
|
||||
modname, _sep, attr = modname.partition('|')
|
||||
mod = importmod(modname)
|
||||
if attr:
|
||||
mod = getattr(mod, attr)
|
||||
return rec, mod.build(rec, args)
|
||||
|
@ -4,10 +4,7 @@ import traceback
|
||||
from functools import wraps
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import _dbapi
|
||||
except ImportError:
|
||||
import devsup._nullapi as _dbapi
|
||||
from . import _dbapi
|
||||
|
||||
__all__ = [
|
||||
"hooknames",
|
||||
@ -45,7 +42,7 @@ def initHook(state):
|
||||
|
||||
@initHook("AfterIocRunning")
|
||||
def myfn():
|
||||
# do stuff
|
||||
pass
|
||||
"""
|
||||
def _add(fn):
|
||||
addHook(state, fn)
|
||||
|
@ -7,8 +7,8 @@ import threading, inspect
|
||||
|
||||
_tables = {}
|
||||
|
||||
from devsup.db import IOScanListThread
|
||||
from devsup import INVALID_ALARM, UDF_ALARM
|
||||
from .db import IOScanListThread
|
||||
from . import INVALID_ALARM, UDF_ALARM
|
||||
|
||||
__all__ = [
|
||||
'Parameter',
|
||||
|
0
devsupApp/src/devsup/test/__init__.py
Normal file
0
devsupApp/src/devsup/test/__init__.py
Normal file
165
devsupApp/src/devsup/test/test_db.py
Normal file
165
devsupApp/src/devsup/test/test_db.py
Normal file
@ -0,0 +1,165 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
import numpy
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal
|
||||
|
||||
from ..db import getRecord
|
||||
from .. import _dbapi
|
||||
from .. import _init
|
||||
|
||||
from .util import IOCHelper
|
||||
|
||||
# short-circuit warning from base_registerRecordDeviceDriver()
|
||||
os.environ['TOP'] = _dbapi.XPYDEV_BASE # external code use devsup.XPYDEV_BASE
|
||||
|
||||
class TestScan(IOCHelper):
|
||||
db = """
|
||||
record(longout, src) {
|
||||
field(OUT, "tgt PP")
|
||||
}
|
||||
record(longin, "tgt") {}
|
||||
"""
|
||||
autostart = True
|
||||
|
||||
def test_link(self):
|
||||
src, tgt = getRecord('src'), getRecord('tgt')
|
||||
|
||||
with src:
|
||||
src.VAL = 42
|
||||
self.assertEqual(src.VAL, 42)
|
||||
|
||||
with tgt:
|
||||
self.assertEqual(tgt.VAL, 0)
|
||||
|
||||
src.scan(sync=True) # lock and dbProcess() on this thread
|
||||
|
||||
with tgt:
|
||||
self.assertEqual(tgt.VAL, 42)
|
||||
|
||||
class TestField(IOCHelper):
|
||||
db = """
|
||||
record(ai, "rec:ai") {
|
||||
field(VAL , "4.2")
|
||||
field(RVAL, "42")
|
||||
}
|
||||
record(stringin, "rec:si") {
|
||||
field(VAL, "")
|
||||
}
|
||||
record(waveform, "rec:wf:a") {
|
||||
field(FTVL, "DOUBLE")
|
||||
field(NELM, "10")
|
||||
}
|
||||
record(waveform, "rec:wf:s") {
|
||||
field(FTVL, "STRING")
|
||||
field(NELM, "10")
|
||||
}
|
||||
"""
|
||||
|
||||
def test_ai(self):
|
||||
rec = getRecord("rec:ai")
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, 4.2)
|
||||
self.assertEqual(rec.RVAL, 42)
|
||||
rec.VAL = 5.2
|
||||
rec.RVAL = 52
|
||||
self.assertEqual(rec.VAL, 5.2)
|
||||
self.assertEqual(rec.RVAL, 52)
|
||||
|
||||
rec.VAL += 1.0
|
||||
self.assertEqual(rec.VAL, 6.2)
|
||||
|
||||
def test_si(self):
|
||||
rec = getRecord("rec:si")
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, "")
|
||||
|
||||
rec.VAL = "test"
|
||||
self.assertEqual(rec.VAL, "test")
|
||||
|
||||
rec.VAL = ""
|
||||
self.assertEqual(rec.VAL, "")
|
||||
|
||||
# implicitly truncates
|
||||
rec.VAL = "This is a really long string which should be truncated"
|
||||
self.assertEqual(rec.VAL, "This is a really long string which shou")
|
||||
|
||||
# TODO: test unicode
|
||||
|
||||
def test_wf_float(self):
|
||||
rec = getRecord("rec:wf:a")
|
||||
|
||||
with rec:
|
||||
assert_array_almost_equal(rec.VAL, [])
|
||||
|
||||
rec.VAL = numpy.arange(5)
|
||||
assert_array_almost_equal(rec.VAL, numpy.arange(5))
|
||||
|
||||
rec.VAL = numpy.arange(10)
|
||||
assert_array_almost_equal(rec.VAL, numpy.arange(10))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
rec.VAL = numpy.arange(15)
|
||||
|
||||
rec.VAL = []
|
||||
assert_array_almost_equal(rec.VAL, [])
|
||||
|
||||
# in-place modification
|
||||
fld = rec.field('VAL')
|
||||
fld.putarraylen(5)
|
||||
arr = fld.getarray()
|
||||
self.assertEqual(arr.shape, (10,)) # size of NELM
|
||||
arr[:5] = numpy.arange(5) # we only fill in the part in use
|
||||
arr[2] = 42
|
||||
|
||||
assert_array_almost_equal(rec.VAL, [0, 1, 42, 3, 4])
|
||||
|
||||
def test_wf_string(self):
|
||||
rec = getRecord("rec:wf:s")
|
||||
|
||||
with rec:
|
||||
assert_array_equal(rec.VAL, numpy.asarray([], dtype='S40'))
|
||||
|
||||
rec.VAL = ["zero", "", "one", "This is a really long string which should be truncated", "", "last"]
|
||||
|
||||
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") {
|
||||
field(DTYP, "Python Device")
|
||||
field(INP , "@devsup.test.test_db|TestDset foo bar")
|
||||
}
|
||||
"""
|
||||
|
||||
class Increment(object):
|
||||
def process(self, rec, reason):
|
||||
rec.VAL += 1
|
||||
def detach(self, rec):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def build(klass, rec, args):
|
||||
if rec.name()=='rec:li':
|
||||
return klass.Increment()
|
||||
else:
|
||||
raise RuntimeError("Unsupported")
|
||||
|
||||
def test_increment(self):
|
||||
rec = getRecord('rec:li')
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, 0)
|
||||
self.assertEqual(rec.UDF, 1)
|
||||
|
||||
rec.scan(sync=True)
|
||||
|
||||
with rec:
|
||||
self.assertEqual(rec.VAL, 1)
|
||||
self.assertEqual(rec.UDF, 0)
|
74
devsupApp/src/devsup/test/util.py
Normal file
74
devsupApp/src/devsup/test/util.py
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import tempfile
|
||||
|
||||
import numpy
|
||||
from numpy.testing import assert_array_almost_equal, assert_array_equal
|
||||
|
||||
from ..db import getRecord
|
||||
from .. import _dbapi
|
||||
from .. import _init
|
||||
|
||||
__all__ = (
|
||||
'IOCHelper',
|
||||
)
|
||||
|
||||
class IOCHelper(unittest.TestCase):
|
||||
"""Test case run in an IOC. ::
|
||||
|
||||
from devsup.db import getRecord
|
||||
from devsup.test.util impmort IOCHelper
|
||||
class TestScan(IOCHelper): # sub-class of unittest.TestCase
|
||||
db = \"\"\"
|
||||
record(longout, foo) {}
|
||||
\"\"\"
|
||||
autostart = True
|
||||
|
||||
def test_link(self):
|
||||
rec = getRecord('foo')
|
||||
with rec: # dbScanLock()
|
||||
self.assertEqual(rec.VAL, 0)
|
||||
"""
|
||||
# DB definition to be used. May include eg. 'record(ai, "blah") {}'
|
||||
db = None
|
||||
# Whether to automatically run iocInit() before test methods
|
||||
# whether iocInit() has been called
|
||||
autostart = True
|
||||
running = False
|
||||
|
||||
def setUp(self):
|
||||
print("testdbPrepare()")
|
||||
_dbapi._UTest.testdbPrepare()
|
||||
_init(iocMain=False) # load base.dbd
|
||||
|
||||
if self.db is not None:
|
||||
with tempfile.NamedTemporaryFile() as F:
|
||||
F.write(self.db.encode('ascii'))
|
||||
F.flush()
|
||||
_dbapi.dbReadDatabase(F.name)
|
||||
|
||||
if self.autostart:
|
||||
self.iocInit()
|
||||
|
||||
def tearDown(self):
|
||||
self.iocShutdown();
|
||||
print("testdbCleanup()")
|
||||
_dbapi.initHookAnnounce(9999) # our magic/fake AtExit hook
|
||||
_dbapi._UTest.testdbCleanup()
|
||||
|
||||
def iocInit(self):
|
||||
"""If not autostart, then this must be called before runtime database access is possible
|
||||
"""
|
||||
if not self.running:
|
||||
print("testIocInitOk")
|
||||
_dbapi._UTest.testIocInitOk()
|
||||
self.running = True
|
||||
|
||||
def iocShutdown(self):
|
||||
"""Call to stop IOC scanning processes. Happens automatically during test tearDown
|
||||
"""
|
||||
if self.running:
|
||||
print("testIocShutdownOk")
|
||||
_dbapi._UTest.testIocShutdownOk()
|
||||
self.running = False
|
@ -1,217 +0,0 @@
|
||||
/*************************************************************************\
|
||||
* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
|
||||
* National Laboratory.
|
||||
* Copyright (c) 2003 The Regents of the University of California, as
|
||||
* Operator of Los Alamos National Laboratory.
|
||||
* EPICS BASE is distributed subject to the Software License Agreement
|
||||
* found in the file LICENSE that is included with this distribution.
|
||||
\*************************************************************************/
|
||||
|
||||
/* $Revision-Id$ */
|
||||
|
||||
/* Author: Andrew Johnson Date: 2003-04-08 */
|
||||
/* adapted for pyDevSup */
|
||||
|
||||
/* Usage:
|
||||
* softIoc [-D softIoc.dbd] [-h] [-S] [-s] [-a ascf]
|
||||
* [-m macro=value,macro2=value2] [-d file.db]
|
||||
* [-x prefix] [st.cmd]
|
||||
*
|
||||
* If used the -D option must come first, and specify the
|
||||
* path to the softIoc.dbd file. The compile-time install
|
||||
* location is saved in the binary as a default.
|
||||
*
|
||||
* Usage information will be printed if -h is given, then
|
||||
* the program will exit normally.
|
||||
*
|
||||
* The -S option prevents an interactive shell being started
|
||||
* after all arguments have been processed.
|
||||
*
|
||||
* Previous versions accepted a -s option to cause a shell
|
||||
* to be started; this option is still accepted but ignored
|
||||
* since a command shell is now started by default.
|
||||
*
|
||||
* Access Security can be enabled with the -a option giving
|
||||
* the name of the configuration file; if any macros were
|
||||
* set with -m before the -a option was given, they will be
|
||||
* used as access security substitution macros.
|
||||
*
|
||||
* Any number of -m and -d arguments can be interspersed;
|
||||
* the macros are applied to the following .db files. Each
|
||||
* later -m option causes earlier macros to be discarded.
|
||||
*
|
||||
* The -x option loads the softIocExit.db with the macro
|
||||
* IOC set to the string provided. This database contains
|
||||
* a subroutine record named $(IOC):exit which has its field
|
||||
* SNAM set to "exit". When this record is processed, the
|
||||
* subroutine that runs will call epicsExit() with the value
|
||||
* of the field A determining whether the exit status is
|
||||
* EXIT_SUCCESS if (A == 0.0) or EXIT_FAILURE (A != 0.0).
|
||||
*
|
||||
* A st.cmd file is optional. If any databases were loaded
|
||||
* the st.cmd file will be run *after* iocInit. To perform
|
||||
* iocsh commands before iocInit, all database loading must
|
||||
* be performed by the script itself, or by the user from
|
||||
* the interactive IOC shell.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "registryFunction.h"
|
||||
#include "epicsThread.h"
|
||||
#include "epicsExit.h"
|
||||
#include "epicsStdio.h"
|
||||
#include "dbStaticLib.h"
|
||||
#include "subRecord.h"
|
||||
#include "dbAccess.h"
|
||||
#include "asDbLib.h"
|
||||
#include "iocInit.h"
|
||||
#include "iocsh.h"
|
||||
|
||||
extern "C" int softIocPy_registerRecordDeviceDriver(struct dbBase *pdbbase);
|
||||
|
||||
#define DBD_FILE XPYDEV_BASE "/dbd/softIocPy.dbd"
|
||||
|
||||
const char *arg0;
|
||||
const char *base_dbd = DBD_FILE;
|
||||
|
||||
|
||||
static void usage(int status) {
|
||||
printf("Usage: %s [-D softIocPy.dbd] [-h] [-S] [-a ascf]\n", arg0);
|
||||
puts("\t[-m macro=value,macro2=value2] [-d file.db]");
|
||||
puts("\t[-x prefix] [st.cmd]");
|
||||
puts("Compiled-in path to softIocPy.dbd is:");
|
||||
printf("\t%s\n", base_dbd);
|
||||
epicsExit(status);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
char *dbd_file = const_cast<char*>(base_dbd);
|
||||
char *macros = NULL;
|
||||
int startIocsh = 1; /* default = start shell */
|
||||
int loadedDb = 0;
|
||||
|
||||
arg0 = strrchr(*argv, '/');
|
||||
if (!arg0) {
|
||||
arg0 = *argv;
|
||||
} else {
|
||||
++arg0; /* skip the '/' */
|
||||
}
|
||||
|
||||
--argc, ++argv;
|
||||
|
||||
/* Do this here in case the dbd file not available */
|
||||
if (argc>0 && **argv=='-' && (*argv)[1]=='h') {
|
||||
usage(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
if (argc>1 && **argv=='-' && (*argv)[1]=='D') {
|
||||
dbd_file = *++argv;
|
||||
argc -= 2;
|
||||
++argv;
|
||||
}
|
||||
|
||||
if (dbLoadDatabase(dbd_file, NULL, NULL)) {
|
||||
epicsExit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
softIocPy_registerRecordDeviceDriver(pdbbase);
|
||||
|
||||
while (argc>1 && **argv == '-') {
|
||||
switch ((*argv)[1]) {
|
||||
case 'a':
|
||||
if (macros) asSetSubstitutions(macros);
|
||||
asSetFilename(*++argv);
|
||||
--argc;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
if (dbLoadRecords(*++argv, macros)) {
|
||||
epicsExit(EXIT_FAILURE);
|
||||
}
|
||||
loadedDb = 1;
|
||||
--argc;
|
||||
break;
|
||||
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
|
||||
case 'm':
|
||||
macros = *++argv;
|
||||
--argc;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
startIocsh = 0;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("%s: option '%s' not recognized\n", arg0, *argv);
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
--argc;
|
||||
++argv;
|
||||
}
|
||||
|
||||
if (argc>0 && **argv=='-') {
|
||||
switch((*argv)[1]) {
|
||||
case 'a':
|
||||
case 'd':
|
||||
case 'm':
|
||||
printf("%s: missing argument to option '%s'\n", arg0, *argv);
|
||||
usage(EXIT_FAILURE);
|
||||
|
||||
case 'h':
|
||||
usage(EXIT_SUCCESS);
|
||||
|
||||
case 'S':
|
||||
startIocsh = 0;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
break;
|
||||
|
||||
default:
|
||||
printf("%s: option '%s' not recognized\n", arg0, *argv);
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
--argc;
|
||||
++argv;
|
||||
}
|
||||
|
||||
if (loadedDb) {
|
||||
iocInit();
|
||||
epicsThreadSleep(0.2);
|
||||
}
|
||||
|
||||
/* run user's startup script */
|
||||
if (argc>0) {
|
||||
if (iocsh(*argv)) epicsExit(EXIT_FAILURE);
|
||||
epicsThreadSleep(0.2);
|
||||
loadedDb = 1; /* Give it the benefit of the doubt... */
|
||||
}
|
||||
|
||||
/* start an interactive shell if it was requested */
|
||||
if (startIocsh) {
|
||||
iocsh(NULL);
|
||||
} else {
|
||||
if (loadedDb) {
|
||||
epicsThreadExitMain();
|
||||
} else {
|
||||
printf("%s: Nothing to do!\n", arg0);
|
||||
usage(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
epicsExit(EXIT_SUCCESS);
|
||||
/*Note that the following statement will never be executed*/
|
||||
return 0;
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
registrar(pySetupReg)
|
||||
|
||||
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")
|
||||
|
||||
function(python_asub)
|
||||
|
@ -15,17 +15,19 @@
|
||||
|
||||
#endif
|
||||
|
||||
PyMODINIT_FUNC init_dbbase(void);
|
||||
struct dbCommon;
|
||||
|
||||
void pyDBD_cleanup(void);
|
||||
PyObject* pyDBD_setup(PyObject *unused);
|
||||
PyObject* pyDBD_cleanup(PyObject *unused);
|
||||
|
||||
int pyUTest_prepare(PyObject *module);
|
||||
|
||||
int pyField_prepare(PyObject *module);
|
||||
void pyField_cleanup(void);
|
||||
|
||||
int pyRecord_prepare(PyObject *module);
|
||||
|
||||
int isPyRecord(dbCommon *);
|
||||
int canIOScanRecord(dbCommon *);
|
||||
int isPyRecord(struct dbCommon *);
|
||||
int canIOScanRecord(struct dbCommon *);
|
||||
|
||||
extern epicsThreadPrivateId pyDevReasonID;
|
||||
|
||||
|
140
devsupApp/src/utest.c
Normal file
140
devsupApp/src/utest.c
Normal file
@ -0,0 +1,140 @@
|
||||
|
||||
/* python has its own ideas about which version to support */
|
||||
#undef _POSIX_C_SOURCE
|
||||
#undef _XOPEN_SOURCE
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#include <epicsVersion.h>
|
||||
#include <dbEvent.h>
|
||||
#include <iocInit.h>
|
||||
#include <errlog.h>
|
||||
|
||||
#if EPICS_VERSION>3 || (EPICS_VERSION==3 && EPICS_REVISION>=15)
|
||||
# include <dbUnitTest.h>
|
||||
# define HAVE_DBTEST
|
||||
#endif
|
||||
|
||||
#include "pydevsup.h"
|
||||
|
||||
#ifdef HAVE_DBTEST
|
||||
static dbEventCtx testEvtCtx;
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
} UTest;
|
||||
|
||||
static PyTypeObject UTest_type = {
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
#else
|
||||
PyObject_HEAD_INIT(NULL)
|
||||
0,
|
||||
#endif
|
||||
"_dbapi._UTest",
|
||||
sizeof(UTest),
|
||||
};
|
||||
|
||||
static PyObject* utest_prepare(PyObject *unused)
|
||||
{
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
//testdbPrepare(); doesn't do anything essential for us as of 7.0.2
|
||||
} Py_END_ALLOW_THREADS
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject* utest_init(PyObject *unused)
|
||||
{
|
||||
#ifdef HAVE_DBTEST
|
||||
int ret;
|
||||
if(testEvtCtx)
|
||||
return PyErr_Format(PyExc_RuntimeError, "Missing testIocShutdownOk()");
|
||||
|
||||
// like, testIocInitOk() without testAbort()
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
eltc(0);
|
||||
ret = iocBuildIsolated() || iocRun();
|
||||
eltc(1);
|
||||
} Py_END_ALLOW_THREADS
|
||||
if(ret) {
|
||||
return PyErr_Format(PyExc_RuntimeError, "iocInit fails with %d", ret);
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
testEvtCtx=db_init_events();
|
||||
} Py_END_ALLOW_THREADS
|
||||
if(!testEvtCtx) {
|
||||
iocShutdown();
|
||||
return PyErr_Format(PyExc_RuntimeError, "iocInit fails create dbEvent context");
|
||||
}
|
||||
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
ret = db_start_events(testEvtCtx, "CAS-test-py", NULL, NULL, epicsThreadPriorityCAServerLow);
|
||||
} Py_END_ALLOW_THREADS
|
||||
if(ret!=DB_EVENT_OK) {
|
||||
db_close_events(testEvtCtx);
|
||||
testEvtCtx = NULL;
|
||||
iocShutdown();
|
||||
return PyErr_Format(PyExc_RuntimeError, "db_start_events fails with %d", ret);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
return PyErr_Format(PyExc_RuntimeError, "Requires Base >=3.15");
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject* utest_shutdown(PyObject *unused)
|
||||
{
|
||||
#ifdef HAVE_DBTEST
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
//testIocShutdownOk();
|
||||
db_close_events(testEvtCtx);
|
||||
testEvtCtx = NULL;
|
||||
iocShutdown();
|
||||
} Py_END_ALLOW_THREADS
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
return PyErr_Format(PyExc_RuntimeError, "Requires Base >=3.15");
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyObject* utest_cleanup(PyObject *unused)
|
||||
{
|
||||
#ifdef HAVE_DBTEST
|
||||
Py_BEGIN_ALLOW_THREADS {
|
||||
testdbCleanup();
|
||||
errlogFlush();
|
||||
} Py_END_ALLOW_THREADS
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
return PyErr_Format(PyExc_RuntimeError, "Requires Base >=3.15");
|
||||
#endif
|
||||
}
|
||||
|
||||
static PyMethodDef UTest_methods[] = {
|
||||
{"testdbPrepare", (PyCFunction)&utest_prepare, METH_STATIC|METH_NOARGS, ""},
|
||||
{"testIocInitOk", (PyCFunction)&utest_init, METH_STATIC|METH_NOARGS, ""},
|
||||
{"testIocShutdownOk", (PyCFunction)&utest_shutdown, METH_STATIC|METH_NOARGS, ""},
|
||||
{"testdbCleanup", (PyCFunction)&utest_cleanup, METH_STATIC|METH_NOARGS, ""},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
int pyUTest_prepare(PyObject *module)
|
||||
{
|
||||
PyObject *typeobj=(PyObject*)&UTest_type;
|
||||
|
||||
UTest_type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE;
|
||||
UTest_type.tp_methods = UTest_methods;
|
||||
|
||||
if(PyType_Ready(&UTest_type)<0)
|
||||
return -1;
|
||||
|
||||
Py_INCREF(typeobj);
|
||||
if(PyModule_AddObject(module, "_UTest", typeobj)) {
|
||||
Py_DECREF(typeobj);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user