diff --git a/README.md b/README.md index 0d2d7fa..b076b37 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # RPi WS281x Python -This is the official Python distribution of the ws281x library: http://github.com/richardghirst/rpi_ws281x +This is the official Python distribution of the ws281x library: https://github.com/jgarff/rpi_ws281x # Installing @@ -11,3 +11,22 @@ Most users should simply run: ``` sudo pip install rpi_ws281x ``` + +## Building + +Clone with submodules, and enter library directory: +``` +git clone --recurse-submodules https://github.com/rpi-ws281x/rpi-ws281x-python.git +cd rpi-ws281x-python/library +``` +To rebuild SWIG files if needed ("black" for code re-formatting only): +``` +swig -python -threads rpi_ws281x.i +black rpi_ws281x.py +``` + +Build and Install: +``` +python3 setup.py build +sudo python3 setup.py install +``` diff --git a/library/rpi_ws281x.i b/library/rpi_ws281x.i index f362345..bc73bbb 100644 --- a/library/rpi_ws281x.i +++ b/library/rpi_ws281x.i @@ -1,6 +1,8 @@ // SWIG interface file to define rpi_ws281x library python wrapper. // Author: Tony DiCola (tony@tonydicola.com), Jeremy Garff (jer@jers.net) +%nothread; + // Define module name rpi_ws281x. This will actually be imported under // the name _rpi_ws281x following the SWIG & Python conventions. %module rpi_ws281x @@ -92,3 +94,12 @@ static int convert_iarray(PyObject *input, uint8_t *ptr, int size) { return &ws->channel[channelnum]; } %} + +%thread; +%inline %{ + ws2811_return_t ws2811_render_nogil(ws2811_t *ws2811) + { + return ws2811_render(ws2811); + } +%} +%nothread; diff --git a/library/rpi_ws281x.py b/library/rpi_ws281x.py index 2761371..4a62b71 100644 --- a/library/rpi_ws281x.py +++ b/library/rpi_ws281x.py @@ -221,3 +221,7 @@ def ws2811_led_set(channel, lednum, color): def ws2811_channel_get(ws, channelnum): return _rpi_ws281x.ws2811_channel_get(ws, channelnum) + + +def ws2811_render_nogil(ws2811): + return _rpi_ws281x.ws2811_render_nogil(ws2811) diff --git a/library/rpi_ws281x/rpi_ws281x.py b/library/rpi_ws281x/rpi_ws281x.py index f9d6a8a..b63a2a7 100644 --- a/library/rpi_ws281x/rpi_ws281x.py +++ b/library/rpi_ws281x/rpi_ws281x.py @@ -2,7 +2,7 @@ # Author: Tony DiCola (tony@tonydicola.com), Jeremy Garff (jer@jers.net) import _rpi_ws281x as ws import atexit - +from threading import Lock class RGBW(int): def __new__(self, r, g=None, b=None, w=None): @@ -50,6 +50,9 @@ def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, channel, the PWM channel to use (defaults to 0). """ + self._mutex = Lock() + self._render_func = ws.ws2811_render + if gamma is None: # Support gamma in place of strip_type for back-compat with # previous version of forked library @@ -142,12 +145,17 @@ def begin(self): str_resp = ws.ws2811_get_return_t_str(resp) raise RuntimeError('ws2811_init failed with code {0} ({1})'.format(resp, str_resp)) + def releaseGIL(self, release = True) + """Setup option to release GIL during render function.""" + self._render_func = ws.ws2811_render_nogil if release else ws.ws2811_render + def show(self): """Update the display with the data from the LED buffer.""" - resp = ws.ws2811_render(self._leds) - if resp != 0: - str_resp = ws.ws2811_get_return_t_str(resp) - raise RuntimeError('ws2811_render failed with code {0} ({1})'.format(resp, str_resp)) + with self._mutex: + resp = self._render_func(self._leds) + if resp != 0: + str_resp = ws.ws2811_get_return_t_str(resp) + raise RuntimeError('ws2811_render failed with code {0} ({1})'.format(resp, str_resp)) def setPixelColor(self, n, color): """Set LED at position n to the provided 24-bit color value (in RGB order). diff --git a/library/rpi_ws281x_wrap.c b/library/rpi_ws281x_wrap.c index 10959fe..f9bbc61 100644 --- a/library/rpi_ws281x_wrap.c +++ b/library/rpi_ws281x_wrap.c @@ -13,6 +13,7 @@ #define SWIGPYTHON #endif +#define SWIG_PYTHON_THREADS #define SWIG_PYTHON_DIRECTOR_NO_VTABLE /* ----------------------------------------------------------------------------- @@ -3132,6 +3133,12 @@ SWIG_FromCharPtr(const char *cptr) return &ws->channel[channelnum]; } + + ws2811_return_t ws2811_render_nogil(ws2811_t *ws2811) + { + return ws2811_render(ws2811); + } + #ifdef __cplusplus extern "C" { #endif @@ -4370,6 +4377,33 @@ SWIGINTERN PyObject *_wrap_ws2811_channel_get(PyObject *SWIGUNUSEDPARM(self), Py } +SWIGINTERN PyObject *_wrap_ws2811_render_nogil(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + ws2811_t *arg1 = (ws2811_t *) 0 ; + void *argp1 = 0 ; + int res1 = 0 ; + PyObject *swig_obj[1] ; + ws2811_return_t result; + + if (!args) SWIG_fail; + swig_obj[0] = args; + res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_ws2811_t, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "ws2811_render_nogil" "', argument " "1"" of type '" "ws2811_t *""'"); + } + arg1 = (ws2811_t *)(argp1); + { + SWIG_PYTHON_THREAD_BEGIN_ALLOW; + result = (ws2811_return_t)ws2811_render_nogil(arg1); + SWIG_PYTHON_THREAD_END_ALLOW; + } + resultobj = SWIG_From_int((int)(result)); + return resultobj; +fail: + return NULL; +} + + static PyMethodDef SwigMethods[] = { { "SWIG_PyInstanceMethod_New", SWIG_PyInstanceMethod_New, METH_O, NULL}, { "ws2811_channel_t_gpionum_set", _wrap_ws2811_channel_t_gpionum_set, METH_VARARGS, NULL}, @@ -4423,6 +4457,7 @@ static PyMethodDef SwigMethods[] = { { "ws2811_led_get", _wrap_ws2811_led_get, METH_VARARGS, NULL}, { "ws2811_led_set", _wrap_ws2811_led_set, METH_VARARGS, NULL}, { "ws2811_channel_get", _wrap_ws2811_channel_get, METH_VARARGS, NULL}, + { "ws2811_render_nogil", _wrap_ws2811_render_nogil, METH_O, NULL}, { NULL, NULL, 0, NULL } }; @@ -5263,6 +5298,9 @@ SWIG_init(void) { SWIG_Python_SetConstant(d, "WS2811_ERROR_SPI_SETUP",SWIG_From_int((int)(WS2811_ERROR_SPI_SETUP))); SWIG_Python_SetConstant(d, "WS2811_ERROR_SPI_TRANSFER",SWIG_From_int((int)(WS2811_ERROR_SPI_TRANSFER))); SWIG_Python_SetConstant(d, "WS2811_RETURN_STATE_COUNT",SWIG_From_int((int)(WS2811_RETURN_STATE_COUNT))); + + /* Initialize threading */ + SWIG_PYTHON_INITIALIZE_THREADS; #if PY_VERSION_HEX >= 0x03000000 return m; #else