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:
15
.travis.yml
15
.travis.yml
@ -14,17 +14,20 @@ install:
|
|||||||
- ./build-deps.sh
|
- ./build-deps.sh
|
||||||
script:
|
script:
|
||||||
- make PYTHON=`which python` -j2
|
- make PYTHON=`which python` -j2
|
||||||
|
- if [ "$TEST" = "YES" ]; then make PYTHON=`which python` -j2 nose; fi
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: BRBASE=3.16 PROF=deb8
|
env: BRBASE=7.0 PROF=deb8 TEST=YES
|
||||||
- python: "2.7"
|
|
||||||
env: BRBASE=3.16 PROF=deb8 CMPLR=clang
|
|
||||||
- python: "3.4"
|
- python: "3.4"
|
||||||
env: BRBASE=3.16 PROF=deb8
|
env: BRBASE=7.0 PROF=deb8 TEST=YES
|
||||||
|
- python: "3.5"
|
||||||
|
env: BRBASE=7.0 PROF=deb9 TEST=YES
|
||||||
- python: "3.6"
|
- python: "3.6"
|
||||||
env: BRBASE=3.16 PROF=deb9
|
env: BRBASE=7.0 PROF=deb9 TEST=YES
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: BRBASE=3.15 PROF=deb8
|
env: BRBASE=3.16 PROF=deb8 TEST=YES
|
||||||
|
- python: "2.7"
|
||||||
|
env: BRBASE=3.15 PROF=deb8 TEST=YES
|
||||||
- python: "2.7"
|
- python: "2.7"
|
||||||
env: BRBASE=3.14 PROF=deb8
|
env: BRBASE=3.14 PROF=deb8
|
||||||
|
18
Makefile
18
Makefile
@ -10,14 +10,24 @@ define DIR_template
|
|||||||
endef
|
endef
|
||||||
$(foreach dir, $(filter-out configure,$(DIRS)),$(eval $(call DIR_template,$(dir))))
|
$(foreach dir, $(filter-out configure,$(DIRS)),$(eval $(call DIR_template,$(dir))))
|
||||||
|
|
||||||
|
pyIocApp_DEPEND_DIRS += devsupApp
|
||||||
|
|
||||||
iocBoot_DEPEND_DIRS += $(filter %App,$(DIRS))
|
iocBoot_DEPEND_DIRS += $(filter %App,$(DIRS))
|
||||||
|
|
||||||
include $(TOP)/configure/RULES_TOP
|
include $(TOP)/configure/RULES_TOP
|
||||||
|
|
||||||
UNINSTALL_DIRS += $(wildcard $(INSTALL_LOCATION)/python*)
|
UNINSTALL_DIRS += $(wildcard $(INSTALL_LOCATION)/python*)
|
||||||
|
|
||||||
#useful targets includ: doc-html and doc-clean
|
# jump to a sub-directory where CONFIG_PY has been included
|
||||||
doc-%:
|
# can't include CONFIG_PY here as it may not exist yet
|
||||||
PYTHONPATH=$$PWD/python$(PY_VER)/$(EPICS_HOST_ARCH) $(MAKE) -C documentation $*
|
nose sphinx sh ipython: all
|
||||||
|
$(MAKE) -C devsupApp/src/O.$(EPICS_HOST_ARCH) $@ PYTHON=$(PYTHON)
|
||||||
|
|
||||||
doc: doc-html
|
sphinx-clean:
|
||||||
|
$(MAKE) -C documentation clean PYTHON=$(PYTHON)
|
||||||
|
|
||||||
|
sphinx-commit: sphinx
|
||||||
|
touch documentation/_build/html/.nojekyll
|
||||||
|
./commit-gh.sh documentation/_build/html
|
||||||
|
|
||||||
|
.PHONY: nose sphinx sphinx-commit sphinx-clean
|
||||||
|
@ -2,24 +2,22 @@ ifneq ($(T_A),)
|
|||||||
|
|
||||||
PYMODULE ?= YES
|
PYMODULE ?= YES
|
||||||
|
|
||||||
ifeq ($(PY_VER),)
|
ifeq ($(PYTHON),)
|
||||||
$(error Must set PY_VER to select a python version)
|
$(error Must set PYTHON to select a python version)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY$(PY_VER).$(EPICS_HOST_ARCH).Common
|
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY.$(EPICS_HOST_ARCH).Common
|
||||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY$(PY_VER).Common.$(T_A)
|
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY.Common.$(T_A)
|
||||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY$(PY_VER).$(EPICS_HOST_ARCH).$(T_A)
|
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_PY.$(EPICS_HOST_ARCH).$(T_A)
|
||||||
|
|
||||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY$(PY_VER).$(EPICS_HOST_ARCH).Common
|
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY.$(EPICS_HOST_ARCH).Common
|
||||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY$(PY_VER).Common.$(T_A)
|
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY.Common.$(T_A)
|
||||||
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY$(PY_VER).$(EPICS_HOST_ARCH).$(T_A)
|
-include $(dir $(lastword $(MAKEFILE_LIST)))/os/CONFIG_SITE_PY.$(EPICS_HOST_ARCH).$(T_A)
|
||||||
|
|
||||||
ifneq ($(PY_OK),YES)
|
ifneq ($(PY_OK),YES)
|
||||||
$(error No usable configuration for python$(PY_VER))
|
$(error No usable configuration for $(PYTHON))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PYTHON ?= python$(PY_VER)
|
|
||||||
|
|
||||||
SHRLIB_DEPLIB_DIRS += $(PY_LIBDIRS)
|
SHRLIB_DEPLIB_DIRS += $(PY_LIBDIRS)
|
||||||
PROD_DEPLIB_DIRS += $(PY_LIBDIRS)
|
PROD_DEPLIB_DIRS += $(PY_LIBDIRS)
|
||||||
|
|
||||||
@ -27,18 +25,29 @@ INCLUDES += $(PY_INCDIRS:%=-I%)
|
|||||||
|
|
||||||
ifeq ($(HAVE_NUMPY),YES)
|
ifeq ($(HAVE_NUMPY),YES)
|
||||||
TARGET_CPPFLAGS += -DHAVE_NUMPY
|
TARGET_CPPFLAGS += -DHAVE_NUMPY
|
||||||
|
else
|
||||||
|
$(error numpy required)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LIB_SYS_LIBS += python$(PY_LD_VER)
|
#LIB_SYS_LIBS += python$(PY_LD_VER)
|
||||||
PROD_SYS_LIBS += python$(PY_LD_VER)
|
#PROD_SYS_LIBS += python$(PY_LD_VER)
|
||||||
|
|
||||||
PY_INSTALL_DIR = $(INSTALL_LOCATION)/python$(PY_VER)/$(T_A)
|
PY_INSTALL_DIR = $(INSTALL_LOCATION)/python$(PY_LD_VER)/$(T_A)
|
||||||
|
|
||||||
|
ifneq ($(PYMODULE),NO)
|
||||||
|
|
||||||
# Python loadables have no prefix (eg 'pymod.so')
|
# Python loadables have no prefix (eg 'pymod.so')
|
||||||
# and are installed alongsize .py files
|
# and are installed alongsize .py files
|
||||||
LOADABLE_SHRLIB_PREFIX =
|
LOADABLE_SHRLIB_PREFIX =
|
||||||
ifneq ($(PYMODULE),NO)
|
|
||||||
INSTALL_SHRLIB = $(PY_INSTALL_DIR)
|
INSTALL_SHRLIB = $(PY_INSTALL_DIR)
|
||||||
|
|
||||||
|
ifeq ($(OS_CLASS),Darwin)
|
||||||
|
# need -undefined dynamic_lookup
|
||||||
|
LOADABLE_SHRLIB_LDFLAGS = -bundle -flat_namespace -undefined dynamic_lookup
|
||||||
|
LOADABLE_SHRLIB_SUFFIX = .so
|
||||||
|
endif
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
@ -37,7 +37,9 @@ PY_VER=2.7
|
|||||||
|
|
||||||
# Module will be build against this version of the
|
# Module will be build against this version of the
|
||||||
# Python interpreter
|
# Python interpreter
|
||||||
#PYTHON = python$(PY_VER)
|
PYTHON ?= python$(PY_VER)
|
||||||
|
|
||||||
|
USR_CPPFLAGS += -DUSE_TYPED_RSET
|
||||||
|
|
||||||
-include $(TOP)/configure/CONFIG_SITE.local
|
-include $(TOP)/configure/CONFIG_SITE.local
|
||||||
-include $(TOP)/../CONFIG_SITE.local
|
-include $(TOP)/../CONFIG_SITE.local
|
||||||
|
@ -2,20 +2,19 @@ TOP=..
|
|||||||
|
|
||||||
include $(TOP)/configure/CONFIG
|
include $(TOP)/configure/CONFIG
|
||||||
|
|
||||||
ifeq ($(PY_VER),)
|
|
||||||
$(error Must set PY_VER to select a python version)
|
|
||||||
endif
|
|
||||||
PYTHON ?= python$(PY_VER)
|
PYTHON ?= python$(PY_VER)
|
||||||
|
|
||||||
TARGETS = $(CONFIG_TARGETS)
|
TARGETS = $(CONFIG_TARGETS)
|
||||||
|
|
||||||
ifdef T_A
|
ifdef T_A
|
||||||
CONFIGS = CONFIG_PY RULES_PY os/CONFIG_PY$(PY_VER).Common.$(T_A)
|
CONFIGS = CONFIG_PY RULES_PY os/CONFIG_PY.Common.$(T_A)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS)))
|
||||||
|
|
||||||
include $(TOP)/configure/RULES
|
include $(TOP)/configure/RULES
|
||||||
|
|
||||||
os/CONFIG_PY$(PY_VER).Common.$(T_A): $(TOP)/makehelper.py
|
os/CONFIG_PY.Common.$(T_A): $(TOP)/makehelper.py
|
||||||
[ -d $(dir $@) ] || $(MKDIR) $(dir $@)
|
[ -d $(dir $@) ] || $(MKDIR) $(dir $@)
|
||||||
$(PYTHON) $< $@
|
$(PYTHON) $< $@
|
||||||
|
|
||||||
|
@ -1,80 +1,36 @@
|
|||||||
TOP=../..
|
TOP=../..
|
||||||
|
|
||||||
include $(TOP)/configure/CONFIG
|
include $(TOP)/configure/CONFIG
|
||||||
PYMODULE = NO
|
|
||||||
include $(TOP)/configure/CONFIG_PY
|
include $(TOP)/configure/CONFIG_PY
|
||||||
#----------------------------------------
|
#----------------------------------------
|
||||||
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
||||||
#=============================
|
#=============================
|
||||||
|
|
||||||
#=============================
|
INSTALL_SHRLIB = $(PY_INSTALL_DIR)/devsup
|
||||||
# Build the IOC application
|
|
||||||
|
|
||||||
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)\"
|
_dbapi_SRCS += dbapi.c
|
||||||
setup_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
_dbapi_SRCS += dbrec.c
|
||||||
setup_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
_dbapi_SRCS += dbfield.c
|
||||||
setup_CPPFLAGS += -DPYDIR=\"python$(PY_VER)\"
|
_dbapi_SRCS += dbdset.c
|
||||||
|
_dbapi_SRCS += utest.c
|
||||||
|
|
||||||
devsupMain_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
_dbapi_SRCS += pyDevSupCommon_registerRecordDeviceDriver.cpp
|
||||||
|
|
||||||
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_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||||
|
|
||||||
PY += devsup/__init__.py
|
PY += devsup/__init__.py
|
||||||
PY += devsup/_nullapi.py
|
|
||||||
PY += devsup/db.py
|
PY += devsup/db.py
|
||||||
PY += devsup/dset.py
|
PY += devsup/dset.py
|
||||||
PY += devsup/hooks.py
|
PY += devsup/hooks.py
|
||||||
@ -83,6 +39,10 @@ PY += devsup/util.py
|
|||||||
PY += devsup/disect.py
|
PY += devsup/disect.py
|
||||||
PY += devsup/ptable.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
|
include $(TOP)/configure/RULES
|
||||||
@ -98,3 +58,20 @@ pyconfig:
|
|||||||
@echo "Library path: $(PY_LIBDIRS)"
|
@echo "Library path: $(PY_LIBDIRS)"
|
||||||
@echo "USR_CPPFLAGS: $(USR_CPPFLAGS)"
|
@echo "USR_CPPFLAGS: $(USR_CPPFLAGS)"
|
||||||
@echo "USR_LDFLAGS: $(USR_LDFLAGS)"
|
@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 */
|
/* python has its own ideas about which version to support */
|
||||||
#undef _POSIX_C_SOURCE
|
#undef _POSIX_C_SOURCE
|
||||||
#undef _XOPEN_SOURCE
|
#undef _XOPEN_SOURCE
|
||||||
@ -21,11 +18,15 @@
|
|||||||
#include <epicsThread.h>
|
#include <epicsThread.h>
|
||||||
#include <epicsExit.h>
|
#include <epicsExit.h>
|
||||||
#include <alarm.h>
|
#include <alarm.h>
|
||||||
|
#include <iocsh.h>
|
||||||
|
#include <iocInit.h>
|
||||||
|
|
||||||
#include "pydevsup.h"
|
#include "pydevsup.h"
|
||||||
|
|
||||||
initHookState pyInitLastState;
|
initHookState pyInitLastState;
|
||||||
|
|
||||||
|
extern int pyDevSupCommon_registerRecordDeviceDriver(DBBASE *pbase);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const initHookState state;
|
const initHookState state;
|
||||||
const char * const name;
|
const char * const name;
|
||||||
@ -104,6 +105,20 @@ void pyfile(const char* file)
|
|||||||
PyGILState_Release(state);
|
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;
|
initHookState pyInitLastState = (initHookState)-1;
|
||||||
|
|
||||||
static void pyhook(initHookState state)
|
static void pyhook(initHookState state)
|
||||||
@ -139,28 +154,160 @@ fail:
|
|||||||
PyGILState_Release(gilstate);
|
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
|
#if PY_MAJOR_VERSION >= 3
|
||||||
static struct PyModuleDef dbapimodule = {
|
static struct PyModuleDef dbapimodule = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_dbapi",
|
"devsup._dbapi",
|
||||||
NULL,
|
NULL,
|
||||||
-1,
|
-1,
|
||||||
NULL
|
&dbapimethod
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* initialize "magic" builtin module */
|
#if PY_MAJOR_VERSION >= 3
|
||||||
|
PyMODINIT_FUNC PyInit__dbapi(void)
|
||||||
|
#else
|
||||||
PyMODINIT_FUNC init_dbapi(void)
|
PyMODINIT_FUNC init_dbapi(void)
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
PyObject *mod = NULL, *hookdict;
|
PyObject *mod = NULL, *hookdict, *vertup;
|
||||||
pystate *st;
|
pystate *st;
|
||||||
|
|
||||||
|
pyDevReasonID = epicsThreadPrivateCreate();
|
||||||
|
|
||||||
|
iocshRegister(&codeDef, &codeRun);
|
||||||
|
iocshRegister(&fileDef, &fileRun);
|
||||||
|
initHookRegister(&pyhook);
|
||||||
|
|
||||||
import_array();
|
import_array();
|
||||||
|
|
||||||
#if PY_MAJOR_VERSION >= 3
|
#if PY_MAJOR_VERSION >= 3
|
||||||
mod = PyModule_Create(&dbapimodule);
|
mod = PyModule_Create(&dbapimodule);
|
||||||
#else
|
#else
|
||||||
mod = Py_InitModule("_dbapi", NULL);
|
mod = Py_InitModule("devsup._dbapi", dbapimethod);
|
||||||
#endif
|
#endif
|
||||||
if(!mod)
|
if(!mod)
|
||||||
goto fail;
|
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, NO_ALARM);
|
||||||
PyModule_AddIntMacro(mod, MINOR_ALARM);
|
PyModule_AddIntMacro(mod, MINOR_ALARM);
|
||||||
PyModule_AddIntMacro(mod, MAJOR_ALARM);
|
PyModule_AddIntMacro(mod, MAJOR_ALARM);
|
||||||
@ -279,150 +389,17 @@ PyMODINIT_FUNC init_dbconstants(void)
|
|||||||
if(vertup)
|
if(vertup)
|
||||||
PyModule_AddObject(mod, "pydevver", 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);
|
MODINIT_RET(mod);
|
||||||
}
|
|
||||||
|
|
||||||
static void cleanupPy(void *junk)
|
fail:
|
||||||
{
|
fprintf(stderr, "Failed to initialize builtin _dbapi module!\n");
|
||||||
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");
|
|
||||||
Py_XDECREF(mod);
|
Py_XDECREF(mod);
|
||||||
|
MODINIT_RET(NULL);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#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 <dbScan.h>
|
||||||
#include <cantProceed.h>
|
#include <cantProceed.h>
|
||||||
#include <registryFunction.h>
|
#include <registryFunction.h>
|
||||||
|
#include <iocshRegisterCommon.h>
|
||||||
|
#include <registryCommon.h>
|
||||||
#include <aSubRecord.h>
|
#include <aSubRecord.h>
|
||||||
|
|
||||||
#include "pydevsup.h"
|
#include "pydevsup.h"
|
||||||
@ -471,8 +473,28 @@ int canIOScanRecord(dbCommon *prec)
|
|||||||
return !!priv->scanobj;
|
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 */
|
/* Called with GIL locked */
|
||||||
void pyDBD_cleanup(void)
|
PyObject* pyDBD_cleanup(PyObject *unused)
|
||||||
{
|
{
|
||||||
ELLNODE *cur;
|
ELLNODE *cur;
|
||||||
inshutdown = 1;
|
inshutdown = 1;
|
||||||
@ -497,12 +519,5 @@ void pyDBD_cleanup(void)
|
|||||||
|
|
||||||
free(priv);
|
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",
|
return PyErr_Format(PyExc_ValueError, "Error fetching array info for %s.%s",
|
||||||
self->addr.precord->name,
|
self->addr.precord->name,
|
||||||
self->addr.pfldDes->name);
|
self->addr.pfldDes->name);
|
||||||
else if(noe<1) {
|
|
||||||
PyErr_SetString(PyExc_IndexError, "zero length array");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
rawfield = self->addr.pfield;
|
rawfield = self->addr.pfield;
|
||||||
/* get_array_info can modify pfield in >3.15.0.1 */
|
/* 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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(len<1 || len > self->addr.no_elements) {
|
if(len > self->addr.no_elements) {
|
||||||
PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [1,%lu)",
|
PyErr_Format(PyExc_ValueError, "Requested length %ld out of range [0,%lu)",
|
||||||
(long)len, (unsigned long)self->addr.no_elements);
|
(long)len, (unsigned long)self->addr.no_elements);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -449,25 +445,28 @@ static PyObject *pyField_len(pyField *self)
|
|||||||
|
|
||||||
static PyMethodDef pyField_methods[] = {
|
static PyMethodDef pyField_methods[] = {
|
||||||
{"name", (PyCFunction)pyField_name, METH_NOARGS,
|
{"name", (PyCFunction)pyField_name, METH_NOARGS,
|
||||||
"Return Names (\"record\",\"field\")"},
|
"name() -> (recname, fldname)\n"},
|
||||||
{"fieldinfo", (PyCFunction)pyField_fldinfo, METH_NOARGS,
|
{"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,
|
{"getval", (PyCFunction)pyField_getval, METH_NOARGS,
|
||||||
"Returns scalar version of field value"},
|
"getval() -> object\n"},
|
||||||
{"putval", (PyCFunction)pyField_putval, METH_VARARGS,
|
{"putval", (PyCFunction)pyField_putval, METH_VARARGS,
|
||||||
"Sets field value from a scalar"},
|
"putval(object)\n"},
|
||||||
{"getarray", (PyCFunction)pyField_getarray, METH_NOARGS,
|
{"getarray", (PyCFunction)pyField_getarray, METH_NOARGS,
|
||||||
|
"getarray() -> numpy.ndarray\n"
|
||||||
"Return a numpy ndarray refering to this field for in-place operations."},
|
"Return a numpy ndarray refering to this field for in-place operations."},
|
||||||
{"getarraylen", (PyCFunction)pyField_getlen, METH_NOARGS,
|
{"getarraylen", (PyCFunction)pyField_getlen, METH_NOARGS,
|
||||||
|
"getarraylen() -> int\n"
|
||||||
"Return current number of valid elements for array fields."},
|
"Return current number of valid elements for array fields."},
|
||||||
{"putarraylen", (PyCFunction)pyField_setlen, METH_VARARGS,
|
{"putarraylen", (PyCFunction)pyField_setlen, METH_VARARGS,
|
||||||
|
"putarraylen(int)\n"
|
||||||
"Set number of valid elements for array fields."},
|
"Set number of valid elements for array fields."},
|
||||||
{"getTime", (PyCFunction)pyField_getTime, METH_NOARGS,
|
{"getTime", (PyCFunction)pyField_getTime, METH_NOARGS,
|
||||||
"Return link target timestamp as a tuple (sec, nsec)."},
|
"getTime() -> (sec, nsec)."},
|
||||||
{"getAlarm", (PyCFunction)pyField_getAlarm, METH_NOARGS,
|
{"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,
|
{"__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}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -569,12 +568,3 @@ int pyField_prepare(PyObject *module)
|
|||||||
return 0;
|
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[] = {
|
static PyMethodDef pyRecord_methods[] = {
|
||||||
{"name", (PyCFunction)pyRecord_name, METH_NOARGS,
|
{"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", (PyCFunction)pyRecord_rtype, METH_NOARGS,
|
||||||
|
"rtype() -> str\n"
|
||||||
"Return record type name string"},
|
"Return record type name string"},
|
||||||
{"isPyRecord", (PyCFunction)pyRecord_ispyrec, METH_NOARGS,
|
{"isPyRecord", (PyCFunction)pyRecord_ispyrec, METH_NOARGS,
|
||||||
|
"isPyRecord() -> bool\n"
|
||||||
"Is this record using Python Device."},
|
"Is this record using Python Device."},
|
||||||
{"info", (PyCFunction)pyRecord_info, METH_VARARGS,
|
{"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", (PyCFunction)pyRecord_infos, METH_NOARGS,
|
||||||
|
"infos() -> {'name':'value'}\n"
|
||||||
"Return a dictionary of all infos for this record."},
|
"Return a dictionary of all infos for this record."},
|
||||||
{"setSevr", (PyCFunction)pyRecord_setSevr, METH_VARARGS|METH_KEYWORDS,
|
{"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!"},
|
"Set alarm new alarm severity/status. Record must be locked!"},
|
||||||
{"setTime", (PyCFunction)pyRecord_setTime, METH_VARARGS,
|
{"setTime", (PyCFunction)pyRecord_setTime, METH_VARARGS,
|
||||||
"Set record timestamp if TSE==-2. Record must be locked!"},
|
"Set record timestamp if TSE==-2. Record must be locked!"},
|
||||||
{"scan", (PyCFunction)pyRecord_scan, METH_VARARGS|METH_KEYWORDS,
|
{"scan", (PyCFunction)pyRecord_scan, METH_VARARGS|METH_KEYWORDS,
|
||||||
"scan(sync=False)\nScan this record. If sync is False then"
|
"scan(sync=False, reason=None, force=0)\n"},
|
||||||
"a scan request is queued. If sync is True then the record"
|
|
||||||
"is scannined immidately on the current thread."},
|
|
||||||
{"asyncStart", (PyCFunction)pyRecord_asyncStart, METH_NOARGS,
|
{"asyncStart", (PyCFunction)pyRecord_asyncStart, METH_NOARGS,
|
||||||
"Begin an asynchronous action. Record must be locked!"},
|
"Begin an asynchronous action. Record must be locked!"},
|
||||||
{"asyncFinish", (PyCFunction)pyRecord_asyncFinish, METH_VARARGS|METH_KEYWORDS,
|
{"asyncFinish", (PyCFunction)pyRecord_asyncFinish, METH_VARARGS|METH_KEYWORDS,
|
||||||
|
@ -1,28 +1,86 @@
|
|||||||
try:
|
import os
|
||||||
import _dbapi
|
import atexit
|
||||||
HAVE_DBAPI = True
|
import tempfile
|
||||||
except ImportError:
|
|
||||||
import devsup._nullapi as _dbapi
|
|
||||||
HAVE_DBAPI = False
|
|
||||||
|
|
||||||
try:
|
from . import _dbapi
|
||||||
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
|
|
||||||
|
|
||||||
XEPICS_ARCH = "nullos-nullarch"
|
from ._dbapi import (EPICS_VERSION_STRING,
|
||||||
XPYDEV_BASE = "invaliddir"
|
EPICS_DEV_SNAPSHOT,
|
||||||
XEPICS_BASE = "invaliddir"
|
EPICS_SITE_VERSION,
|
||||||
|
EPICS_VERSION,
|
||||||
epicsver = (0,0,0,0,"0","")
|
EPICS_REVISION,
|
||||||
pydevver = (0,0)
|
EPICS_MODIFICATION,
|
||||||
|
EPICS_PATCH_LEVEL,
|
||||||
INVALID_ALARM = UDF_ALARM = 0
|
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__ = []
|
__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
|
from devsup.util import Worker, importmod
|
||||||
|
|
||||||
try:
|
from . import _dbapi
|
||||||
import _dbapi
|
|
||||||
except ImportError:
|
|
||||||
import _nullapi as _dbapi
|
|
||||||
|
|
||||||
_rec_cache = {}
|
_rec_cache = {}
|
||||||
_no_such_field = object()
|
_no_such_field = object()
|
||||||
@ -263,6 +260,76 @@ class Record(_dbapi._Record):
|
|||||||
|
|
||||||
super(Record, self).setTime(sec, nsec)
|
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):
|
def __getattr__(self, name):
|
||||||
try:
|
try:
|
||||||
F = self.field(name)
|
F = self.field(name)
|
||||||
@ -341,5 +408,8 @@ def processLink(name, lstr):
|
|||||||
modname, args = parts[0], parts[1] if len(parts)>1 else None
|
modname, args = parts[0], parts[1] if len(parts)>1 else None
|
||||||
else:
|
else:
|
||||||
args = lstr
|
args = lstr
|
||||||
|
modname, _sep, attr = modname.partition('|')
|
||||||
mod = importmod(modname)
|
mod = importmod(modname)
|
||||||
|
if attr:
|
||||||
|
mod = getattr(mod, attr)
|
||||||
return rec, mod.build(rec, args)
|
return rec, mod.build(rec, args)
|
||||||
|
@ -4,10 +4,7 @@ import traceback
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
try:
|
from . import _dbapi
|
||||||
import _dbapi
|
|
||||||
except ImportError:
|
|
||||||
import devsup._nullapi as _dbapi
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"hooknames",
|
"hooknames",
|
||||||
@ -45,7 +42,7 @@ def initHook(state):
|
|||||||
|
|
||||||
@initHook("AfterIocRunning")
|
@initHook("AfterIocRunning")
|
||||||
def myfn():
|
def myfn():
|
||||||
# do stuff
|
pass
|
||||||
"""
|
"""
|
||||||
def _add(fn):
|
def _add(fn):
|
||||||
addHook(state, fn)
|
addHook(state, fn)
|
||||||
|
@ -7,8 +7,8 @@ import threading, inspect
|
|||||||
|
|
||||||
_tables = {}
|
_tables = {}
|
||||||
|
|
||||||
from devsup.db import IOScanListThread
|
from .db import IOScanListThread
|
||||||
from devsup import INVALID_ALARM, UDF_ALARM
|
from . import INVALID_ALARM, UDF_ALARM
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Parameter',
|
'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,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
|
#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);
|
int pyField_prepare(PyObject *module);
|
||||||
void pyField_cleanup(void);
|
|
||||||
|
|
||||||
int pyRecord_prepare(PyObject *module);
|
int pyRecord_prepare(PyObject *module);
|
||||||
|
|
||||||
int isPyRecord(dbCommon *);
|
int isPyRecord(struct dbCommon *);
|
||||||
int canIOScanRecord(dbCommon *);
|
int canIOScanRecord(struct dbCommon *);
|
||||||
|
|
||||||
extern epicsThreadPrivateId pyDevReasonID;
|
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;
|
||||||
|
}
|
@ -108,7 +108,7 @@ In somefile.c ::
|
|||||||
PyMODINIT_FUNC init_myextname(void)
|
PyMODINIT_FUNC init_myextname(void)
|
||||||
|
|
||||||
Installing for several Python versions
|
Installing for several Python versions
|
||||||
------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
The recipe for building and installing the pyDevSup module
|
The recipe for building and installing the pyDevSup module
|
||||||
for several python version side by side is ::
|
for several python version side by side is ::
|
||||||
|
@ -28,23 +28,39 @@ libdirs = [get_config_var('LIBDIR')]
|
|||||||
have_np='NO'
|
have_np='NO'
|
||||||
try:
|
try:
|
||||||
from numpy.distutils.misc_util import get_numpy_include_dirs
|
from numpy.distutils.misc_util import get_numpy_include_dirs
|
||||||
incdirs += get_numpy_include_dirs()
|
incdirs = get_numpy_include_dirs()+incdirs
|
||||||
have_np='YES'
|
have_np='YES'
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
incdirs = [get_python_inc()]+get_numpy_include_dirs()
|
|
||||||
libdirs = [get_config_var('LIBDIR')]
|
|
||||||
|
|
||||||
print('TARGET_CFLAGS +=',get_config_var('BASECFLAGS'), file=out)
|
print('TARGET_CFLAGS +=',get_config_var('BASECFLAGS'), file=out)
|
||||||
print('TARGET_CXXFLAGS +=',get_config_var('BASECFLAGS'), file=out)
|
print('TARGET_CXXFLAGS +=',get_config_var('BASECFLAGS'), file=out)
|
||||||
|
|
||||||
print('PY_VER :=',get_config_var('VERSION'), file=out)
|
print('PY_VER :=',get_config_var('VERSION'), file=out)
|
||||||
print('PY_LD_VER :=',get_config_var('LDVERSION') or get_config_var('VERSION'), file=out)
|
ldver = get_config_var('LDVERSION')
|
||||||
|
if ldver is None:
|
||||||
|
ldver = get_config_var('VERSION')
|
||||||
|
if get_config_var('Py_DEBUG'):
|
||||||
|
ldver = ldver+'_d'
|
||||||
|
print('PY_LD_VER :=',ldver, file=out)
|
||||||
print('PY_INCDIRS :=',' '.join(incdirs), file=out)
|
print('PY_INCDIRS :=',' '.join(incdirs), file=out)
|
||||||
print('PY_LIBDIRS :=',' '.join(libdirs), file=out)
|
print('PY_LIBDIRS :=',' '.join(libdirs), file=out)
|
||||||
print('HAVE_NUMPY :=',have_np, file=out)
|
print('HAVE_NUMPY :=',have_np, file=out)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import asyncio
|
||||||
|
except ImportError:
|
||||||
|
print('HAVE_ASYNCIO := NO', file=out)
|
||||||
|
else:
|
||||||
|
print('HAVE_ASYNCIO := YES', file=out)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cothread
|
||||||
|
except ImportError:
|
||||||
|
print('HAVE_COTHREAD := NO', file=out)
|
||||||
|
else:
|
||||||
|
print('HAVE_COTHREAD := YES', file=out)
|
||||||
|
|
||||||
print('PY_OK := YES', file=out)
|
print('PY_OK := YES', file=out)
|
||||||
|
|
||||||
out.close()
|
out.close()
|
||||||
|
77
pyIocApp/Makefile
Normal file
77
pyIocApp/Makefile
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
TOP=..
|
||||||
|
|
||||||
|
include $(TOP)/configure/CONFIG
|
||||||
|
PYMODULE = NO
|
||||||
|
include $(TOP)/configure/CONFIG_PY
|
||||||
|
#----------------------------------------
|
||||||
|
# ADD MACRO DEFINITIONS AFTER THIS LINE
|
||||||
|
#=============================
|
||||||
|
|
||||||
|
#=============================
|
||||||
|
# Build the IOC application
|
||||||
|
|
||||||
|
USR_CPPFLAGS += -I$(TOP)/devsupApp/src
|
||||||
|
|
||||||
|
LIBRARY = pyDevSup$(PY_LD_VER)
|
||||||
|
|
||||||
|
SHRLIB_VERSION = 0
|
||||||
|
|
||||||
|
DBD += pyDevSup.dbd
|
||||||
|
|
||||||
|
pyDevSup$(PY_LD_VER)_SYS_LIBS += python$(PY_LD_VER)
|
||||||
|
|
||||||
|
pyDevSup$(PY_LD_VER)_LIBS += $(EPICS_BASE_IOC_LIBS)
|
||||||
|
|
||||||
|
setup_CPPFLAGS += -DXEPICS_ARCH=\"$(T_A)\"
|
||||||
|
setup_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||||
|
setup_CPPFLAGS += -DXEPICS_BASE=\"$(EPICS_BASE)\"
|
||||||
|
setup_CPPFLAGS += -DPYDIR=\"python$(PY_LD_VER)\"
|
||||||
|
|
||||||
|
pyDevSup$(PY_LD_VER)_SRCS += setup.c
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
devsupMain_CPPFLAGS += -DXPYDEV_BASE=\"$(abspath $(INSTALL_LOCATION))\"
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
#===========================
|
||||||
|
|
||||||
|
include $(TOP)/configure/RULES
|
||||||
|
include $(TOP)/configure/RULES_PY
|
||||||
|
#----------------------------------------
|
||||||
|
# ADD RULES AFTER THIS LINE
|
1
pyIocApp/pyDevSup.dbd
Normal file
1
pyIocApp/pyDevSup.dbd
Normal file
@ -0,0 +1 @@
|
|||||||
|
registrar(pySetupReg)
|
135
pyIocApp/setup.c
Normal file
135
pyIocApp/setup.c
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
/* Global interpreter setup
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* python has its own ideas about which version to support */
|
||||||
|
#undef _POSIX_C_SOURCE
|
||||||
|
#undef _XOPEN_SOURCE
|
||||||
|
|
||||||
|
#include <Python.h>
|
||||||
|
#ifdef HAVE_NUMPY
|
||||||
|
#include <numpy/ndarrayobject.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <epicsVersion.h>
|
||||||
|
#include <dbCommon.h>
|
||||||
|
#include <dbAccess.h>
|
||||||
|
#include <dbStaticLib.h>
|
||||||
|
#include <dbScan.h>
|
||||||
|
#include <initHooks.h>
|
||||||
|
#include <epicsThread.h>
|
||||||
|
#include <epicsExit.h>
|
||||||
|
#include <alarm.h>
|
||||||
|
|
||||||
|
#include "pydevsup.h"
|
||||||
|
|
||||||
|
static void cleanupPy(void *junk)
|
||||||
|
{
|
||||||
|
PyThreadState *state = PyGILState_GetThisThreadState();
|
||||||
|
|
||||||
|
PyEval_RestoreThread(state);
|
||||||
|
|
||||||
|
if(PyRun_SimpleString("import devsup\n"
|
||||||
|
"devsup._fini(iocMain=True)\n"
|
||||||
|
)) {
|
||||||
|
PyErr_Print();
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_Finalize(); // calls python atexit hooks
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pySetupReg(void)
|
||||||
|
{
|
||||||
|
Py_InitializeEx(0);
|
||||||
|
PyEval_InitThreads();
|
||||||
|
|
||||||
|
setupPyPath();
|
||||||
|
|
||||||
|
if(PyRun_SimpleString("import devsup\n"
|
||||||
|
"devsup._init(iocMain=True)\n"
|
||||||
|
)) {
|
||||||
|
PyErr_Print();
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)PyEval_SaveThread();
|
||||||
|
|
||||||
|
epicsAtExit(&cleanupPy, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <epicsExport.h>
|
||||||
|
epicsExportRegistrar(pySetupReg);
|
3
test.cmd
3
test.cmd
@ -3,7 +3,6 @@
|
|||||||
py "import logging"
|
py "import logging"
|
||||||
py "logging.basicConfig(level=logging.DEBUG)"
|
py "logging.basicConfig(level=logging.DEBUG)"
|
||||||
|
|
||||||
py "import devsup; print devsup.HAVE_DBAPI"
|
|
||||||
py "import sys; sys.path.insert(0,'${PWD}/testApp')"
|
py "import sys; sys.path.insert(0,'${PWD}/testApp')"
|
||||||
py "print sys.path"
|
py "print sys.path"
|
||||||
|
|
||||||
@ -23,4 +22,4 @@ dbLoadRecords("db/test6.db","P=tst:,TNAME=tsum")
|
|||||||
iocInit()
|
iocInit()
|
||||||
|
|
||||||
# Start Reference tracker
|
# Start Reference tracker
|
||||||
py "from devsup import disect; disect.periodic(10)"
|
#py "from devsup import disect; disect.periodic(10)"
|
||||||
|
@ -27,11 +27,7 @@ class WfSup(object):
|
|||||||
x=self.x[:N]
|
x=self.x[:N]
|
||||||
|
|
||||||
# calculate inplace: uniform(0.5,2.0)*sin(pha*x)+2
|
# calculate inplace: uniform(0.5,2.0)*sin(pha*x)+2
|
||||||
val[:] = x
|
val[:] = np.sin(x*pha)*uniform(0.5,2.0) + 2
|
||||||
val[:] *= pha
|
|
||||||
np.sin(val, out=val)
|
|
||||||
val[:]*=uniform(0.5,2.0)
|
|
||||||
val[:]+=2
|
|
||||||
|
|
||||||
self.fld.putarraylen(N)
|
self.fld.putarraylen(N)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user