Custom Drivers
Users may extend LiCoRICE by setting the LICORICE_TEMPLATE_PATH environment variable to add or overwrite template files, including drivers. In this way, users may write their own drivers to interface with peripherals. Drivers are represented as Cython extension types which allow interfacing with C libraries while exposing drivers as encapsulated Python classes.
Drivers can also be added to LiCoRICE directly by adding an extension type to the correct folder in the LiCoRICE source code and compiling from source. Sink drivers must be added to the licorice/templates/sink_drivers/ folder and source drivers to licorice/templates/source _drivers/.
Specification
A LiCoRICE custom driver has a few mandatory requirements.
Drivers must constitute of a set of files in one folder with the driver’s name with the following structure:
<driver-name> |-- __init__.pxd |-- <driver-name>.pxd.j2 |-- <driver-name>.pyx.j2
Driver extension type classes must subclass the appropriate
source_driver.SourceDriverorsink_driver.SinkDriverabstract class. These classes are implemented in LiCoRICE and can be imported as below if thelicoricepackage is installed in your virtualenv:from source_drivers cimport source_driver # source_driver.SourceDriver from sink_drivers cimport source_driver # sink_driver.SinkDriver
Driver classes must implement a
runandexit_handlermethod which are theNotImplementedmethods of the generic abstractSourceDriverandSinkDriverclasses.
Example
The following is an implementation of a parallel port source driver which uses pyparallel to read in data each tick. The driver files use Jinja2 templating in order to embed configuration values. Note that variables for the extension type class are defined in the .pxd file (like a C header file) and method implementations are defined in the .pyx file (like a C source file).
An empty __init__.pxd file must be included in the driver folder so that the driver can be imported into python code as a package.
# __init__.pxd
# must have an empty __init__.pxd file
Then, the .pxd.j2 templated header file defines any variables used the class (use cdef object to specify Python variables) as well as the method signature for the mandatory run and exit_handler methods. Other helper methods can be included here as well.
1# parport.pxd.j2
2
3from libc.stdint cimport uint8_t
4
5from runner_utils cimport times_t
6from source_drivers cimport source_driver
7
8cdef class ParportSourceDriver(source_driver.SourceDriver):
9 cdef object pport
10 cdef float sleep_duration
11
12 cdef size_t run(self, times_t *times, void *inBuf, size_t packetSize, object out_sigs) except *
13 cdef void exit_handler(self, int exitStatus) except *
Finally, the .pyx.j2 source file includes the implementation of the run and exit_handler methods, as well as a __cinit__ method that sets up any resources needed during realtime execution.
1# parport.pyx.j2
2
3import parallel
4
5from libc.stdint cimport uint8_t
6
7from source_drivers cimport source_driver
8
9cdef class ParportSourceDriver(source_driver.SourceDriver):
10 def __cinit__(self):
11 self.pport = parallel.Parallel(port={{in_signal['args']['addr']}})
12 self.pport.setDataDir(False) # read from data pins, sets PPDATADIR
13 self.sleep_duration = {{config["config"]["tick_len"]}} / (2. * 1e6)
14
15 cdef size_t run(self, times_t *times, void *inBuf, size_t packetSize, object out_sigs) except *:
16 (<unsigned char *>inBuf)[0] = <unsigned char>self.pport.getData()
17
18{%- if async %}
19
20 sleep(self.sleep_duration)
21{%- endif %}
22
23 return 1
24
25 cdef void exit_handler(self, int exitStatus) except *:
26 pass