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 <HHHartmann@users.noreply.github.com>
This commit is contained in:
Nathaniel Wesley Filardo 2021-01-06 23:35:34 +00:00 committed by GitHub
parent 9a26e0a94b
commit 85df6b588d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1632 additions and 939 deletions

View File

@ -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

View File

@ -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];
const char *buf;
uint32_t nbr_frames;
switch(lua_type(L, 3)) {
case LUA_TSTRING: {
size_t buf_len;
const char *buf = luaL_checklstring(L, 3, &buf_len);
uint32_t nbr_frames = buf_len / 4;
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.");

723
app/modules/pixbuf.c Normal file
View File

@ -0,0 +1,723 @@
#include "module.h"
#include "lauxlib.h"
#include <string.h>
#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);

50
app/modules/pixbuf.h Normal file
View File

@ -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

View File

@ -5,6 +5,8 @@
#include <string.h>
#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;
}

View File

@ -5,6 +5,8 @@
#include <string.h>
#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();

View File

@ -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;
}

View File

@ -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 <stdlib.h>
#include <math.h>
#include <string.h>
#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_ */

View File

@ -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

View File

@ -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)<br />
- `G1` the first pixel's Green channel (0-255)

342
docs/modules/pixbuf.md Normal file
View File

@ -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)
```

View File

@ -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`

View File

@ -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)
```

288
tests/NTest_pixbuf.lua Normal file
View File

@ -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()
--]]

View File

@ -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")

View File

@ -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,