diff --git a/README.md b/README.md new file mode 100644 index 0000000..dcf28e9 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +pyDevSup +======== + +EPICS Device support in Python. + +See [documentation](http://mdavidsaver.github.io/pyDevSup) + +For file [releases](http://sourceforge.net/projects/epics/files/pyDevSup) diff --git a/devsupApp/src/devsup/_nullapi.py b/devsupApp/src/devsup/_nullapi.py index 5c748e7..0e6e910 100644 --- a/devsupApp/src/devsup/_nullapi.py +++ b/devsupApp/src/devsup/_nullapi.py @@ -53,29 +53,28 @@ class _Record(object): def scan(self, sync=False, reason=None, force=0): """Scan this record. - :param sync: scan in current thread (``True``), or queue (``False``). + :param sync: scan in current thread (``True``), or queue to a worker (``False``). :param reason: Reason object passed to :meth:`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 scannined immidately on the current thread. + 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 ` method. - ``force`` is used to decide if the record will actuall be processed, + ``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. - It is **never** safe to use ``sync=True`` while holding record locks, - including from within a *process* method. + .. important:: + It is **never** safe to use ``sync=True`` while holding record locks, + including from within a *process* method. """ def asyncStart(self): @@ -154,7 +153,7 @@ class _Field(object): def putval(self, val): """Update the field value - Must be an Int, Float or str. Strings will be truncated to 39 charactors. + Must be an Int, Float or str. Strings will be truncated to 39 characters. """ def getarray(self): @@ -179,11 +178,11 @@ class _Field(object): """Set the number of active elements in field's array. Requires that the underlying field be an array. - Must be less than the maximum length of the field. + 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 condtion of the linked field. + """Returns a tuple (severity, status) with the condition of the linked field. Only works for fields of type DBF_INLINK. """ diff --git a/devsupApp/src/devsup/db.py b/devsupApp/src/devsup/db.py index 5c4176e..af4f5b1 100644 --- a/devsupApp/src/devsup/db.py +++ b/devsupApp/src/devsup/db.py @@ -23,7 +23,7 @@ def getRecord(name): full record name. The result is cached so the future calls will return the same instance. - This is the prefered way to get :class:`Record` instances. + This is the preferred way to get :class:`Record` instances. >>> R = getRecord("my:record:name") Record("my:record:name") @@ -38,7 +38,7 @@ def getRecord(name): class IOScanListBlock(object): """A list of records which will be processed together. - This convienence class to handle the accounting to + This convenience class to handle the accounting to maintain a list of records. """ def __init__(self): @@ -218,7 +218,7 @@ class Record(_dbapi._Record): """Lookup field in this record :rtype: :class:`Field` - :throws: KeyError for non-existant fields. + :throws: KeyError for non-existent fields. The returned object is cached so future calls will return the same instance. @@ -248,6 +248,9 @@ class Record(_dbapi._Record): Has not effect if the TSE field is not set to -2. All inputs must be referenced to the posix epoch. + + If a datetime is provided, it must use the local system + timezone. """ if hasattr(ts, 'timetuple'): ts = time.mktime(ts.timetuple()) @@ -296,7 +299,7 @@ class Field(_dbapi._Field): """Get timestamp of link target. Only works for DBF_INLINK fields. - Returns the time in seconds since the posix epoch. + Returns the time in seconds since the POSIX epoch. :rtype: float """ diff --git a/devsupApp/src/devsup/ptable.py b/devsupApp/src/devsup/ptable.py index baa830b..4ad3d30 100644 --- a/devsupApp/src/devsup/ptable.py +++ b/devsupApp/src/devsup/ptable.py @@ -17,7 +17,7 @@ __all__ = [ 'build' ] -# Reason code to cause a record to read a new value from a table paramter +# Reason code to cause a record to read a new value from a table parameter _INTERNAL = object() # action types @@ -40,7 +40,7 @@ def _add_action(self, act, fn): class Parameter(object): """Define a parameter in a table. - When a sub-class of TableBase is instancianted, parameters become + When a sub-class of TableBase is instantiated, parameters become py:class:`_ParamInstance` instances. >>> class MyTable(TableBase): @@ -100,7 +100,7 @@ class Parameter(object): class ParameterGroup(object): """A helper for defining actions on groups of parameters - When a sub-class of TableBase is instancianted, parameter groups become + When a sub-class of TableBase is instantiated, parameter groups become py:class:`_ParamGroupInstance` instances. >>> class MyTable(TableBase): @@ -116,7 +116,7 @@ class ParameterGroup(object): self.params, self.name = params, name def onproc(self, fn): """Decorator run a member function action whenever - a device support attached to any paramter in the group processes. + a device support attached to any parameter in the group processes. >>> class MyTable(TableBase): A, B = Parameter(), Parameter() @@ -133,7 +133,7 @@ class ParameterGroup(object): "Decorator to run an action when any parameters has an invalid value" return _add_action(self, (any, lambda p:not p.isvalid), fn) def oncondition(self, fmap, freduce=all): - """Decorator for a custom condtion. + """Decorator for a custom condition. The condition is specified in two parts, a map function, and a reduce function. The map function is applied to each parameter in the group. Then a list @@ -265,7 +265,7 @@ class TableBase(object): Sub-class this and populate with :py:class:`Parameter` and :py:class:`ParameterGroup`. - When a table is instanciated it must be given a unique name. + #When a table is instantiated it must be given a unique name. >>> class MyTable(TableBase): ... @@ -282,7 +282,7 @@ class TableBase(object): self._parameters = {} # Find Parameters and ParameterGroup in the class dictionary - # and place approprate things in the instance dictionary + # and place appropriate things in the instance dictionary rparams = {} rgroups = {} for k,v in self.__class__.__dict__.items(): diff --git a/documentation/devsup.rst b/documentation/devsup.rst index 94ef094..054e792 100644 --- a/documentation/devsup.rst +++ b/documentation/devsup.rst @@ -16,12 +16,12 @@ devsup Package .. module:: devsup.db -.. autofunction:: devsup.db.getRecord +.. autofunction:: getRecord :class:`Record` Class ^^^^^^^^^^^^^^^^^^^^^ -.. class:: devsup.db.Record +.. class:: Record Allows access to a single record instance. *Record* instances can be created for any record in @@ -65,7 +65,7 @@ devsup Package :class:`Field` Class ^^^^^^^^^^^^^^^^^^^^ -.. autoclass:: devsup.db.Field +.. autoclass:: Field .. automethod:: name @@ -77,18 +77,22 @@ devsup Package .. automethod:: getarray + .. automethod:: getarraylen + + .. automethod:: putarraylen + .. automethod:: fieldinfo .. automethod:: getTime .. automethod:: getAlarm -.. autoclass:: devsup.db.IOScanListBlock +.. autoclass:: IOScanListBlock :members: :inherited-members: :undoc-members: -.. autoclass:: devsup.db.IOScanListThread +.. autoclass:: IOScanListThread :members: add, interrupt diff --git a/documentation/environment.rst b/documentation/environment.rst index 785dd86..6384c06 100644 --- a/documentation/environment.rst +++ b/documentation/environment.rst @@ -62,8 +62,8 @@ The following should be added to individual EPICS Makefiles. :: include $(TOP)/configure/RULES include $(PYDEVSUP)/configure/RULES_PY -This will add or ammend several make variables. The ``USR_*FLAGS`` variables -may be extended with approprate flags for building python modules. The ``PY_VER`` +This will add or amend several make variables. The ``USR_*FLAGS`` variables +may be extended with appropriate flags for building python modules. The ``PY_VER`` variable is defined with the Python version number found in install directories (eg "2.7"). The ``PY_LD_VER`` variable is defined with the python library version number (eg "3.2mu"), which may be the same as ``PY_VER``. @@ -94,7 +94,7 @@ Additional .py files can be installed as follows. :: Building extensions ------------------- -For convienance, additional Python extensions can be build by the EPICS +For convenience, additional Python extensions can be build by the EPICS build system. In this example the extension name is "_myextname" and the resulting library is expected to provide the an initialization function named "init_myextname". :: diff --git a/documentation/gettingstarted.rst b/documentation/gettingstarted.rst new file mode 100644 index 0000000..b8ec7b2 --- /dev/null +++ b/documentation/gettingstarted.rst @@ -0,0 +1,60 @@ +Getting Started +=============== + +Counter +------- + +Consider a simple EPICS database with one record. Call it :download:`cntrec.db <../testApp/cntrec.db>` + +.. literalinclude:: ../testApp/cntrec.db + +This is creating a single record which will use the "Python Device" support code (aka this package). +It will attempt to scan (call the process method) one a second. +The *INP* field is parsed and the first work identifies the Python module which will provide +the logic behind this record (everything after the first word is passed to the module :py:func:`build` function. + +Now create :download:`cntrec.db <../testApp/cntmod.py>` with the following. + +.. literalinclude:: ../testApp/cntmod.py + +This module is expected to provide a special callable :py:func:`build`. +We also provide a constructor and method :py:meth:`detach ` +which don't do anything. +The :py:meth:`process ` method increments the *VAL* field of the attached :py:class:`Record `. + +Start this IOC with. :: + + $ ./bin/linux-x86_64/softIocPy2.7 -d cntrec.db + Starting iocInit + ... + iocRun: All initialization complete + epics>dbl + test:count + epics> + +Now in another terminal run.:: + + $ camonitor test:count + ... + test:count 2014-06-16 16:48:22.891825 9 + test:count 2014-06-16 16:48:23.891967 10 + test:count 2014-06-16 16:48:24.892137 11 + test:count 2014-06-16 16:48:25.892286 12 + +It may be necessary to run *export EPICS_CA_ADDR_LIST=localhost* first. + +Additional examples and applications +------------------------------------ + +This module comes with several examples in *testApp* as well as three complete applications. + +logApp + Observes a line based text file as new lines are appended. + Writes each line to a charactor array PV. + Special handling of caPutLog files. + +pidMonApp + Monitors the PID file created by a UNIX daemon. + +weatherApp + Retreives weather reports via the *pymetar* module. diff --git a/documentation/index.rst b/documentation/index.rst index 41109f2..d2a6df2 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -15,6 +15,7 @@ Contents: .. toctree:: :maxdepth: 4 + gettingstarted environment devsup interfaces diff --git a/documentation/interfaces.rst b/documentation/interfaces.rst index dbda024..66fa3dd 100644 --- a/documentation/interfaces.rst +++ b/documentation/interfaces.rst @@ -55,7 +55,7 @@ and the string "some other string". of the methods which all Python device support instances must provide. These methods will be called during the course of IOC processing. - Execptions raised by these methods are printed to the IOC console, + Exceptions raised by these methods are printed to the IOC console, but will otherwise be ignored. The module :mod:`devsup.interfaces` provides a Zope Interface @@ -76,7 +76,7 @@ and the string "some other string". :param reason: ``None`` or an object provided when processing was requested. This method is called whenever the associated record needs to be updated - in responce to a request. The source of this request is typically determined + in response to a request. The source of this request is typically determined by the record's SCAN field. The actions taken by this method will depend heavily on the application. @@ -120,7 +120,7 @@ and the string "some other string". def allowScan(self, record): return self.a_scan.add(record) - Which is most cases can be abbriviated to :: + Which in most cases can be abbriviated to :: class MySup(object): def __init__(self): diff --git a/testApp/cntmod.py b/testApp/cntmod.py new file mode 100644 index 0000000..b5ab16a --- /dev/null +++ b/testApp/cntmod.py @@ -0,0 +1,8 @@ +class MySupport: + def __init__(self, rec, link): + pass + def detach(self, rec): + pass + def process(self, rec, reason): + rec.VAL = rec.VAL + 1 +build = MySupport diff --git a/testApp/cntrec.db b/testApp/cntrec.db new file mode 100644 index 0000000..9887725 --- /dev/null +++ b/testApp/cntrec.db @@ -0,0 +1,5 @@ + record(longin, "test:count") { + field(DTYP, "Python Device") + field(INP , "@cntmod") + field(SCAN, "1 second") + }