From b85643f2b1fa435d98c8baef2ad91b2cfdfec4ef Mon Sep 17 00:00:00 2001 From: stephen322 Date: Tue, 3 Oct 2023 16:19:28 -0400 Subject: [PATCH 1/3] Wrap render in SWIG threads macro, to disable Python GIL Amended after running black formatting --- library/rpi_ws281x.i | 11 +++++++++ library/rpi_ws281x.py | 4 ++++ library/rpi_ws281x/rpi_ws281x.py | 7 ++++++ library/rpi_ws281x_wrap.c | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 60 insertions(+) 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..06f0edf 100644 --- a/library/rpi_ws281x/rpi_ws281x.py +++ b/library/rpi_ws281x/rpi_ws281x.py @@ -149,6 +149,13 @@ def show(self): str_resp = ws.ws2811_get_return_t_str(resp) raise RuntimeError('ws2811_render failed with code {0} ({1})'.format(resp, str_resp)) + def show_nogil(self): + """Update the display with the data from the LED buffer.""" + resp = ws.ws2811_render_nogil(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 From 0e1dfb1d7a2db4da54754a509eff7c605e655140 Mon Sep 17 00:00:00 2001 From: stephen322 Date: Wed, 4 Oct 2023 16:27:14 -0400 Subject: [PATCH 2/3] Update README.md (#1) Add build instructions, update c library url. SWIG build has '-threads' argument for recent GIL patch. --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) 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 +``` From f9451c0493db1b539bdce0567e4bd1fa93837708 Mon Sep 17 00:00:00 2001 From: stephen322 Date: Fri, 13 Oct 2023 21:00:31 -0400 Subject: [PATCH 3/3] Add option to release GIL during show/render; use mutex for added thread safety --- library/rpi_ws281x/rpi_ws281x.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/library/rpi_ws281x/rpi_ws281x.py b/library/rpi_ws281x/rpi_ws281x.py index 06f0edf..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,19 +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 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)) + 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_nogil(self): + def show(self): """Update the display with the data from the LED buffer.""" - resp = ws.ws2811_render_nogil(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).