From 85df6b588d5e20ff2e77b93428c593ffa3307af5 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Wed, 6 Jan 2021 23:35:34 +0000 Subject: [PATCH] LED strip refactor (#3158) `ws2812` buffer extracted to new `pixbuf` module. * The new pixbuf module has more functionality than the `ws2812`-specific buffer it replaces. * This is work in progress towards https://github.com/nodemcu/nodemcu-firmware/issues/2916 * The LED driver modules `ws2812`, `ws2801`, `apa102`, and `tm1829` have sprouted `pixbuf` support. * `NTest` tests for `pixbuf` now exist. While here, document the ws2812 UART-based overlapping with mainline execution. Fixes https://github.com/nodemcu/nodemcu-firmware/issues/3140 Co-authored-by: Gregor Hartmann --- app/include/user_modules.h | 1 + app/modules/apa102.c | 25 +- app/modules/pixbuf.c | 723 +++++++++++++++++++++++++++++++++++ app/modules/pixbuf.h | 50 +++ app/modules/tm1829.c | 58 +-- app/modules/ws2801.c | 21 +- app/modules/ws2812.c | 487 +---------------------- app/modules/ws2812.h | 55 --- app/modules/ws2812_effects.c | 202 +++++----- docs/modules/apa102.md | 5 +- docs/modules/pixbuf.md | 342 +++++++++++++++++ docs/modules/tm1829.md | 4 +- docs/modules/ws2812.md | 295 ++------------ tests/NTest_pixbuf.lua | 288 ++++++++++++++ tests/NTest_ws2812.lua | 4 +- tools/luacheck_config.lua | 11 + 16 files changed, 1632 insertions(+), 939 deletions(-) create mode 100644 app/modules/pixbuf.c create mode 100644 app/modules/pixbuf.h delete mode 100644 app/modules/ws2812.h create mode 100644 docs/modules/pixbuf.md create mode 100644 tests/NTest_pixbuf.lua diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 814b835a..3718027a 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -45,6 +45,7 @@ //#define LUA_USE_MODULES_PCM //#define LUA_USE_MODULES_PERF //#define LUA_USE_MODULES_PIPE +//#define LUA_USE_MODULES_PIXBUF //#define LUA_USE_MODULES_PWM //#define LUA_USE_MODULES_PWM2 //#define LUA_USE_MODULES_RFSWITCH diff --git a/app/modules/apa102.c b/app/modules/apa102.c index b17b67ac..253c2648 100644 --- a/app/modules/apa102.c +++ b/app/modules/apa102.c @@ -5,6 +5,8 @@ #include "platform.h" #include "user_interface.h" +#include "pixbuf.h" + #define NOP asm volatile(" nop \n\t") @@ -79,9 +81,26 @@ static int apa102_write(lua_State* L) { MOD_CHECK_ID(gpio, clock_pin); uint32_t alt_clock_pin = pin_num[clock_pin]; - size_t buf_len; - const char *buf = luaL_checklstring(L, 3, &buf_len); - uint32_t nbr_frames = buf_len / 4; + const char *buf; + uint32_t nbr_frames; + + switch(lua_type(L, 3)) { + case LUA_TSTRING: { + size_t buf_len; + buf = luaL_checklstring(L, 3, &buf_len); + nbr_frames = buf_len / 4; + break; + } + case LUA_TUSERDATA: { + pixbuf *buffer = pixbuf_from_lua_arg(L, 3); + luaL_argcheck(L, buffer->nchan == 4, 3, "Pixbuf not 4-channel"); + buf = (const char *)buffer->values; + nbr_frames = buffer->npix; + break; + } + default: + return luaL_argerror(L, 3, "String or pixbuf expected"); + } if (nbr_frames > 100000) { return luaL_error(L, "The supplied buffer is too long, and might cause the callback watchdog to bark."); diff --git a/app/modules/pixbuf.c b/app/modules/pixbuf.c new file mode 100644 index 00000000..5ab40a6e --- /dev/null +++ b/app/modules/pixbuf.c @@ -0,0 +1,723 @@ +#include "module.h" +#include "lauxlib.h" + +#include + +#include "pixbuf.h" +#define PIXBUF_METATABLE "pixbuf.buf" + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +pixbuf *pixbuf_from_lua_arg(lua_State *L, int arg) { + return luaL_checkudata(L, arg, PIXBUF_METATABLE); +} + +pixbuf *pixbuf_opt_from_lua_arg(lua_State *L, int arg) { + return luaL_testudata(L, arg, PIXBUF_METATABLE); +} + +static ssize_t posrelat(ssize_t pos, size_t len) { + /* relative string position: negative means back from end */ + if (pos < 0) + pos += (ssize_t)len + 1; + return MIN(MAX(pos, 1), len); +} + +const size_t pixbuf_channels(pixbuf *p) { + return p->nchan; +} + +const size_t pixbuf_size(pixbuf *p) { + return p->npix * p->nchan; +} + +/* + * Construct a pixbuf newuserdata using C arguments. + * + * Allocates, so may throw! Leaves new buffer at the top of the Lua stack + * and returns a C pointer. + */ +static pixbuf *pixbuf_new(lua_State *L, size_t leds, size_t chans) { + // Allocate memory + + // A crude hack of an overflow check, but unlikely to be reached in practice + if ((leds > 8192) || (chans > 32)) { + luaL_error(L, "pixbuf size limits exeeded"); + return NULL; // UNREACHED + } + + size_t size = sizeof(pixbuf) + leds * chans; + + pixbuf *buffer = (pixbuf*)lua_newuserdata(L, size); + + // Associate its metatable + luaL_getmetatable(L, PIXBUF_METATABLE); + lua_setmetatable(L, -2); + + // Save led strip size + *(size_t *)&buffer->npix = leds; + *(size_t *)&buffer->nchan = chans; + + memset(buffer->values, 0, leds * chans); + + return buffer; +} + +// Handle a buffer where we can store led values +int pixbuf_new_lua(lua_State *L) { + const int leds = luaL_checkint(L, 1); + const int chans = luaL_checkint(L, 2); + + luaL_argcheck(L, leds > 0, 1, "should be a positive integer"); + luaL_argcheck(L, chans > 0, 2, "should be a positive integer"); + + pixbuf_new(L, leds, chans); + return 1; +} + +static int pixbuf_concat_lua(lua_State *L) { + pixbuf *lhs = pixbuf_from_lua_arg(L, 1); + pixbuf *rhs = pixbuf_from_lua_arg(L, 2); + + luaL_argcheck(L, lhs->nchan == rhs->nchan, 1, + "can only concatenate buffers with same channel count"); + + size_t osize = lhs->npix + rhs->npix; + if (lhs->npix > osize) { + return luaL_error(L, "size sum overflow"); + } + + pixbuf *buffer = pixbuf_new(L, osize, lhs->nchan); + + memcpy(buffer->values, lhs->values, pixbuf_size(lhs)); + memcpy(buffer->values + pixbuf_size(lhs), rhs->values, pixbuf_size(rhs)); + + return 1; +} + +static int pixbuf_channels_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + lua_pushinteger(L, buffer->nchan); + return 1; +} + +static int pixbuf_dump_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + lua_pushlstring(L, (char*)buffer->values, pixbuf_size(buffer)); + return 1; +} + +static int pixbuf_eq_lua(lua_State *L) { + bool res; + + pixbuf *lhs = pixbuf_from_lua_arg(L, 1); + pixbuf *rhs = pixbuf_from_lua_arg(L, 2); + + if (lhs->npix != rhs->npix) { + res = false; + } else if (lhs->nchan != rhs->nchan) { + res = false; + } else { + res = true; + for(size_t i = 0; i < pixbuf_size(lhs); i++) { + if(lhs->values[i] != rhs->values[i]) { + res = false; + break; + } + } + } + + lua_pushboolean(L, res); + return 1; +} + +static int pixbuf_fade_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + const int fade = luaL_checkinteger(L, 2); + unsigned direction = luaL_optinteger( L, 3, PIXBUF_FADE_OUT ); + + luaL_argcheck(L, fade > 0, 2, "fade value should be a strictly positive int"); + + uint8_t *p = &buffer->values[0]; + for (size_t i = 0; i < pixbuf_size(buffer); i++) + { + if (direction == PIXBUF_FADE_OUT) + { + *p++ /= fade; + } + else + { + // as fade in can result in value overflow, an int is used to perform the check afterwards + int val = *p * fade; + *p++ = MIN(255, val); + } + } + + return 0; +} + +/* Fade an Ixxx-type strip by just manipulating the I bytes */ +static int pixbuf_fadeI_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + const int fade = luaL_checkinteger(L, 2); + unsigned direction = luaL_optinteger( L, 3, PIXBUF_FADE_OUT ); + + luaL_argcheck(L, fade > 0, 2, "fade value should be a strictly positive int"); + + uint8_t *p = &buffer->values[0]; + for (size_t i = 0; i < buffer->npix; i++, p+=buffer->nchan) { + if (direction == PIXBUF_FADE_OUT) { + *p /= fade; + } else { + int val = *p * fade; + *p++ = MIN(255, val); + } + } + + return 0; +} + +static int pixbuf_fill_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + + if (buffer->npix == 0) { + goto out; + } + + if (lua_gettop(L) != (1 + buffer->nchan)) { + return luaL_argerror(L, 1, "need as many values as colors per pixel"); + } + + /* Fill the first pixel from the Lua stack */ + for (size_t i = 0; i < buffer->nchan; i++) { + buffer->values[i] = luaL_checkinteger(L, 2+i); + } + + /* Fill the rest of the pixels from the first */ + for (size_t i = 1; i < buffer->npix; i++) { + memcpy(&buffer->values[i * buffer->nchan], buffer->values, buffer->nchan); + } + +out: + lua_settop(L, 1); + return 1; +} + +static int pixbuf_get_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + const int led = luaL_checkinteger(L, 2) - 1; + size_t channels = buffer->nchan; + + luaL_argcheck(L, led >= 0 && led < buffer->npix, 2, "index out of range"); + + uint8_t tmp[channels]; + memcpy(tmp, &buffer->values[channels*led], channels); + + for (size_t i = 0; i < channels; i++) + { + lua_pushinteger(L, tmp[i]); + } + + return channels; +} + +/* :map(f, buf1, ilo, ihi, [buf2, ilo2]) */ +static int pixbuf_map_lua(lua_State *L) { + pixbuf *outbuf = pixbuf_from_lua_arg(L, 1); + /* f at index 2 */ + + pixbuf *buffer1 = pixbuf_opt_from_lua_arg(L, 3); + if (!buffer1) + buffer1 = outbuf; + + const int ilo = posrelat(luaL_optinteger(L, 4, 1), buffer1->npix) - 1; + const int ihi = posrelat(luaL_optinteger(L, 5, buffer1->npix), buffer1->npix) - 1; + + luaL_argcheck(L, ihi > ilo, 3, "Buffer limits out of order"); + + size_t npix = ihi - ilo + 1; + + luaL_argcheck(L, npix == outbuf->npix, 1, "Output buffer wrong size"); + + pixbuf *buffer2 = pixbuf_opt_from_lua_arg(L, 6); + const int ilo2 = buffer2 ? posrelat(luaL_optinteger(L, 7, 1), buffer2->npix) - 1 : 0; + + if (buffer2) { + luaL_argcheck(L, ilo2 + npix <= buffer2->npix, 6, "Second buffer too short"); + } + + for (size_t p = 0; p < npix; p++) { + lua_pushvalue(L, 2); + for (size_t c = 0; c < buffer1->nchan; c++) { + lua_pushinteger(L, buffer1->values[(ilo + p) * buffer1->nchan + c]); + } + if (buffer2) { + for (size_t c = 0; c < buffer2->nchan; c++) { + lua_pushinteger(L, buffer2->values[(ilo2 + p) * buffer2->nchan + c]); + } + } + lua_call(L, buffer1->nchan + (buffer2 ? buffer2->nchan : 0), outbuf->nchan); + for (size_t c = 0; c < outbuf->nchan; c++) { + outbuf->values[(p + 1) * outbuf->nchan - c - 1] = luaL_checkinteger(L, -1); + lua_pop(L, 1); + } + } + + lua_settop(L, 1); + return 1; +} + +struct mix_source { + int factor; + const uint8_t *values; +}; + +static uint32_t pixbuf_mix_clamp(int32_t v) { + if (v < 0) { return 0; } + if (v > 255) { return 255; } + return v; +} + +/* This one can sum straightforwardly, channel by channel */ +static void pixbuf_mix_raw(pixbuf *out, size_t n_src, struct mix_source* src) { + size_t cells = pixbuf_size(out); + + for (size_t c = 0; c < cells; c++) { + int32_t val = 0; + for (size_t s = 0; s < n_src; s++) { + val += (int32_t)src[s].values[c] * src[s].factor; + } + + val += 128; // rounding instead of floor + val /= 256; // do not use implemetation dependant right shift + + out->values[c] = (uint8_t)pixbuf_mix_clamp(val); + } +} + +/* Mix intensity-mediated three-color pixbufs. + * + * XXX This is untested in real hardware; do they actually behave like this? + */ +static void pixbuf_mix_i3(pixbuf *out, size_t ibits, size_t n_src, + struct mix_source* src) { + for(size_t p = 0; p < out->npix; p++) { + int32_t sums[3] = { 0, 0, 0 }; + + for (size_t s = 0; s < n_src; s++) { + for (size_t c = 0; c < 3; c++) { + sums[c] += (int32_t)src[s].values[4*p+c+1] // color channel + * src[s].values[4*p] // global intensity + * src[s].factor; // user factor + + } + } + + uint32_t pmaxc = 0; + for (size_t c = 0; c < 3; c++) { + pmaxc = sums[c] > pmaxc ? sums[c] : pmaxc; + } + + size_t maxgi; + if (pmaxc == 0) { + /* Zero value */ + memset(&out->values[4*p], 0, 4); + return; + } else if (pmaxc <= (1 << 16)) { + /* Minimum global factor */ + maxgi = 1; + } else if (pmaxc >= ((1 << ibits) - 1) << 16) { + /* Maximum global factor */ + maxgi = (1 << ibits) - 1; + } else { + maxgi = (pmaxc >> 16) + 1; + } + + // printf("mixi3: %x %x %x -> %x, %zx\n", sums[0], sums[1], sums[2], pmaxc, maxgi); + + out->values[4*p] = maxgi; + for (size_t c = 0; c < 3; c++) { + out->values[4*p+c+1] = pixbuf_mix_clamp((sums[c] + 256 * maxgi - 127) / (256 * maxgi)); + } + } +} + +// buffer:mix(factor1, buffer1, ..) +// factor is 256 for 100% +// uses saturating arithmetic (one buffer at a time) +static int pixbuf_mix_core(lua_State *L, size_t ibits) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + pixbuf *src_buffer; + + int pos = 2; + size_t n_sources = (lua_gettop(L) - 1) / 2; + struct mix_source sources[n_sources]; + + if (n_sources == 0) { + lua_settop(L, 1); + return 1; + } + + for (size_t src = 0; src < n_sources; src++, pos += 2) { + int factor = luaL_checkinteger(L, pos); + src_buffer = pixbuf_from_lua_arg(L, pos + 1); + + luaL_argcheck(L, src_buffer->npix == buffer->npix && + src_buffer->nchan == buffer->nchan, + pos + 1, "buffer not same size or shape"); + + sources[src].factor = factor; + sources[src].values = src_buffer->values; + } + + if (ibits != 0) { + luaL_argcheck(L, src_buffer->nchan == 4, 2, "Requires 4 channel pixbuf"); + pixbuf_mix_i3(buffer, ibits, n_sources, sources); + } else { + pixbuf_mix_raw(buffer, n_sources, sources); + } + + lua_settop(L, 1); + return 1; +} + +static int pixbuf_mix_lua(lua_State *L) { + return pixbuf_mix_core(L, 0); +} + +static int pixbuf_mix4I5_lua(lua_State *L) { + return pixbuf_mix_core(L, 5); +} + + +// Returns the total of all channels +static int pixbuf_power_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + + int total = 0; + size_t p = 0; + for (size_t i = 0; i < buffer->npix; i++) { + for (size_t j = 0; j < buffer->nchan; j++, p++) { + total += buffer->values[p]; + } + } + + lua_pushinteger(L, total); + return 1; +} + +// Returns the total of all channels, intensity-style +static int pixbuf_powerI_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + + int total = 0; + size_t p = 0; + for (size_t i = 0; i < buffer->npix; i++) { + int inten = buffer->values[p++]; + for (size_t j = 0; j < buffer->nchan - 1; j++, p++) { + total += inten * buffer->values[p]; + } + } + + lua_pushinteger(L, total); + return 1; +} + +static int pixbuf_replace_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), buffer->npix); + size_t channels = buffer->nchan; + + uint8_t *src; + size_t srcLen; + + if (lua_type(L, 2) == LUA_TSTRING) { + size_t length; + src = (uint8_t *) lua_tolstring(L, 2, &length); + srcLen = length / channels; + } else { + pixbuf *rhs = pixbuf_from_lua_arg(L, 2); + luaL_argcheck(L, rhs->nchan == buffer->nchan, 2, "buffers have different channels"); + src = rhs->values; + srcLen = rhs->npix; + } + + luaL_argcheck(L, srcLen + start - 1 <= buffer->npix, 2, "does not fit into destination"); + + memcpy(buffer->values + (start - 1) * channels, src, srcLen * channels); + + return 0; +} + +static int pixbuf_set_lua(lua_State *L) { + + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + const int led = luaL_checkinteger(L, 2) - 1; + const size_t channels = buffer->nchan; + + luaL_argcheck(L, led >= 0 && led < buffer->npix, 2, "index out of range"); + + int type = lua_type(L, 3); + if(type == LUA_TTABLE) + { + for (size_t i = 0; i < channels; i++) + { + lua_rawgeti(L, 3, i+1); + buffer->values[channels*led+i] = lua_tointeger(L, -1); + lua_pop(L, 1); + } + } + else if(type == LUA_TSTRING) + { + size_t len; + const char *buf = lua_tolstring(L, 3, &len); + + // Overflow check + if( channels*led + len > channels*buffer->npix ) { + return luaL_error(L, "string size will exceed strip length"); + } + if ( len % channels != 0 ) { + return luaL_error(L, "string does not contain whole LEDs"); + } + + memcpy(&buffer->values[channels*led], buf, len); + } + else + { + luaL_argcheck(L, lua_gettop(L) <= 2 + channels, 2 + channels, + "extra values given"); + + for (size_t i = 0; i < channels; i++) + { + buffer->values[channels*led+i] = luaL_checkinteger(L, 3+i); + } + } + + lua_settop(L, 1); + return 1; +} + +static void pixbuf_shift_circular(pixbuf *buffer, struct pixbuf_shift_params *sp) { + /* Move a buffer of pixels per iteration; loop repeatedly if needed */ + uint8_t tmpbuf[32]; + uint8_t *v = buffer->values; + size_t shiftRemaining = sp->shift; + size_t cursor = sp->offset; + + do { + size_t shiftNow = MIN(shiftRemaining, sizeof tmpbuf); + + if (sp->shiftLeft) { + memcpy(tmpbuf, &v[cursor], shiftNow); + memmove(&v[cursor], &v[cursor+shiftNow], sp->window - shiftNow); + memcpy(&v[cursor+sp->window-shiftNow], tmpbuf, shiftNow); + } else { + memcpy(tmpbuf, &v[cursor+sp->window-shiftNow], shiftNow); + memmove(&v[cursor+shiftNow], &v[cursor], sp->window - shiftNow); + memcpy(&v[cursor], tmpbuf, shiftNow); + } + + cursor += shiftNow; + shiftRemaining -= shiftNow; + } while(shiftRemaining > 0); +} + +static void pixbuf_shift_logical(pixbuf *buffer, struct pixbuf_shift_params *sp) { + /* Logical shifts don't require a temporary buffer, so we just move bytes */ + uint8_t *v = buffer->values; + + if (sp->shiftLeft) { + memmove(&v[sp->offset], &v[sp->offset+sp->shift], sp->window - sp->shift); + bzero(&v[sp->offset+sp->window-sp->shift], sp->shift); + } else { + memmove(&v[sp->offset+sp->shift], &v[sp->offset], sp->window - sp->shift); + bzero(&v[sp->offset], sp->shift); + } +} + +void pixbuf_shift(pixbuf *b, struct pixbuf_shift_params *sp) { +#if 0 + printf("Pixbuf %p shifting %s %s by %zd from %zd with window %zd\n", + b, + sp->shiftLeft ? "left" : "right", + sp->type == PIXBUF_SHIFT_LOGICAL ? "logically" : "circularly", + sp->shift, sp->offset, sp->window); +#endif + + switch(sp->type) { + case PIXBUF_SHIFT_LOGICAL: return pixbuf_shift_logical(b, sp); + case PIXBUF_SHIFT_CIRCULAR: return pixbuf_shift_circular(b, sp); + } +} + +int pixbuf_shift_lua(lua_State *L) { + struct pixbuf_shift_params sp; + + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + const int shift_shift = luaL_checkinteger(L, 2) * buffer->nchan; + const unsigned shift_type = luaL_optinteger(L, 3, PIXBUF_SHIFT_LOGICAL); + const int pos_start = posrelat(luaL_optinteger(L, 4, 1), buffer->npix); + const int pos_end = posrelat(luaL_optinteger(L, 5, -1), buffer->npix); + + if (shift_shift < 0) { + sp.shiftLeft = true; + sp.shift = -shift_shift; + } else { + sp.shiftLeft = false; + sp.shift = shift_shift; + } + + switch(shift_type) { + case PIXBUF_SHIFT_LOGICAL: + case PIXBUF_SHIFT_CIRCULAR: + sp.type = shift_type; + break; + default: + return luaL_argerror(L, 3, "invalid shift type"); + } + + if (pos_start < 1) { + return luaL_argerror(L, 4, "start position must be >= 1"); + } + + if (pos_end < pos_start) { + return luaL_argerror(L, 5, "end position must be >= start"); + } + + sp.offset = (pos_start - 1) * buffer->nchan; + sp.window = (pos_end - pos_start + 1) * buffer->nchan; + + if (sp.shift > pixbuf_size(buffer)) { + return luaL_argerror(L, 2, "shifting more elements than buffer size"); + } + + if (sp.shift > sp.window) { + return luaL_argerror(L, 2, "shifting more than sliced window"); + } + + pixbuf_shift(buffer, &sp); + + return 0; +} + +/* XXX for backwards-compat with ws2812_effects; deprecated and should be removed */ +void pixbuf_prepare_shift(pixbuf *buffer, struct pixbuf_shift_params *sp, + int shift, enum pixbuf_shift type, int start, int end) +{ + start = posrelat(start, buffer->npix); + end = posrelat(end, buffer->npix); + + lua_assert((end > start) && (start > 0) && (end < buffer->npix)); + + sp->type = type; + sp->offset = (start - 1) * buffer->nchan; + sp->window = (end - start + 1) * buffer->nchan; + + if (shift < 0) { + sp->shiftLeft = true; + sp->shift = -shift * buffer->nchan; + } else { + sp->shiftLeft = false; + sp->shift = shift * buffer->nchan; + } +} + +static int pixbuf_size_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + lua_pushinteger(L, buffer->npix); + return 1; +} + +static int pixbuf_sub_lua(lua_State *L) { + pixbuf *lhs = pixbuf_from_lua_arg(L, 1); + size_t l = lhs->npix; + ssize_t start = posrelat(luaL_checkinteger(L, 2), l); + ssize_t end = posrelat(luaL_optinteger(L, 3, -1), l); + if (start <= end) { + pixbuf *result = pixbuf_new(L, end - start + 1, lhs->nchan); + memcpy(result->values, lhs->values + lhs->nchan * (start - 1), + lhs->nchan * (end - start + 1)); + return 1; + } else { + pixbuf_new(L, 0, lhs->nchan); + return 1; + } +} + +static int pixbuf_tostring_lua(lua_State *L) { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + + luaL_Buffer result; + luaL_buffinit(L, &result); + + luaL_addchar(&result, '['); + int p = 0; + for (size_t i = 0; i < buffer->npix; i++) { + if (i > 0) { + luaL_addchar(&result, ','); + } + luaL_addchar(&result, '('); + for (size_t j = 0; j < buffer->nchan; j++, p++) { + if (j > 0) { + luaL_addchar(&result, ','); + } + char numbuf[5]; + sprintf(numbuf, "%d", buffer->values[p]); + luaL_addstring(&result, numbuf); + } + luaL_addchar(&result, ')'); + } + + luaL_addchar(&result, ']'); + luaL_pushresult(&result); + + return 1; +} + +LROT_BEGIN(pixbuf_map, NULL, LROT_MASK_INDEX | LROT_MASK_EQ) + LROT_TABENTRY ( __index, pixbuf_map ) + LROT_FUNCENTRY( __eq, pixbuf_eq_lua ) + + LROT_FUNCENTRY( __concat, pixbuf_concat_lua ) + LROT_FUNCENTRY( __tostring, pixbuf_tostring_lua ) + + LROT_FUNCENTRY( channels, pixbuf_channels_lua ) + LROT_FUNCENTRY( dump, pixbuf_dump_lua ) + LROT_FUNCENTRY( fade, pixbuf_fade_lua ) + LROT_FUNCENTRY( fadeI, pixbuf_fadeI_lua ) + LROT_FUNCENTRY( fill, pixbuf_fill_lua ) + LROT_FUNCENTRY( get, pixbuf_get_lua ) + LROT_FUNCENTRY( replace, pixbuf_replace_lua ) + LROT_FUNCENTRY( map, pixbuf_map_lua ) + LROT_FUNCENTRY( mix, pixbuf_mix_lua ) + LROT_FUNCENTRY( mix4I5, pixbuf_mix4I5_lua ) + LROT_FUNCENTRY( power, pixbuf_power_lua ) + LROT_FUNCENTRY( powerI, pixbuf_powerI_lua ) + LROT_FUNCENTRY( set, pixbuf_set_lua ) + LROT_FUNCENTRY( shift, pixbuf_shift_lua ) + LROT_FUNCENTRY( size, pixbuf_size_lua ) + LROT_FUNCENTRY( sub, pixbuf_sub_lua ) +LROT_END(pixbuf_map, NULL, LROT_MASK_INDEX | LROT_MASK_EQ) + +LROT_BEGIN(pixbuf, NULL, 0) + LROT_NUMENTRY( FADE_IN, PIXBUF_FADE_IN ) + LROT_NUMENTRY( FADE_OUT, PIXBUF_FADE_OUT ) + + LROT_NUMENTRY( SHIFT_CIRCULAR, PIXBUF_SHIFT_CIRCULAR ) + LROT_NUMENTRY( SHIFT_LOGICAL, PIXBUF_SHIFT_LOGICAL ) + + LROT_FUNCENTRY( newBuffer, pixbuf_new_lua ) +LROT_END(pixbuf, NULL, 0) + +int luaopen_pixbuf(lua_State *L) { + luaL_rometatable(L, PIXBUF_METATABLE, LROT_TABLEREF(pixbuf_map)); + lua_pushrotable(L, LROT_TABLEREF(pixbuf)); + return 1; +} + +NODEMCU_MODULE(PIXBUF, "pixbuf", pixbuf, luaopen_pixbuf); diff --git a/app/modules/pixbuf.h b/app/modules/pixbuf.h new file mode 100644 index 00000000..6b7ff27d --- /dev/null +++ b/app/modules/pixbuf.h @@ -0,0 +1,50 @@ +#ifndef APP_MODULES_PIXBUF_H_ +#define APP_MODULES_PIXBUF_H_ + +typedef struct pixbuf { + const size_t npix; + const size_t nchan; + + /* Flexible Array Member; true size is npix * pixbuf_channels_for(type) */ + uint8_t values[]; +} pixbuf; + +enum pixbuf_fade { + PIXBUF_FADE_IN, + PIXBUF_FADE_OUT +}; + +enum pixbuf_shift { + PIXBUF_SHIFT_LOGICAL, + PIXBUF_SHIFT_CIRCULAR +}; + +pixbuf *pixbuf_from_lua_arg(lua_State *, int); +const size_t pixbuf_size(pixbuf *); + +// Exported for backwards compat with ws2812 module +int pixbuf_new_lua(lua_State *); + +/* + * WS2812_EFFECTS does pixbuf manipulation directly in C, which isn't the + * intended use case, but for backwards compat, we export just what it needs. + * Move this struct to pixbuf.c and mark these exports static instead once + * WS2812_EFFECTS is no more. + */ +struct pixbuf_shift_params { + enum pixbuf_shift type; + // 0 <= offset <= buffer length + size_t offset; + // 0 <= window + offset <= buffer length + size_t window; + // 0 <= shift <= window_size + size_t shift; + bool shiftLeft; +}; +void pixbuf_shift(pixbuf *, struct pixbuf_shift_params *); +void pixbuf_prepare_shift(pixbuf *, struct pixbuf_shift_params *, + int val, enum pixbuf_shift, int start, int end); +const size_t pixbuf_channels(pixbuf *); +/* end WS2812_EFFECTS exports */ + +#endif diff --git a/app/modules/tm1829.c b/app/modules/tm1829.c index 0b1713b2..30ff6a13 100644 --- a/app/modules/tm1829.c +++ b/app/modules/tm1829.c @@ -5,6 +5,8 @@ #include #include "user_interface.h" +#include "pixbuf.h" + static inline uint32_t _getCycleCount(void) { uint32_t cycles; __asm__ __volatile__("rsr %0,ccount":"=a" (cycles)); @@ -13,8 +15,9 @@ static inline uint32_t _getCycleCount(void) { // This algorithm reads the cpu clock cycles to calculate the correct // pulse widths. It works in both 80 and 160 MHz mode. -static void ICACHE_RAM_ATTR tm1829_write_to_pin(uint8_t pin, uint8_t *pixels, uint32_t length) { - uint8_t *p, *end; +static void ICACHE_RAM_ATTR tm1829_write_to_pin(uint8_t pin, const uint8_t *pixels, size_t length) { + const uint8_t *p, *end; + uint8_t phasergb = 0; p = pixels; end = p + length; @@ -28,6 +31,13 @@ static void ICACHE_RAM_ATTR tm1829_write_to_pin(uint8_t pin, uint8_t *pixels, ui register int i; register uint8_t pixel = *p++; + if ((phasergb == 0) && (pixel == 0xFF)) { + // clamp initial byte value to avoid constant-current shenanigans. Yuck! + pixel = 0xFE; + } + if (++phasergb == 3) { + phasergb = 0; + } ets_intr_lock(); @@ -55,35 +65,27 @@ static void ICACHE_RAM_ATTR tm1829_write_to_pin(uint8_t pin, uint8_t *pixels, ui } // Lua: tm1829.write(pin, "string") -// Byte triples in the string are interpreted as R G B values and sent to the hardware as G R B. -// WARNING: this function scrambles the input buffer : -// a = string.char(255,0,128) -// tm1829.write(3,a) -// =a.byte() -// (0,255,128) +// Byte triples in the string are interpreted as GRB values. static int ICACHE_FLASH_ATTR tm1829_write(lua_State* L) { const uint8_t pin = luaL_checkinteger(L, 1); + const uint8_t *pixels; size_t length; - const char *rgb = luaL_checklstring(L, 2, &length); - // dont modify lua-internal lstring - make a copy instead - char *buffer = (char *)malloc(length); - - // Ignore incomplete Byte triples at the end of buffer - length -= length % 3; - - // Copy payload and make sure first byte is < 0xFF (triggers - // constant current command, instead of PWM duty command) - size_t i; - for (i = 0; i < length; i += 3) { - buffer[i] = rgb[i]; - buffer[i + 1] = rgb[i + 1]; - buffer[i + 2] = rgb[i + 2]; - - // Check for first byte - if (buffer[i] == 0xff) - buffer[i] = 0xfe; + switch(lua_type(L, 3)) { + case LUA_TSTRING: { + pixels = luaL_checklstring(L, 2, &length); + break; + } + case LUA_TUSERDATA: { + pixbuf *buffer = pixbuf_from_lua_arg(L, 2); + luaL_argcheck(L, pixbuf_channels(buffer) == 3, 2, "Bad pixbuf format"); + pixels = buffer->values; + length = 3 * buffer->npix; + break; + } + default: + return luaL_argerror(L, 2, "String or pixbuf expected"); } // Initialize the output pin and wait a bit @@ -91,12 +93,10 @@ static int ICACHE_FLASH_ATTR tm1829_write(lua_State* L) platform_gpio_write(pin, 1); // Send the buffer - tm1829_write_to_pin(pin_num[pin], (uint8_t*) buffer, length); + tm1829_write_to_pin(pin_num[pin], pixels, length); os_delay_us(500); // reset time - free(buffer); - return 0; } diff --git a/app/modules/ws2801.c b/app/modules/ws2801.c index 45bc90a7..7354554f 100644 --- a/app/modules/ws2801.c +++ b/app/modules/ws2801.c @@ -5,6 +5,8 @@ #include #include "osapi.h" +#include "pixbuf.h" + /** * Code is based on https://github.com/CHERTS/esp8266-devkit/blob/master/Espressif/examples/EspLightNode/user/ws2801.c * and provides a similar api as the ws2812 module. @@ -110,13 +112,28 @@ static int ICACHE_FLASH_ATTR ws2801_init_lua(lua_State* L) { */ static int ICACHE_FLASH_ATTR ws2801_writergb(lua_State* L) { size_t length; - const char *buffer = luaL_checklstring(L, 1, &length); + const uint8_t *values; + + switch(lua_type(L,1)) { + case LUA_TSTRING: + values = (const uint8_t*) luaL_checklstring(L, 1, &length); + break; + case LUA_TUSERDATA: { + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + luaL_argcheck(L, buffer->nchan == 3, 1, "Pixbuf not 3-channel"); + values = buffer->values; + length = pixbuf_size(buffer); + break; + } + default: + return luaL_argerror(L, 1, "pixbuf or string expected"); + } os_delay_us(10); ets_intr_lock(); - ws2801_strip(buffer, length); + ws2801_strip(values, length); ets_intr_unlock(); diff --git a/app/modules/ws2812.c b/app/modules/ws2812.c index ca54b2cd..534d4399 100644 --- a/app/modules/ws2812.c +++ b/app/modules/ws2812.c @@ -9,13 +9,11 @@ #include "driver/uart.h" #include "osapi.h" -#include "ws2812.h" +#include "pixbuf.h" -#define CANARY_VALUE 0x32383132 #define MODE_SINGLE 0 #define MODE_DUAL 1 - // Init UART1 to be able to stream WS2812 data to GPIO2 pin // If DUAL mode is selected, init UART0 to stream to TXD0 as well // You HAVE to redirect LUA's output somewhere else @@ -125,14 +123,14 @@ static int ws2812_write(lua_State* L) { } else if (type == LUA_TUSERDATA) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - + pixbuf *buffer = pixbuf_from_lua_arg(L, 1); + luaL_argcheck(L, pixbuf_channels(buffer) == 3, 1, "Bad pixbuf format"); buffer1 = buffer->values; - length1 = buffer->colorsPerLed*buffer->size; + length1 = pixbuf_size(buffer); } else { - luaL_argerror(L, 1, "ws2812.buffer or string expected"); + luaL_argerror(L, 1, "pixbuf or string expected"); } // Second optionnal parameter @@ -148,14 +146,14 @@ static int ws2812_write(lua_State* L) { } else if (type == LUA_TUSERDATA) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer"); - + pixbuf *buffer = pixbuf_from_lua_arg(L, 2); + luaL_argcheck(L, pixbuf_channels(buffer) == 3, 2, "Bad pixbuf format"); buffer2 = buffer->values; - length2 = buffer->colorsPerLed*buffer->size; + length2 = pixbuf_size(buffer); } else { - luaL_argerror(L, 2, "ws2812.buffer or string expected"); + luaL_argerror(L, 2, "pixbuf or string expected"); } // Send the buffers @@ -164,475 +162,20 @@ static int ws2812_write(lua_State* L) { return 0; } -static ptrdiff_t posrelat(ptrdiff_t pos, size_t len) { - /* relative string position: negative means back from end */ - if (pos < 0) pos += (ptrdiff_t)len + 1; - return MIN(MAX(pos, 1), len); -} - -static ws2812_buffer *allocate_buffer(lua_State *L, int leds, int colorsPerLed) { - // Allocate memory - size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds; - ws2812_buffer * buffer = (ws2812_buffer*)lua_newuserdata(L, size); - - // Associate its metatable - luaL_getmetatable(L, "ws2812.buffer"); - lua_setmetatable(L, -2); - - // Save led strip size - buffer->size = leds; - buffer->colorsPerLed = colorsPerLed; - - return buffer; -} - - -// Handle a buffer where we can store led values -static int ws2812_new_buffer(lua_State *L) { - const int leds = luaL_checkint(L, 1); - const int colorsPerLed = luaL_checkint(L, 2); - - luaL_argcheck(L, leds > 0, 1, "should be a positive integer"); - luaL_argcheck(L, colorsPerLed > 0, 2, "should be a positive integer"); - - ws2812_buffer * buffer = allocate_buffer(L, leds, colorsPerLed); - - memset(buffer->values, 0, colorsPerLed * leds); - - return 1; -} - - -int ws2812_buffer_fill(ws2812_buffer * buffer, int * colors) { - - // Grab colors - int i, j; - - // Fill buffer - uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) - { - for (j = 0; j < buffer->colorsPerLed; j++) - { - *p++ = colors[j]; - } - } - - return 0; -} - -static int ws2812_buffer_fill_lua(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - - // Grab colors - int i; - int * colors = luaM_malloc(L, buffer->colorsPerLed * sizeof(int)); - - for (i = 0; i < buffer->colorsPerLed; i++) - { - colors[i] = luaL_checkinteger(L, 2+i); - } - - ws2812_buffer_fill(buffer, colors); - - // Free memory - luaM_free(L, colors); - - return 0; -} - -void ws2812_buffer_fade(ws2812_buffer * buffer, int fade, unsigned direction) { - uint8_t * p = &buffer->values[0]; - int val = 0; - int i; - for (i = 0; i < buffer->size * buffer->colorsPerLed; i++) - { - if (direction == FADE_OUT) - { - *p++ /= fade; - } - else - { - // as fade in can result in value overflow, an int is used to perform the check afterwards - val = *p * fade; - if (val > 255) val = 255; - *p++ = val; - } - } -} - -static int ws2812_buffer_fade_lua(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - const int fade = luaL_checkinteger(L, 2); - unsigned direction = luaL_optinteger( L, 3, FADE_OUT ); - - luaL_argcheck(L, fade > 0, 2, "fade value should be a strict positive number"); - - ws2812_buffer_fade(buffer, fade, direction); - - return 0; -} - -int ws2812_buffer_shift(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end){ - - ws2812_buffer_shift_prepare* prepare = ws2812_buffer_get_shift_prepare(L, buffer, shiftValue, shift_type, pos_start, pos_end); - ws2812_buffer_shift_prepared(prepare); - // Free memory - luaM_freemem(L, prepare, sizeof(ws2812_buffer_shift_prepare) + prepare->shift_len); - - return 0; -} - -ws2812_buffer_shift_prepare* ws2812_buffer_get_shift_prepare(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end){ - - ptrdiff_t start = posrelat(pos_start, buffer->size); - ptrdiff_t end = posrelat(pos_end, buffer->size); - - start--; - int size = end - start; - size_t offset = start * buffer->colorsPerLed; - - luaL_argcheck(L, shiftValue >= 0-size && shiftValue <= size, 2, "shifting more elements than buffer size"); - - int shift = shiftValue >= 0 ? shiftValue : -shiftValue; - - size_t shift_len, remaining_len; - // calculate length of shift section and remaining section - shift_len = shift*buffer->colorsPerLed; - remaining_len = (size-shift)*buffer->colorsPerLed; - - ws2812_buffer_shift_prepare* prepare = luaM_malloc(L, sizeof(ws2812_buffer_shift_prepare) + shift_len); - prepare->offset = offset; - prepare->tmp_pixels = (uint8_t*)(prepare+1); - prepare->shiftValue = shiftValue; - prepare->shift_len = shift_len; - prepare->remaining_len = remaining_len; - prepare->shift_type = shift_type; - prepare->buffer = buffer; - - return prepare; -} - -void ws2812_buffer_shift_prepared(ws2812_buffer_shift_prepare* prepare) { - - // check if we want to shift at all - if (prepare->shift_len == 0 || (prepare->shift_len + prepare->remaining_len) <= 0) - { - return; - } - - if (prepare->shiftValue > 0) - { - // Store the values which are moved out of the array (last n pixels) - memcpy(prepare->tmp_pixels, &prepare->buffer->values[prepare->offset + prepare->remaining_len], prepare->shift_len); - // Move pixels to end - os_memmove(&prepare->buffer->values[prepare->offset + prepare->shift_len], &prepare->buffer->values[prepare->offset], prepare->remaining_len); - // Fill beginning with temp data - if (prepare->shift_type == SHIFT_LOGICAL) - { - memset(&prepare->buffer->values[prepare->offset], 0, prepare->shift_len); - } - else - { - memcpy(&prepare->buffer->values[prepare->offset], prepare->tmp_pixels, prepare->shift_len); - } - } - else - { - // Store the values which are moved out of the array (last n pixels) - memcpy(prepare->tmp_pixels, &prepare->buffer->values[prepare->offset], prepare->shift_len); - // Move pixels to end - os_memmove(&prepare->buffer->values[prepare->offset], &prepare->buffer->values[prepare->offset + prepare->shift_len], prepare->remaining_len); - // Fill beginning with temp data - if (prepare->shift_type == SHIFT_LOGICAL) - { - memset(&prepare->buffer->values[prepare->offset + prepare->remaining_len], 0, prepare->shift_len); - } - else - { - memcpy(&prepare->buffer->values[prepare->offset + prepare->remaining_len], prepare->tmp_pixels, prepare->shift_len); - } - } -} - -static int ws2812_buffer_shift_lua(lua_State* L) { - - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - const int shiftValue = luaL_checkinteger(L, 2); - const unsigned shift_type = luaL_optinteger( L, 3, SHIFT_LOGICAL ); - - const int pos_start = luaL_optinteger(L, 4, 1); - const int pos_end = luaL_optinteger(L, 5, -1); - - - ws2812_buffer_shift(L, buffer, shiftValue, shift_type, pos_start, pos_end); - return 0; -} - -static int ws2812_buffer_dump(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - - lua_pushlstring(L, buffer->values, buffer->size * buffer->colorsPerLed); - - return 1; -} - -static int ws2812_buffer_replace(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), buffer->size); - - uint8_t *src; - size_t srcLen; - - if (lua_type(L, 2) == LUA_TSTRING) { - size_t length; - - src = (uint8_t *) lua_tolstring(L, 2, &length); - srcLen = length / buffer->colorsPerLed; - } else { - ws2812_buffer * rhs = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer"); - src = rhs->values; - srcLen = rhs->size; - luaL_argcheck(L, rhs->colorsPerLed == buffer->colorsPerLed, 2, "Buffers have different colors"); - } - - luaL_argcheck(L, srcLen + start - 1 <= buffer->size, 2, "Does not fit into destination"); - - memcpy(buffer->values + (start - 1) * buffer->colorsPerLed, src, srcLen * buffer->colorsPerLed); - - return 0; -} - -// buffer:mix(factor1, buffer1, ..) -// factor is 256 for 100% -// uses saturating arithmetic (one buffer at a time) -static int ws2812_buffer_mix(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - - int pos = 2; - size_t cells = buffer->size * buffer->colorsPerLed; - - int n_sources = (lua_gettop(L) - 1) / 2; - - struct { - int factor; - const uint8_t *values; - } source[n_sources]; - - int src; - for (src = 0; src < n_sources; src++, pos += 2) { - int factor = luaL_checkinteger(L, pos); - ws2812_buffer *src_buffer = (ws2812_buffer*) luaL_checkudata(L, pos + 1, "ws2812.buffer"); - - luaL_argcheck(L, src_buffer->size == buffer->size && src_buffer->colorsPerLed == buffer->colorsPerLed, pos + 1, "Buffer not same shape"); - - source[src].factor = factor; - source[src].values = src_buffer->values; - } - - size_t i; - for (i = 0; i < cells; i++) { - int32_t val = 0; - for (src = 0; src < n_sources; src++) { - val += (int32_t)(source[src].values[i] * source[src].factor); - } - - val += 128; // rounding istead of floor - val /= 256; // do not use implemetation dependant right shift - - if (val < 0) { - val = 0; - } else if (val > 255) { - val = 255; - } - buffer->values[i] = (uint8_t)val; - } - - return 0; -} - -// Returns the total of all channels -static int ws2812_buffer_power(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - - size_t cells = buffer->size * buffer->colorsPerLed; - - size_t i; - int total = 0; - for (i = 0; i < cells; i++) { - total += buffer->values[i]; - } - - lua_pushinteger(L, total); - - return 1; -} - -static int ws2812_buffer_get(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - const int led = luaL_checkinteger(L, 2) - 1; - - luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range"); - - int i; - for (i = 0; i < buffer->colorsPerLed; i++) - { - lua_pushinteger(L, buffer->values[buffer->colorsPerLed*led+i]); - } - - return buffer->colorsPerLed; -} - -static int ws2812_buffer_set(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - const int led = luaL_checkinteger(L, 2) - 1; - - luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range"); - - int type = lua_type(L, 3); - if(type == LUA_TTABLE) - { - int i; - for (i = 0; i < buffer->colorsPerLed; i++) - { - // Get value and push it on stack - lua_rawgeti(L, 3, i+1); - - // Convert it as int and store them in buffer - buffer->values[buffer->colorsPerLed*led+i] = lua_tointeger(L, -1); - } - - // Clean up the stack - lua_pop(L, buffer->colorsPerLed); - } - else if(type == LUA_TSTRING) - { - size_t len; - const char * buf = lua_tolstring(L, 3, &len); - - // Overflow check - if( buffer->colorsPerLed*led + len > buffer->colorsPerLed*buffer->size ) - { - return luaL_error(L, "string size will exceed strip length"); - } - - memcpy(&buffer->values[buffer->colorsPerLed*led], buf, len); - } - else - { - int i; - for (i = 0; i < buffer->colorsPerLed; i++) - { - buffer->values[buffer->colorsPerLed*led+i] = luaL_checkinteger(L, 3+i); - } - } - - return 0; -} - -static int ws2812_buffer_size(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - - lua_pushinteger(L, buffer->size); - - return 1; -} - -static int ws2812_buffer_sub(lua_State* L) { - ws2812_buffer * lhs = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - size_t l = lhs->size; - ptrdiff_t start = posrelat(luaL_checkinteger(L, 2), l); - ptrdiff_t end = posrelat(luaL_optinteger(L, 3, -1), l); - if (start <= end) { - ws2812_buffer *result = allocate_buffer(L, end - start + 1, lhs->colorsPerLed); - memcpy(result->values, lhs->values + lhs->colorsPerLed * (start - 1), lhs->colorsPerLed * (end - start + 1)); - } else { - ws2812_buffer *result = allocate_buffer(L, 0, lhs->colorsPerLed); - } - return 1; -} - -static int ws2812_buffer_concat(lua_State* L) { - ws2812_buffer * lhs = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - ws2812_buffer * rhs = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer"); - - luaL_argcheck(L, lhs->colorsPerLed == rhs->colorsPerLed, 1, "Can only concatenate buffers with same colors"); - - int colorsPerLed = lhs->colorsPerLed; - int leds = lhs->size + rhs->size; - - ws2812_buffer * buffer = allocate_buffer(L, leds, colorsPerLed); - - memcpy(buffer->values, lhs->values, lhs->colorsPerLed * lhs->size); - memcpy(buffer->values + lhs->colorsPerLed * lhs->size, rhs->values, rhs->colorsPerLed * rhs->size); - - return 1; -} - -static int ws2812_buffer_tostring(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - - luaL_Buffer result; - luaL_buffinit(L, &result); - - luaL_addchar(&result, '['); - int i; - int p = 0; - for (i = 0; i < buffer->size; i++) { - int j; - if (i > 0) { - luaL_addchar(&result, ','); - } - luaL_addchar(&result, '('); - for (j = 0; j < buffer->colorsPerLed; j++, p++) { - if (j > 0) { - luaL_addchar(&result, ','); - } - char numbuf[5]; - sprintf(numbuf, "%d", buffer->values[p]); - luaL_addstring(&result, numbuf); - } - luaL_addchar(&result, ')'); - } - - luaL_addchar(&result, ']'); - luaL_pushresult(&result); - - return 1; -} - -LROT_BEGIN(ws2812_buffer_map, NULL, LROT_MASK_INDEX) - LROT_FUNCENTRY( __concat, ws2812_buffer_concat ) - LROT_TABENTRY( __index, ws2812_buffer_map ) - LROT_FUNCENTRY( __tostring, ws2812_buffer_tostring ) - LROT_FUNCENTRY( dump, ws2812_buffer_dump ) - LROT_FUNCENTRY( fade, ws2812_buffer_fade_lua) - LROT_FUNCENTRY( fill, ws2812_buffer_fill_lua ) - LROT_FUNCENTRY( get, ws2812_buffer_get ) - LROT_FUNCENTRY( replace, ws2812_buffer_replace ) - LROT_FUNCENTRY( mix, ws2812_buffer_mix ) - LROT_FUNCENTRY( power, ws2812_buffer_power ) - LROT_FUNCENTRY( set, ws2812_buffer_set ) - LROT_FUNCENTRY( shift, ws2812_buffer_shift_lua ) - LROT_FUNCENTRY( size, ws2812_buffer_size ) - LROT_FUNCENTRY( sub, ws2812_buffer_sub ) -LROT_END(ws2812_buffer_map, NULL, LROT_MASK_INDEX) - LROT_BEGIN(ws2812, NULL, 0) LROT_FUNCENTRY( init, ws2812_init ) - LROT_FUNCENTRY( newBuffer, ws2812_new_buffer ) + LROT_FUNCENTRY( newBuffer, pixbuf_new_lua ) // backwards compatibility LROT_FUNCENTRY( write, ws2812_write ) - LROT_NUMENTRY( FADE_IN, FADE_IN ) - LROT_NUMENTRY( FADE_OUT, FADE_OUT ) + LROT_NUMENTRY( FADE_IN, PIXBUF_FADE_IN ) // BC + LROT_NUMENTRY( FADE_OUT, PIXBUF_FADE_OUT ) // BC LROT_NUMENTRY( MODE_SINGLE, MODE_SINGLE ) LROT_NUMENTRY( MODE_DUAL, MODE_DUAL ) - LROT_NUMENTRY( SHIFT_LOGICAL, SHIFT_LOGICAL ) - LROT_NUMENTRY( SHIFT_CIRCULAR, SHIFT_CIRCULAR ) + LROT_NUMENTRY( SHIFT_LOGICAL, PIXBUF_SHIFT_LOGICAL ) // BC + LROT_NUMENTRY( SHIFT_CIRCULAR, PIXBUF_SHIFT_CIRCULAR ) // BC LROT_END(ws2812, NULL, 0) -int luaopen_ws2812(lua_State *L) { +static int luaopen_ws2812(lua_State *L) { // TODO: Make sure that the GPIO system is initialized - luaL_rometatable(L, "ws2812.buffer", LROT_TABLEREF(ws2812_buffer_map)); return 0; } diff --git a/app/modules/ws2812.h b/app/modules/ws2812.h deleted file mode 100644 index 95fd3f02..00000000 --- a/app/modules/ws2812.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef APP_MODULES_WS2812_H_ -#define APP_MODULES_WS2812_H_ - -#include "module.h" -#include "lauxlib.h" -#include "lmem.h" -#include "platform.h" -#include -#include -#include -#include "user_interface.h" -#include "driver/uart.h" -#include "osapi.h" - -#define FADE_IN 1 -#define FADE_OUT 0 -#define SHIFT_LOGICAL 0 -#define SHIFT_CIRCULAR 1 - -#ifndef MIN -#define MIN(a,b) ((a) < (b) ? (a) : (b)) -#endif -#ifndef MAX -#define MAX(a,b) ((a) > (b) ? (a) : (b)) -#endif - -typedef struct { - int size; - uint8_t colorsPerLed; - uint8_t values[0]; -} ws2812_buffer; - -typedef struct { - size_t offset; - uint8_t* tmp_pixels; - int shiftValue; - size_t shift_len; - size_t remaining_len; - unsigned shift_type; - ws2812_buffer* buffer; -} ws2812_buffer_shift_prepare; - - -void ICACHE_RAM_ATTR ws2812_write_data(const uint8_t *pixels, uint32_t length, const uint8_t *pixels2, uint32_t length2); -// To shift the lua_State is needed for error message and memory allocation. -// We also need the shift operation inside a timer callback, where we cannot access the lua_State, -// so This is split up in prepare and the actual call, which can be called multiple times with the same prepare object. -// After being done just luaM_free on the prepare object. -void ws2812_buffer_shift_prepared(ws2812_buffer_shift_prepare* prepare); -ws2812_buffer_shift_prepare* ws2812_buffer_get_shift_prepare(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end); - -int ws2812_buffer_fill(ws2812_buffer * buffer, int * colors); -void ws2812_buffer_fade(ws2812_buffer * buffer, int fade, unsigned direction); - -#endif /* APP_MODULES_WS2812_H_ */ diff --git a/app/modules/ws2812_effects.c b/app/modules/ws2812_effects.c index 894a66ae..1b3f1c9a 100644 --- a/app/modules/ws2812_effects.c +++ b/app/modules/ws2812_effects.c @@ -10,7 +10,7 @@ #include "osapi.h" #include "pm/swtimer.h" -#include "ws2812.h" +#include "pixbuf.h" #include "color_utils.h" #define CANARY_VALUE 0x32372132 @@ -43,9 +43,8 @@ #define IDX_B 2 #define IDX_W 3 - typedef struct { - ws2812_buffer *buffer; + pixbuf *buffer; int buffer_ref; uint32_t mode_delay; uint32_t counter_mode_call; @@ -58,11 +57,10 @@ typedef struct { uint8_t effect_type; uint8_t color[4]; int effect_int_param1; - ws2812_buffer_shift_prepare* prepare; + + struct pixbuf_shift_params shift; } ws2812_effects; - - enum ws2812_effects_type { WS2812_EFFECT_STATIC, WS2812_EFFECT_BLINK, @@ -91,40 +89,33 @@ static ws2812_effects *state; // UTILITY METHODS //----------------- +// XXX Not exported because this module is its sole non-Lua consumer and we +// should be going away soon! Deprecated, 'n all that. +extern void ICACHE_RAM_ATTR ws2812_write_data( + const uint8_t *pixels, uint32_t length, + const uint8_t *pixels2, uint32_t length2); -static int ws2812_write(ws2812_buffer* buffer) { - size_t length1, length2; - const char *buffer1, *buffer2; - - buffer1 = buffer->values; - length1 = buffer->colorsPerLed*buffer->size; - - buffer2 = 0; - length2 = 0; - - // Send the buffers - ws2812_write_data(buffer1, length1, buffer2, length2); - +static int ws2812_effects_write(pixbuf* buffer) { + ws2812_write_data(buffer->values, pixbuf_size(buffer), 0, 0); return 0; } +// :opens_boxes -1 +static void ws2812_set_pixel(int pixel, uint32_t color) { + pixbuf * buffer = state->buffer; -static int ws2812_set_pixel(int pixel, uint32_t color) { - ws2812_buffer * buffer = state->buffer; uint8_t g = ((color & 0x00FF0000) >> 16); uint8_t r = ((color & 0x0000FF00) >> 8); uint8_t b = (color & 0x000000FF); - uint8_t w = buffer->colorsPerLed == 4 ? ((color & 0xFF000000) >> 24) : 0; + uint8_t w = pixbuf_channels(buffer) == 4 ? ((color & 0xFF000000) >> 24) : 0; - int offset = pixel * buffer->colorsPerLed; + int offset = pixel * pixbuf_channels(buffer); buffer->values[offset+IDX_R] = r; buffer->values[offset+IDX_G] = g; buffer->values[offset+IDX_B] = b; - if (buffer->colorsPerLed == 4) { + if (pixbuf_channels(buffer) == 4) { buffer->values[offset+IDX_W] = w; } - - return 0; } @@ -160,16 +151,14 @@ static int ws2812_effects_init(lua_State *L) { platform_print_deprecation_note("ws2812_effects", "soon; please see https://github.com/nodemcu/nodemcu-firmware/issues/3122"); - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); - luaL_argcheck(L, buffer != NULL, 1, "no valid buffer provided"); + pixbuf * buffer = pixbuf_from_lua_arg(L, 1); + // get rid of old state if (state != NULL) { - if (state->prepare) { - luaM_free(L, state->prepare); - } luaL_unref(L, LUA_REGISTRYINDEX, state->buffer_ref); free((void *) state); } + // Allocate memory and set all to zero state = (ws2812_effects *) calloc(1,sizeof(ws2812_effects)); // initialize @@ -245,11 +234,10 @@ static int ws2812_effects_set_brightness(lua_State* L) { return 0; } - - +// :opens_boxes -1 static void ws2812_effects_fill_buffer(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; uint8_t bright_g = g * state->brightness / BRIGHTNESS_MAX; uint8_t bright_r = r * state->brightness / BRIGHTNESS_MAX; @@ -259,11 +247,11 @@ static void ws2812_effects_fill_buffer(uint8_t r, uint8_t g, uint8_t b, uint8_t // Fill buffer int i; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { + for(i = 0; i < buffer->npix; i++) { *p++ = bright_g; *p++ = bright_r; *p++ = bright_b; - if (buffer->colorsPerLed == 4) { + if (pixbuf_channels(buffer) == 4) { *p++ = bright_w; } } @@ -308,8 +296,8 @@ static int ws2812_effects_mode_blink() { } else { // off - ws2812_buffer * buffer = state->buffer; - memset(&buffer->values[0], 0, buffer->size * buffer->colorsPerLed); + pixbuf * buffer = state->buffer; + memset(&buffer->values[0], 0, pixbuf_size(buffer)); } return 0; } @@ -318,10 +306,10 @@ static int ws2812_effects_mode_blink() { static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; - int segments = (length1 / buffer->colorsPerLed) - 1; - int segmentSize = buffer->size / segments; + int segments = (length1 / pixbuf_channels(buffer)) - 1; + int segmentSize = buffer->npix / segments; uint8_t g1, r1, b1, g2, r2, b2; int i,j,k; @@ -330,7 +318,7 @@ static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { r2 = *gradient_spec++; b2 = *gradient_spec++; // skip non-rgb components - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *gradient_spec++; } @@ -353,7 +341,7 @@ static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { g2 = *gradient_spec++; r2 = *gradient_spec++; b2 = *gradient_spec++; - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *gradient_spec++; } @@ -371,7 +359,7 @@ static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { int numPixels = segmentSize; // make sure we fill the strip correctly in case of rounding errors if (k == segments - 1) { - numPixels = buffer->size - (segmentSize * (segments - 1)); + numPixels = buffer->npix - (segmentSize * (segments - 1)); } int steps = numPixels - 1; @@ -391,7 +379,7 @@ static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { *p++ = ((grb & 0x0000FF00) >> 8) * state->brightness / BRIGHTNESS_MAX; *p++ = (grb & 0x000000FF) * state->brightness / BRIGHTNESS_MAX; - for (j = 3; j < buffer->colorsPerLed; j++) { + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } } @@ -404,10 +392,10 @@ static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; - int segments = (length1 / buffer->colorsPerLed) - 1; - int segmentSize = buffer->size / segments; + int segments = (length1 / pixbuf_channels(buffer)) - 1; + int segmentSize = buffer->npix / segments; uint8_t g1, r1, b1, g2, r2, b2; int i,j,k; @@ -416,7 +404,7 @@ static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { r2 = *buffer1++; b2 = *buffer1++; // skip non-rgb components - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *buffer1++; } @@ -432,7 +420,7 @@ static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { r2 = *buffer1++; b2 = *buffer1++; - for (j = 3; j < buffer->colorsPerLed; j++) { + for (j = 3; j < pixbuf_channels(buffer); j++) { *buffer1++; } @@ -440,7 +428,7 @@ static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { int numPixels = segmentSize; // make sure we fill the strip correctly in case of rounding errors if (k == segments - 1) { - numPixels = buffer->size - (segmentSize * (segments - 1)); + numPixels = buffer->npix - (segmentSize * (segments - 1)); } int steps = numPixels - 1; @@ -449,7 +437,7 @@ static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { *p++ = (g1 + ((g2-g1) * i / steps)) * state->brightness / BRIGHTNESS_MAX; *p++ = (r1 + ((r2-r1) * i / steps)) * state->brightness / BRIGHTNESS_MAX; *p++ = (b1 + ((b2-b1) * i / steps)) * state->brightness / BRIGHTNESS_MAX; - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } @@ -466,7 +454,7 @@ static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { */ static int ws2812_effects_mode_random_color() { state->mode_color_index = get_random_wheel_index(state->mode_color_index); - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; uint32_t color = color_wheel(state->mode_color_index); uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / BRIGHTNESS_MAX; @@ -476,11 +464,11 @@ static int ws2812_effects_mode_random_color() { // Fill buffer int i,j; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { + for(i = 0; i < buffer->npix; i++) { *p++ = g; *p++ = r; *p++ = b; - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } @@ -493,7 +481,7 @@ static int ws2812_effects_mode_random_color() { */ static int ws2812_effects_mode_rainbow() { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; uint32_t color = color_wheel(state->counter_mode_step); uint8_t r = (color & 0x00FF0000) >> 16; @@ -503,11 +491,11 @@ static int ws2812_effects_mode_rainbow() { // Fill buffer int i,j; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { + for(i = 0; i < buffer->npix; i++) { *p++ = g * state->brightness / BRIGHTNESS_MAX; *p++ = r * state->brightness / BRIGHTNESS_MAX; *p++ = b * state->brightness / BRIGHTNESS_MAX; - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } @@ -523,12 +511,12 @@ static int ws2812_effects_mode_rainbow() { */ static int ws2812_effects_mode_rainbow_cycle(int repeat_count) { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; int i,j; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { - uint16_t wheel_index = (i * 360 / buffer->size * repeat_count) % 360; + for(i = 0; i < buffer->npix; i++) { + uint16_t wheel_index = (i * 360 / buffer->npix * repeat_count) % 360; uint32_t color = color_wheel(wheel_index); uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / BRIGHTNESS_MAX; uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / BRIGHTNESS_MAX; @@ -536,7 +524,7 @@ static int ws2812_effects_mode_rainbow_cycle(int repeat_count) { *p++ = g; *p++ = r; *p++ = b; - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } @@ -552,7 +540,7 @@ static int ws2812_effects_mode_rainbow_cycle(int repeat_count) { */ static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; uint8_t p_g = state->color[0]; uint8_t p_r = state->color[1]; @@ -561,7 +549,7 @@ static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { // Fill buffer int i,j; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { + for(i = 0; i < buffer->npix; i++) { int flicker = rand() % (max_flicker > 0 ? max_flicker : 1); int r1 = p_r-flicker; int g1 = p_g-flicker; @@ -572,7 +560,7 @@ static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { *p++ = g1 * state->brightness / BRIGHTNESS_MAX; *p++ = r1 * state->brightness / BRIGHTNESS_MAX; *p++ = b1 * state->brightness / BRIGHTNESS_MAX; - for (j = 3; j < buffer->colorsPerLed; j++) { + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } } @@ -584,7 +572,7 @@ static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { * Halloween effect */ static int ws2812_effects_mode_halloween() { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; int g1 = 50 * state->brightness / BRIGHTNESS_MAX; int r1 = 255 * state->brightness / BRIGHTNESS_MAX; @@ -598,11 +586,11 @@ static int ws2812_effects_mode_halloween() { // Fill buffer int i,j; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { + for(i = 0; i < buffer->npix; i++) { *p++ = (i % 4 < 2) ? g1 : g2; *p++ = (i % 4 < 2) ? r1 : r2; *p++ = (i % 4 < 2) ? b1 : b2; - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } @@ -614,7 +602,7 @@ static int ws2812_effects_mode_halloween() { static int ws2812_effects_mode_circus_combustus() { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; int g1 = 0 * state->brightness / BRIGHTNESS_MAX; int r1 = 255 * state->brightness / BRIGHTNESS_MAX; @@ -627,7 +615,7 @@ static int ws2812_effects_mode_circus_combustus() { // Fill buffer int i,j; uint8_t * p = &buffer->values[0]; - for(i = 0; i < buffer->size; i++) { + for(i = 0; i < buffer->npix; i++) { if (i % 6 < 2) { *p++ = g1; *p++ = r1; @@ -643,7 +631,7 @@ static int ws2812_effects_mode_circus_combustus() { *p++ = 0; *p++ = 0; } - for (j = 3; j < buffer->colorsPerLed; j++) + for (j = 3; j < pixbuf_channels(buffer); j++) { *p++ = 0; } @@ -660,35 +648,37 @@ static int ws2812_effects_mode_circus_combustus() { */ static int ws2812_effects_mode_larson_scanner() { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; int led_index = 0; - ws2812_buffer_fade(buffer, 2, FADE_OUT); + for(int i=0; i < pixbuf_size(buffer); i++) { + buffer->values[i] = buffer->values[i] >> 2; + } uint16_t pos = 0; - if(state->counter_mode_step < buffer->size) { + if(state->counter_mode_step < buffer->npix) { pos = state->counter_mode_step; } else { - pos = (buffer->size * 2) - state->counter_mode_step - 2; + pos = (buffer->npix * 2) - state->counter_mode_step - 2; } - pos = pos * buffer->colorsPerLed; + pos = pos * pixbuf_channels(buffer); buffer->values[pos + 1] = state->color[1]; buffer->values[pos] = state->color[0]; buffer->values[pos + 2] = state->color[2]; - state->counter_mode_step = (state->counter_mode_step + 1) % ((buffer->size * 2) - 2); + state->counter_mode_step = (state->counter_mode_step + 1) % ((buffer->npix * 2) - 2); } static int ws2812_effects_mode_color_wipe() { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; - int led_index = (state->counter_mode_step % buffer->size) * buffer->colorsPerLed; + int led_index = (state->counter_mode_step % buffer->npix) * pixbuf_channels(buffer); - if (state->counter_mode_step >= buffer->size) + if (state->counter_mode_step >= buffer->npix) { buffer->values[led_index] = 0; buffer->values[led_index + 1] = 0; @@ -703,30 +693,30 @@ static int ws2812_effects_mode_color_wipe() { buffer->values[led_index + 1] = px_r; buffer->values[led_index + 2] = px_b; } - state->counter_mode_step = (state->counter_mode_step + 1) % (buffer->size * 2); + state->counter_mode_step = (state->counter_mode_step + 1) % (buffer->npix * 2); } static int ws2812_effects_mode_random_dot(uint8_t dots) { - ws2812_buffer * buffer = state->buffer; + pixbuf * buffer = state->buffer; // fade out - for(int i=0; i < buffer->size * buffer->colorsPerLed; i++) { + for(int i=0; i < pixbuf_size(buffer); i++) { buffer->values[i] = buffer->values[i] >> 1; } for(int i=0; i < dots; i++) { // pick random pixel - int led_index = rand() % buffer->size; + int led_index = rand() % buffer->npix; uint32_t color = (state->color[0] << 16) | (state->color[1] << 8) | state->color[2]; - if (buffer->colorsPerLed == 4) { + if (pixbuf_channels(buffer) == 4) { color = color | (state->color[3] << 24); } ws2812_set_pixel(led_index, color); } - state->counter_mode_step = (state->counter_mode_step + 1) % ((buffer->size * 2) - 2); + state->counter_mode_step = (state->counter_mode_step + 1) % ((buffer->npix * 2) - 2); } @@ -767,6 +757,11 @@ static uint32_t ws2812_effects_mode_delay() return delay; } +static void ws2812_effects_do_shift(void) +{ + pixbuf_shift(state->buffer, &state->shift); + ws2812_effects_write(state->buffer); +} /** * run loop for the effects. @@ -784,7 +779,7 @@ static void ws2812_effects_loop(void* p) else if (state->effect_type == WS2812_EFFECT_RAINBOW_CYCLE) { // the rainbow cycle effect can be achieved by shifting the buffer - ws2812_buffer_shift_prepared(state->prepare); + ws2812_effects_do_shift(); } else if (state->effect_type == WS2812_EFFECT_FLICKER) { @@ -816,11 +811,11 @@ static void ws2812_effects_loop(void* p) } else if (state->effect_type == WS2812_EFFECT_HALLOWEEN) { - ws2812_buffer_shift_prepared(state->prepare); + ws2812_effects_do_shift(); } else if (state->effect_type == WS2812_EFFECT_CIRCUS_COMBUSTUS) { - ws2812_buffer_shift_prepared(state->prepare); + ws2812_effects_do_shift(); } else if (state->effect_type == WS2812_EFFECT_LARSON_SCANNER) { @@ -828,7 +823,7 @@ static void ws2812_effects_loop(void* p) } else if (state->effect_type == WS2812_EFFECT_CYCLE) { - ws2812_buffer_shift_prepared(state->prepare); + ws2812_effects_do_shift(); } else if (state->effect_type == WS2812_EFFECT_COLOR_WIPE) { @@ -845,7 +840,7 @@ static void ws2812_effects_loop(void* p) // call count state->counter_mode_call = (state->counter_mode_call + 1) % UINT32_MAX; // write the buffer - ws2812_write(state->buffer); + ws2812_effects_write(state->buffer); // set the timer if (state->running == 1 && state->mode_delay >= 10) if (state->running == 1 && state->mode_delay >= 10) @@ -855,15 +850,6 @@ static void ws2812_effects_loop(void* p) } } -void prepare_shift(lua_State* L, ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end){ - // deinit old effect - if (state->prepare) { - luaM_free(L, state->prepare); - } - - state->prepare = ws2812_buffer_get_shift_prepare(L, buffer, shiftValue, shift_type, pos_start, pos_end); -} - /** * Set the active effect mode */ @@ -910,13 +896,13 @@ static int ws2812_effects_set_mode(lua_State* L) { size_t length1; const char *buffer1 = lua_tolstring(L, 2, &length1); - if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 0)) + if ((length1 / pixbuf_channels(state->buffer) < 2) || (length1 % pixbuf_channels(state->buffer) != 0)) { luaL_argerror(L, 2, "must be at least two colors and same size as buffer colors"); } ws2812_effects_gradient(buffer1, length1); - ws2812_write(state->buffer); + ws2812_effects_write(state->buffer); } else { @@ -930,13 +916,13 @@ static int ws2812_effects_set_mode(lua_State* L) { size_t length1; const char *buffer1 = lua_tolstring(L, 2, &length1); - if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 0)) + if ((length1 / pixbuf_channels(state->buffer) < 2) || (length1 % pixbuf_channels(state->buffer) != 0)) { luaL_argerror(L, 2, "must be at least two colors and same size as buffer colors"); } ws2812_effects_gradient_rgb(buffer1, length1); - ws2812_write(state->buffer); + ws2812_effects_write(state->buffer); } else { @@ -952,7 +938,7 @@ static int ws2812_effects_set_mode(lua_State* L) { break; case WS2812_EFFECT_RAINBOW_CYCLE: ws2812_effects_mode_rainbow_cycle(effect_param != EFFECT_PARAM_INVALID ? effect_param : 1); - prepare_shift(L, state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + pixbuf_prepare_shift(state->buffer, &state->shift, 1, PIXBUF_SHIFT_CIRCULAR, 1, -1); break; case WS2812_EFFECT_FLICKER: state->effect_int_param1 = effect_param; @@ -967,11 +953,11 @@ static int ws2812_effects_set_mode(lua_State* L) { break; case WS2812_EFFECT_HALLOWEEN: ws2812_effects_mode_halloween(); - prepare_shift(L, state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + pixbuf_prepare_shift(state->buffer, &state->shift, 1, PIXBUF_SHIFT_CIRCULAR, 1, -1); break; case WS2812_EFFECT_CIRCUS_COMBUSTUS: ws2812_effects_mode_circus_combustus(); - prepare_shift(L, state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + pixbuf_prepare_shift(state->buffer, &state->shift, 1, PIXBUF_SHIFT_CIRCULAR, 1, -1); break; case WS2812_EFFECT_LARSON_SCANNER: ws2812_effects_mode_larson_scanner(); @@ -980,7 +966,7 @@ static int ws2812_effects_set_mode(lua_State* L) { if (effect_param != EFFECT_PARAM_INVALID) { state->effect_int_param1 = effect_param; } - prepare_shift(L, state->buffer, state->effect_int_param1, SHIFT_CIRCULAR, 1, -1); + pixbuf_prepare_shift(state->buffer, &state->shift, state->effect_int_param1, PIXBUF_SHIFT_CIRCULAR, 1, -1); break; case WS2812_EFFECT_COLOR_WIPE: // fill buffer with black. r,g,b,w = 0 diff --git a/docs/modules/apa102.md b/docs/modules/apa102.md index 9eb656f9..f5a040b9 100644 --- a/docs/modules/apa102.md +++ b/docs/modules/apa102.md @@ -19,7 +19,10 @@ Send ABGR data in 8 bits to a APA102 chain. - `data_pin` any GPIO pin 0, 1, 2, ... - `clock_pin` any GPIO pin 0, 1, 2, ... - `string` payload to be sent to one or more APA102 LEDs. - It should be composed from a ABGR quadruplet per element. + + It may be a [pixbuf](pixbuf) with four channels or a string, + composed from a ABGR quadruplet per element: + - `A1` the first pixel's Intensity channel (0-31) - `B1` the first pixel's Blue channel (0-255)
- `G1` the first pixel's Green channel (0-255) diff --git a/docs/modules/pixbuf.md b/docs/modules/pixbuf.md new file mode 100644 index 00000000..c55730e3 --- /dev/null +++ b/docs/modules/pixbuf.md @@ -0,0 +1,342 @@ +# Pixel Buffer (pixbuf) Module + +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2020-?? | [nwf](https://github.com/nwf) | nwf | [pixbuf.c](../../app/modules/pixbuf.c) | + +The pixbuf library offers C-array byte objects and convenient utility functions +for maintaining small frame buffers, usually for use with LED arrays, as +supported by, e.g., ws2812. + +## pixbuf.newBuffer() +Allocate a new memory buffer to store LED values. + +#### Syntax +`pixbuf.newBuffer(numberOfLeds, numberOfChannels)` + +#### Parameters + - `numberOfLeds` length of the LED strip (in pixels) + - `numberOfChannels` the channel count (bytes per pixel) + +#### Returns +`pixbuf.buffer` object + +## pixbuf.buffer:get() +Return the value at the given position, in native strip color order + +#### Syntax +`buffer:get(index)` + +#### Parameters + - `index` position in the buffer (1 for first LED) + +#### Returns +`(color)` + +#### Example +```lua +buffer = pixbuf.newBuffer(32, 4) +print(buffer:get(1)) +0 0 0 0 +``` + +## pixbuf.buffer:set() +Set the value at the given position, in native strip color order + +#### Syntax +`buffer:set(index, color)` + +#### Parameters + - `index` position in the buffer (1 for the first LED) + - `color` payload of the color + +Payload could be: +- `number, number, ...`, passing as many colors as required by the array type +- `table` should contain one value per color required by the array type +- `string` with a natural multiple of the colors required by the array type + +`string` inputs may be used to set multiple consecutive pixels! + +#### Returns +The buffer + +#### Example +```lua +buffer = pixbuf.newBuffer(32, 3) +buffer:set(1, 255, 0, 0) -- set the first LED green for a GRB strip +``` + +```lua +buffer = pixbuf.newBuffer(32, 4) +buffer:set(1, {255, 0, 0, 255}) -- set the first LED white and red for a RGBW strip +``` + +```lua +-- set the first LED green for a RGB strip and exploit the return value +buffer = pixbuf.newBuffer(32, 3):set(1, string.char(0, 255, 0)) +``` + +## pixbuf.buffer:size() +Return the size of the buffer in number of LEDs + +#### Syntax +`buffer:size()` + +#### Parameters +none + +#### Returns +`int` + +## pixbuf.buffer:channels() +Return the buffer's channel count + +#### Syntax +`buffer:channels()` + +#### Parameters +none + +#### Returns +`int` + +## pixbuf.buffer:fill() +Fill the buffer with the given color. +The number of given bytes must match the channel count of the buffer. + +#### Syntax +`buffer:fill(color)` + +#### Parameters + - `color` bytes for each channel + +#### Returns +The buffer + +#### Example +```lua +buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip +``` + +## pixbuf.buffer:dump() +Returns the contents of the buffer (the pixel values) as a string. This can then be saved to a file or sent over a network and may be fed back to [`pixbuf.buffer:set()`](#pixbufbufferset). + +#### Syntax +`buffer:dump()` + +#### Returns +A string containing the pixel values. + +#### Example +```lua +local s = buffer:dump() +``` + +## pixbuf.buffer:replace() +Inserts a string (or a pixbuf) into another buffer with an offset. +The buffer must be of the same type or an error will be thrown. + +#### Syntax +`buffer:replace(source[, offset])` + +#### Parameters + - `source` the pixel values to be set into the buffer. This is either a string or a pixbuf. + - `offset` the offset where the source is to be placed in the buffer. Default is 1. Negative values can be used. + +#### Returns +`nil` + +#### Example +```lua +buffer:replace(anotherbuffer:dump()) -- copy one buffer into another via a string +buffer:replace(anotherbuffer) -- copy one buffer into another +newbuffer = buffer.sub(1) -- make a copy of a buffer into a new buffer +``` + +## pixbuf.buffer:mix() +This is a general method that loads data into a buffer that is a linear combination of data from other buffers. It can be used to copy a buffer or, +more usefully, do a cross fade. The pixel values are computed as integers and then range limited to [0, 255]. This means that negative +factors work as expected, and that the order of combining buffers does not matter. + +#### Syntax +`buffer:mix(factor1, buffer1, ...)` + +#### Parameters + - `factor1` This is the factor that the contents of `buffer1` are multiplied by. This factor is scaled by a factor of 256. Thus `factor1` value of 256 is a factor of 1.0. + - `buffer1` This is the source buffer. It must be of the same shape as the destination buffer. + +There can be any number of factor/buffer pairs. + +#### Returns +The output buffer. + +#### Example +```lua +-- loads buffer with a crossfade between buffer1 and buffer2 +buffer:mix(256 - crossmix, buffer1, crossmix, buffer2) + +-- multiplies all values in buffer by 0.75 +-- This can be used in place of buffer:fade +buffer:mix(192, buffer) +``` + +## pixbuf.buffer:mix4I5() +Like [`pixbuf.buffer:mix()`](#pixbufbuffermix) but treats the first channel as +a scaling, 5-bit intensity value. The buffers must all have four channels. +This is mostly useful for APA102 LEDs. + +## pixbuf.buffer:power() +Computes the total energy requirement for the buffer. This is merely the total sum of all the pixel values (which assumes that each color in each +pixel consumes the same amount of power). A real WS2812 (or WS2811) has three constant current drivers of 20mA -- one for each of R, G and B. The +pulse width modulation will cause the *average* current to scale linearly with pixel value. + +#### Syntax +`buffer:power()` + +#### Returns +An integer which is the sum of all the pixel values. + +#### Example +```lua +-- Dim the buffer to no more than the PSU can provide +local psu_current_ma = 1000 +local led_current_ma = 20 +local led_sum = psu_current_ma * 255 / led_current_ma + +local p = buffer:power() +if p > led_sum then + buffer:mix(256 * led_sum / p, buffer) -- power is now limited +end +``` + +## pixbuf.buffer:powerI() +Like [`pixbuf.buffer:power()`](#pixbufbufferpower) but treats the first channel as +a scaling intensity value. + +## pixbuf.buffer:fade() +Fade in or out. Defaults to out. Multiply or divide each byte of each led with/by the given value. Useful for a fading effect. + +#### Syntax +`buffer:fade(value [, direction])` + +#### Parameters + - `value` value by which to divide or multiply each byte + - `direction` pixbuf.FADE\_IN or pixbuf.FADE\_OUT. Defaults to pixbuf.FADE\_OUT + +#### Returns +`nil` + +#### Example +```lua +buffer:fade(2) +buffer:fade(2, pixbuf.FADE_IN) +``` + +## pixbuf.buffer:fadeI() +Like [`pixbuf.buffer:fade()`](#pixbufbufferfade) but treats the first channel as +a scaling intensity value. This is mostly useful for APA102 LEDs. + +## pixbuf.buffer:shift() +Shift the content of (a piece of) the buffer in positive or negative direction. This allows simple animation effects. A slice of the buffer can be specified by using the +standard start and end offset Lua notation. Negative values count backwards from the end of the buffer. + +#### Syntax +`buffer:shift(value [, mode[, i[, j]]])` + +#### Parameters + - `value` number of pixels by which to rotate the buffer. Positive values rotate forwards, negative values backwards. + - `mode` is the shift mode to use. Can be one of `pixbuf.SHIFT_LOGICAL` or `pixbuf.SHIFT_CIRCULAR`. In case of SHIFT\_LOGICAL, the freed pixels are set to 0 (off). In case of SHIFT\_CIRCULAR, the buffer is treated like a ring buffer, inserting the pixels falling out on one end again on the other end. Defaults to SHIFT\_LOGICAL. + - `i` is the first offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is 1. + - `j` is the last offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is -1. + +#### Returns +`nil` + +#### Example +```lua +buffer:shift(3) +``` + +## pixbuf.buffer:sub() +This implements the extraction function like `string.sub`. The indexes are in leds and all the same rules apply. + +#### Syntax +`buffer1:sub(i[, j])` + +#### Parameters + - `i` This is the start of the extracted data. Negative values can be used. + - `j` this is the end of the extracted data. Negative values can be used. The default is -1. + +#### Returns +A buffer containing the extracted piece. + +#### Example +``` +b = buffer:sub(1,10) +``` + +## pixbuf.buffer:__concat() +This implements the `..` operator to concatenate two buffers. They must have the same number of colors per led. + +#### Syntax +`buffer1 .. buffer2` + +#### Parameters + - `buffer1` this is the start of the resulting buffer + - `buffer2` this is the end of the resulting buffer + +#### Returns +The concatenated buffer. + +#### Example +``` +ws2812.write(buffer1 .. buffer2) +``` +## pixbuf.buffer:map() +Map a function across each pixel of one, or zip a function along two, +pixbuf(s), storing into the buffer on which it is called. + +#### Syntax +`buffer0:map(f, [buffer1], [start1], [end1], [buffer2, [start2]])` + +#### Parameters + - `f` This is the mapping function; it is applied for each pixel to all channels of `buffer1` and + all channels of `buffer2`, if given. It must return a value for each channel of the output + buffer, `buffer0`. + - `buffer1` The first source buffer. Defaults to `buffer0`. + - `start1` This is the start of the mapped range of `buffer1`. Negative values can be used and will be interpreted as before the end of `buffer1`. The default is 1. + - `end1` this is the end of the mapped range. Negative values can be used. The default is -1 (i.e., the end of `buffer1`). + - `buffer2` is a second buffer, for zip operations + - `start2` This is the start of the mapped range within `buffer2`. Negative values can be used and will be interpreted as before the end of `buffer2`. The default is 1. + +`buffer0` must have sufficient room to recieve all pixels from `start1` to +`end1` (which is true of the defaults, when `buffer1` is `buffer0` and `start1` +is 1 and `end1` is -1). `buffer2`, if given, must have sufficient pixels after +`start2`. + +#### Returns +`buffer0` + +#### Examples + +Change channel order within a single buffer: +```Lua +buffer:map(function(r,g,b) return g,r,b end) +``` + +Change channel order for a subset of pixels: +```Lua +buffer:map(function(r,g,b) return g,r,b end, nil, 2, 5) +``` + +Extract one channel for a subset of pixels: +```Lua +outbuf = pixbuf.create(11, 1) +outbuf:map(function(r,g,b) return b end, inbuf, 10, 20) +``` + +Concatenate channels per pixel, possibly with different offsets in buffers: +```Lua +outbuf:map(function(...) return ... end, inbuf1, inbuf2) +outbuf:map(function(...) return ... end, inbuf1, 5, 10, inbuf2, 3) +``` diff --git a/docs/modules/tm1829.md b/docs/modules/tm1829.md index b4790491..976a7eec 100644 --- a/docs/modules/tm1829.md +++ b/docs/modules/tm1829.md @@ -15,7 +15,9 @@ Send data to a led strip using native chip format. `tm1829.write(string)` #### Parameters -- `string` payload to be sent to one or more TM1829 leds. +- `string` payload to be sent to one or more TM1829 leds. It is either + a 3-channel [pixbuf](pixbuf) (e.g., `pixbuf.TYPE_RGB`) or a string of + raw byte values to be sent. #### Returns `nil` diff --git a/docs/modules/ws2812.md b/docs/modules/ws2812.md index 125ac134..de2d58fc 100644 --- a/docs/modules/ws2812.md +++ b/docs/modules/ws2812.md @@ -31,8 +31,20 @@ In `ws2812.MODE_DUAL` mode you will be able to handle two strips in parallel but `nil` ## ws2812.write() -Send data to one or two led strip using its native format which is generally Green,Red,Blue for RGB strips -and Green,Red,Blue,White for RGBW strips. +Send data to one or two led strip using its native format, which is generally +Green, Red, Blue for RGB strips and Green, Red, Blue, White for RGBW strips. +(However, ws2812 drivers have been observed wired up in other orders.) + +Because this function uses the hardware UART(s), it is able to return and allow +Lua to resume execution up to 300 microseconds before the data has finished +being sent. If you wish to perform actions synchronous with the end of the +data transmission, [`tmr.delay()`](../tmr#tmr.delay()) for 300 microseconds. + +Separately, because this function returns early, back-to-back invocations may +not leave enough time for the strip to latch, and so may appear to the ws2812 +drivers to be simply writes to a longer LED strip. Please ensure that you have +more than 350 microseconds between the return of `ws2812.write()` to your Lua +and the next invocation thereof. #### Syntax `ws2812.write(data1, [data2])` @@ -44,7 +56,7 @@ and Green,Red,Blue,White for RGBW strips. Payload type could be: - `nil` nothing is done - `string` representing bytes to send -- `ws2812.buffer` see [Buffer module](#buffer-module) +- a [pixbuf](pixbuf) object containing the bytes to send. The pixbuf's type is not checked! #### Returns `nil` @@ -70,280 +82,31 @@ ws2812.init(ws2812.MODE_DUAL) ws2812.write(nil, string.char(0, 255, 0, 0, 255, 0)) -- turn the two first RGB leds to red on the second strip, do nothing on the first ``` -# Buffer module +# Pixbuf support For more advanced animations, it is useful to keep a "framebuffer" of the strip, interact with it and flush it to the strip. -For this purpose, the ws2812 library offers a read/write buffer. This buffer has a `__tostring` method so that it can be printed. This is useful for debugging. +For this purpose, the [pixbuf](pixbuf) library offers a read/write buffer and +convenient functions for pixel value manipulation. + +For backwards-compatibility, `pixbuf.newBuffer()` is aliased as +`ws2812.newBuffer`, but this will be removed in the next nodemcu-firmware +release. #### Example Led chaser with a RGBW strip + ```lua ws2812.init() -local i, buffer = 0, ws2812.newBuffer(300, 4); buffer:fill(0, 0, 0, 0); tmr.create():alarm(50, 1, function() + +i, buffer = 0, pixbuf.newBuffer(300, 4) + +buffer:fill(0, 0, 0, 0) + +tmr.create():alarm(50, 1, function() i = i + 1 buffer:fade(2) buffer:set(i % buffer:size() + 1, 0, 0, 0, 255) ws2812.write(buffer) end) ``` - -## ws2812.newBuffer() -Allocate a new memory buffer to store led values. - -#### Syntax -`ws2812.newBuffer(numberOfLeds, bytesPerLed)` - -#### Parameters - - `numberOfLeds` length of the led strip - - `bytesPerLed` 3 for RGB strips and 4 for RGBW strips - -#### Returns -`ws2812.buffer` - -## ws2812.buffer:get() -Return the value at the given position - -#### Syntax -`buffer:get(index)` - -#### Parameters - - `index` position in the buffer (1 for first led) - -#### Returns -`(color)` - -#### Example -```lua -buffer = ws2812.newBuffer(32, 4) -print(buffer:get(1)) -0 0 0 0 -``` - -## ws2812.buffer:set() -Set the value at the given position - -#### Syntax -`buffer:set(index, color)` - -#### Parameters - - `index` position in the buffer (1 for the first led) - - `color` payload of the color - -Payload could be: -- `number, number, ...` you should pass as many arguments as `bytesPerLed` -- `table` should contains `bytesPerLed` numbers -- `string` should contains `bytesPerLed` bytes - -#### Returns -`nil` - -#### Example -```lua -buffer = ws2812.newBuffer(32, 3) -buffer:set(1, 255, 0, 0) -- set the first led green for a RGB strip -``` - -```lua -buffer = ws2812.newBuffer(32, 4) -buffer:set(1, {0, 0, 0, 255}) -- set the first led white for a RGBW strip -``` - -```lua -buffer = ws2812.newBuffer(32, 3) -buffer:set(1, string.char(255, 0, 0)) -- set the first led green for a RGB strip -``` - -## ws2812.buffer:size() -Return the size of the buffer in number of leds - -#### Syntax -`buffer:size()` - -#### Parameters -none - -#### Returns -`int` - -## ws2812.buffer:fill() -Fill the buffer with the given color. -The number of given bytes must match the number of bytesPerLed of the buffer - -#### Syntax -`buffer:fill(color)` - -#### Parameters - - `color` bytes of the color, you should pass as many arguments as `bytesPerLed` - -#### Returns -`nil` - -#### Example -```lua -buffer:fill(0, 0, 0) -- fill the buffer with black for a RGB strip -``` - -## ws2812.buffer:dump() -Returns the contents of the buffer (the pixel values) as a string. This can then be saved to a file or sent over a network. - -#### Syntax -`buffer:dump()` - -#### Returns -A string containing the pixel values. - -#### Example -```lua -local s = buffer:dump() -``` - -## ws2812.buffer:replace() -Inserts a string (or a buffer) into another buffer with an offset. -The buffer must have the same number of colors per led or an error will be thrown. - -#### Syntax -`buffer:replace(source[, offset])` - -#### Parameters - - `source` the pixel values to be set into the buffer. This is either a string or a buffer. - - `offset` the offset where the source is to be placed in the buffer. Default is 1. Negative values can be used. - -#### Returns -`nil` - -#### Example -```lua -buffer:replace(anotherbuffer:dump()) -- copy one buffer into another via a string -buffer:replace(anotherbuffer) -- copy one buffer into another -newbuffer = buffer.sub(1) -- make a copy of a buffer into a new buffer -``` - -## ws2812.buffer:mix() -This is a general method that loads data into a buffer that is a linear combination of data from other buffers. It can be used to copy a buffer or, -more usefully, do a cross fade. The pixel values are computed as integers and then range limited to [0, 255]. This means that negative -factors work as expected, and that the order of combining buffers does not matter. - -#### Syntax -`buffer:mix(factor1, buffer1, ...)` - -#### Parameters - - `factor1` This is the factor that the contents of `buffer1` are multiplied by. This factor is scaled by a factor of 256. Thus `factor1` value of 256 is a factor of 1.0. - - `buffer1` This is the source buffer. It must be of the same shape as the destination buffer. - -There can be any number of factor/buffer pairs. - -#### Returns -`nil` - -#### Example -```lua --- loads buffer with a crossfade between buffer1 and buffer2 -buffer:mix(256 - crossmix, buffer1, crossmix, buffer2) - --- multiplies all values in buffer by 0.75 --- This can be used in place of buffer:fade -buffer:mix(192, buffer) -``` - -## ws2812.buffer:power() -Computes the total energy requirement for the buffer. This is merely the total sum of all the pixel values (which assumes that each color in each -pixel consumes the same amount of power). A real WS2812 (or WS2811) has three constant current drivers of 20mA -- one for each of R, G and B. The -pulse width modulation will cause the *average* current to scale linearly with pixel value. - -#### Syntax -`buffer:power()` - -#### Returns -An integer which is the sum of all the pixel values. - -#### Example -```lua --- Dim the buffer to no more than the PSU can provide -local psu_current_ma = 1000 -local led_current_ma = 20 -local led_sum = psu_current_ma * 255 / led_current_ma - -local p = buffer:power() -if p > led_sum then - buffer:mix(256 * led_sum / p, buffer) -- power is now limited -end -``` - -## ws2812.buffer:fade() -Fade in or out. Defaults to out. Multiply or divide each byte of each led with/by the given value. Useful for a fading effect. - -#### Syntax -`buffer:fade(value [, direction])` - -#### Parameters - - `value` value by which to divide or multiply each byte - - `direction` ws2812.FADE\_IN or ws2812.FADE\_OUT. Defaults to ws2812.FADE\_OUT - -#### Returns -`nil` - -#### Example -```lua -buffer:fade(2) -buffer:fade(2, ws2812.FADE_IN) -``` -## ws2812.buffer:shift() -Shift the content of (a piece of) the buffer in positive or negative direction. This allows simple animation effects. A slice of the buffer can be specified by using the -standard start and end offset Lua notation. Negative values count backwards from the end of the buffer. - -#### Syntax -`buffer:shift(value [, mode[, i[, j]]])` - -#### Parameters - - `value` number of pixels by which to rotate the buffer. Positive values rotate forwards, negative values backwards. - - `mode` is the shift mode to use. Can be one of `ws2812.SHIFT_LOGICAL` or `ws2812.SHIFT_CIRCULAR`. In case of SHIFT\_LOGICAL, the freed pixels are set to 0 (off). In case of SHIFT\_CIRCULAR, the buffer is treated like a ring buffer, inserting the pixels falling out on one end again on the other end. Defaults to SHIFT\_LOGICAL. - - `i` is the first offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is 1. - - `j` is the last offset in the buffer to be affected. Negative values are permitted and count backwards from the end. Default is -1. - -#### Returns -`nil` - -#### Example -```lua -buffer:shift(3) -``` - -## ws2812.buffer:sub() -This implements the extraction function like `string.sub`. The indexes are in leds and all the same rules apply. - -#### Syntax -`buffer1:sub(i[, j])` - -#### Parameters - - `i` This is the start of the extracted data. Negative values can be used. - - `j` this is the end of the extracted data. Negative values can be used. The default is -1. - -#### Returns -A buffer containing the extracted piece. - -#### Example -``` -b = buffer:sub(1,10) -``` - -## ws2812.buffer:__concat() -This implements the `..` operator to concatenate two buffers. They must have the same number of colors per led. - -#### Syntax -`buffer1 .. buffer2` - -#### Parameters - - `buffer1` this is the start of the resulting buffer - - `buffer2` this is the end of the resulting buffer - -#### Returns -The concatenated buffer. - -#### Example -``` -ws2812.write(buffer1 .. buffer2) -``` - - - diff --git a/tests/NTest_pixbuf.lua b/tests/NTest_pixbuf.lua new file mode 100644 index 00000000..e07e4a3e --- /dev/null +++ b/tests/NTest_pixbuf.lua @@ -0,0 +1,288 @@ +local N = ... +N = (N or require "NTest")("pixbuf") + +local function initBuffer(buf, ...) + for i,v in ipairs({...}) do + buf:set(i, v, v*2, v*3, v*4) + end + return buf +end + +N.test('initialize a buffer', function() + local buffer = pixbuf.newBuffer(9, 3) + nok(buffer == nil) + ok(eq(buffer:size(), 9), "check size") + ok(eq(buffer:dump(), string.char(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)), "initialize with 0") + + fail(function() pixbuf.newBuffer(9, -1) end, "should be a positive integer") + fail(function() pixbuf.newBuffer(0, 3) end, "should be a positive integer") + fail(function() pixbuf.newBuffer(-1, 3) end, "should be a positive integer") +end) + +N.test('have correct size', function() + local buffer = pixbuf.newBuffer(9, 3) + ok(eq(buffer:size(), 9), "check size") + buffer = pixbuf.newBuffer(9, 4) + ok(eq(buffer:size(), 9), "check size") +end) + +N.test('fill a buffer with one color', function() + local buffer = pixbuf.newBuffer(3, 3) + buffer:fill(1,222,55) + ok(eq(buffer:dump(), string.char(1,222,55,1,222,55,1,222,55)), "RGB") + buffer = pixbuf.newBuffer(3, 4) + buffer:fill(1,222,55,77) + ok(eq(buffer:dump(), string.char(1,222,55,77,1,222,55,77,1,222,55,77)), "RGBW") +end) + +N.test('replace correctly', function() + local buffer = pixbuf.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255)) + ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") + + buffer = pixbuf.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 2) + ok(eq(buffer:dump(), string.char(0,0,0,3,255,165,33,0,244,12,87,255,0,0,0)), "RGBW") + + buffer = pixbuf.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -5) + ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") + + fail(function() buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 4) end, + "does not fit into destination") +end) + +N.test('replace correctly issue #2921', function() + local buffer = pixbuf.newBuffer(5, 3) + buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -7) + ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") +end) + +N.test('get/set correctly', function() + local buffer = pixbuf.newBuffer(3, 4) + buffer:fill(1,222,55,13) + ok(eq({buffer:get(2)},{1,222,55,13})) + buffer:set(2, 4,53,99,0) + ok(eq({buffer:get(1)},{1,222,55,13})) + ok(eq({buffer:get(2)},{4,53,99,0})) + ok(eq(buffer:dump(), string.char(1,222,55,13,4,53,99,0,1,222,55,13)), "RGBW") + + fail(function() buffer:get(0) end, "index out of range") + fail(function() buffer:get(4) end, "index out of range") + fail(function() buffer:set(0,1,2,3,4) end, "index out of range") + fail(function() buffer:set(4,1,2,3,4) end, "index out of range") + fail(function() buffer:set(2,1,2,3) end, "number expected, got no value") + fail(function() buffer:set(2,1,2,3,4,5) end, "extra values given") +end) + +N.test('get/set multiple with string', function() + -- verify that :set does indeed return its input + local buffer = pixbuf.newBuffer(4, 3):set(1,"ABCDEF") + buffer:set(3,"LMNOPQ") + ok(eq(buffer:dump(), "ABCDEFLMNOPQ")) + + fail(function() buffer:set(4,"AAAAAA") end, "string size will exceed strip length") + fail(function() buffer:set(2,"AAAAA") end, "string does not contain whole LEDs") +end) + +N.test('fade correctly', function() + local buffer = pixbuf.newBuffer(1, 3) + buffer:fill(1,222,55) + buffer:fade(2) + ok(buffer:dump() == string.char(0,111,27), "RGB") + buffer:fill(1,222,55) + buffer:fade(3, pixbuf.FADE_OUT) + ok(buffer:dump() == string.char(0,math.floor(222/3),math.floor(55/3)), "RGB") + buffer:fill(1,222,55) + buffer:fade(3, pixbuf.FADE_IN) + ok(buffer:dump() == string.char(3,255,165), "RGB") + buffer = pixbuf.newBuffer(1, 4) + buffer:fill(1,222,55, 77) + buffer:fade(2, pixbuf.FADE_OUT) + ok(eq(buffer:dump(), string.char(0,111,27,38)), "RGBW") +end) + +N.test('mix correctly issue #1736', function() + local buffer1 = pixbuf.newBuffer(1, 3) + local buffer2 = pixbuf.newBuffer(1, 3) + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(256/8*7,buffer1,256/8,buffer2) + ok(eq({buffer1:get(1)}, {10,23,54})) +end) + +N.test('mix saturation correctly ', function() + local buffer1 = pixbuf.newBuffer(1, 3) + local buffer2 = pixbuf.newBuffer(1, 3) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(256/2,buffer1,-256,buffer2) + ok(eq({buffer1:get(1)}, {0,0,0})) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(25600,buffer1,256/8,buffer2) + ok(eq({buffer1:get(1)}, {255,255,255})) + + buffer1:fill(10,22,54) + buffer2:fill(10,27,55) + buffer1:mix(-257,buffer1,255,buffer2) + ok(eq({buffer1:get(1)}, {0,5,1})) +end) + +N.test('power', function() + local buffer = pixbuf.newBuffer(2, 4) + buffer:fill(10,22,54,234) + ok(eq(buffer:power(), 2*(10+22+54+234))) +end) + +N.test('shift LOGICAL', function() + local buffer1 = pixbuf.newBuffer(4, 4) + local buffer2 = pixbuf.newBuffer(4, 4) + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,0,7,8) + ok(buffer1 ~= buffer2, "disequality pre shift") + buffer1:shift(2) + ok(buffer1 == buffer2, "shift right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12,0,0) + buffer1:shift(-2) + ok(buffer1 == buffer2, "shift left") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,0,8,12) + buffer1:shift(1, nil, 2,3) + ok(buffer1 == buffer2, "shift middle right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,9,0,12) + buffer1:shift(-1, nil, 2,3) + ok(buffer1 == buffer2, "shift middle left") + + -- bounds checks, handle gracefully as string:sub does + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,8,9,12,0) + buffer1:shift(-1, pixbuf.SHIFT_LOGICAL, 0,5) + ok(buffer1 == buffer2, "shift left out of bound") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,7,8,9) + buffer1:shift(1, pixbuf.SHIFT_LOGICAL, 0,5) + ok(buffer1 == buffer2, "shift right out of bound") + +end) + +N.test('shift LOGICAL issue #2946', function() + local buffer1 = pixbuf.newBuffer(4, 4) + local buffer2 = pixbuf.newBuffer(4, 4) + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,0,0,0) + buffer1:shift(4) + ok(buffer1 == buffer2, "shift all right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,0,0,0,0) + buffer1:shift(-4) + ok(buffer1 == buffer2, "shift all left") + + fail(function() buffer1:shift(10) end, "shifting more elements than buffer size") + fail(function() buffer1:shift(-6) end, "shifting more elements than buffer size") +end) + +N.test('shift CIRCULAR', function() + local buffer1 = pixbuf.newBuffer(4, 4) + local buffer2 = pixbuf.newBuffer(4, 4) + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12,7,8) + buffer1:shift(2, pixbuf.SHIFT_CIRCULAR) + ok(buffer1 == buffer2, "shift right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12,7,8) + buffer1:shift(-2, pixbuf.SHIFT_CIRCULAR) + ok(buffer1 == buffer2, "shift left") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,9,8,12) + buffer1:shift(1, pixbuf.SHIFT_CIRCULAR, 2,3) + ok(buffer1 == buffer2, "shift middle right") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,9,8,12) + buffer1:shift(-1, pixbuf.SHIFT_CIRCULAR, 2,3) + ok(buffer1 == buffer2, "shift middle left") + + -- bounds checks, handle gracefully as string:sub does + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,8,9,12,7) + buffer1:shift(-1, pixbuf.SHIFT_CIRCULAR, 0,5) + ok(buffer1 == buffer2, "shift left out of bound") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,12,7,8,9) + buffer1:shift(1, pixbuf.SHIFT_CIRCULAR, 0,5) + ok(buffer1 == buffer2, "shift right out of bound") + + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,12,7,8,9) + buffer1:shift(1, pixbuf.SHIFT_CIRCULAR, -12,12) + ok(buffer1 == buffer2, "shift right way out of bound") + +end) + +N.test('sub', function() + local buffer1 = pixbuf.newBuffer(4, 4) + initBuffer(buffer1,7,8,9,12) + buffer1 = buffer1:sub(4,3) + ok(eq(buffer1:size(), 0), "sub empty") + + local buffer2 = pixbuf.newBuffer(2, 4) + buffer1 = pixbuf.newBuffer(4, 4) + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,9,12) + buffer1 = buffer1:sub(3,4) + ok(buffer1 == buffer2, "sub") + + buffer1 = pixbuf.newBuffer(4, 4) + buffer2 = pixbuf.newBuffer(4, 4) + initBuffer(buffer1,7,8,9,12) + initBuffer(buffer2,7,8,9,12) + buffer1 = buffer1:sub(-12,33) + ok(buffer1 == buffer2, "out of bounds") +end) + +N.test('map', function() + local buffer1 = pixbuf.newBuffer(4, 4) + buffer1:fill(65,66,67,68) + + buffer1:map(function(a,b,c,d) return b,a,c,d end) + ok(eq("BACDBACDBACDBACD", buffer1:dump()), "swizzle") + + local buffer2 = pixbuf.newBuffer(4, 1) + buffer2:map(function(b,a,c,d) return c end, buffer1) -- luacheck: ignore + ok(eq("CCCC", buffer2:dump()), "projection") + + local buffer3 = pixbuf.newBuffer(4, 3) + buffer3:map(function(b,a,c,d) return a,b,d end, buffer1) -- luacheck: ignore + ok(eq("ABDABDABDABD", buffer3:dump()), "projection 2") + + buffer1:fill(70,71,72,73) + buffer1:map(function(c,a,b,d) return a,b,c,d end, buffer2, nil, nil, buffer3) + ok(eq("ABCDABCDABCDABCD", buffer1:dump()), "zip") + + buffer1 = pixbuf.newBuffer(2, 4) + buffer1:fill(70,71,72,73) + buffer2:set(1,"ABCD") + buffer3:set(1,"EFGHIJKLM") + buffer1:map(function(c,a,b,d) return a,b,c,d end, buffer2, 1, 2, buffer3, 2) + ok(eq("HIAJKLBM", buffer1:dump()), "partial zip") +end) + +--[[ +pixbuf.buffer:__concat() +--]] diff --git a/tests/NTest_ws2812.lua b/tests/NTest_ws2812.lua index f0c616c2..98dc0f40 100644 --- a/tests/NTest_ws2812.lua +++ b/tests/NTest_ws2812.lua @@ -58,7 +58,7 @@ N.test('replace correctly', function() buffer:replace(string.char(3,255,165,33,0,244,12,87,255), -5) ok(eq(buffer:dump(), string.char(3,255,165,33,0,244,12,87,255,0,0,0,0,0,0)), "RGBW") - fail(function() buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 4) end, "Does not fit into destination") + fail(function() buffer:replace(string.char(3,255,165,33,0,244,12,87,255), 4) end, "does not fit into destination") end) N.test('replace correctly issue #2921', function() @@ -91,7 +91,7 @@ N.test('fade correctly', function() ok(buffer:dump() == string.char(0,111,27), "RGB") buffer:fill(1,222,55) buffer:fade(3, ws2812.FADE_OUT) - ok(buffer:dump() == string.char(0,222/3,55/3), "RGB") + ok(buffer:dump() == string.char(0,math.floor(222/3),math.floor(55/3)), "RGB") buffer:fill(1,222,55) buffer:fade(3, ws2812.FADE_IN) ok(buffer:dump() == string.char(3,255,165), "RGB") diff --git a/tools/luacheck_config.lua b/tools/luacheck_config.lua index 653b6dd5..4857c24e 100644 --- a/tools/luacheck_config.lua +++ b/tools/luacheck_config.lua @@ -492,6 +492,17 @@ stds.nodemcu_libs = { create = empty } }, + pixbuf = { + fields = { + FADE_IN = empty, + FADE_OUT = empty, + SHIFT_CIRCULAR = empty, + SHIFT_LOGICAL = empty, + init = empty, + newBuffer = empty, + write = empty + } + }, pwm = { fields = { close = empty,