diff --git a/app/modules/ws2812.c b/app/modules/ws2812.c index 383070d2..c84c7456 100644 --- a/app/modules/ws2812.c +++ b/app/modules/ws2812.c @@ -19,7 +19,6 @@ typedef struct { - int canary; int size; uint8_t colorsPerLed; uint8_t values[0]; @@ -132,8 +131,7 @@ static int ws2812_write(lua_State* L) { } else if (type == LUA_TUSERDATA) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); buffer1 = buffer->values; length1 = buffer->colorsPerLed*buffer->size; @@ -156,8 +154,7 @@ static int ws2812_write(lua_State* L) { } else if (type == LUA_TUSERDATA) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 2); - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 2, "ws2812.buffer expected"); + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 2, "ws2812.buffer"); buffer2 = buffer->values; length2 = buffer->colorsPerLed*buffer->size; @@ -173,14 +170,13 @@ static int ws2812_write(lua_State* L) { return 0; } -// 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"); +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 (pos >= 0) ? pos : 0; +} +static ws2812_buffer *allocate_buffer(lua_State *L, int leds, int colorsPerLed) { // Allocate memory size_t size = sizeof(ws2812_buffer) + colorsPerLed*leds*sizeof(uint8_t); ws2812_buffer * buffer = (ws2812_buffer*)lua_newuserdata(L, size); @@ -193,16 +189,26 @@ static int ws2812_new_buffer(lua_State *L) { buffer->size = leds; buffer->colorsPerLed = colorsPerLed; - // Store canary for future type checks - buffer->canary = CANARY_VALUE; + 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); + + c_memset(buffer->values, 0, colorsPerLed * leds); return 1; } static int ws2812_buffer_fill(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); - - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); // Grab colors int i, j; @@ -230,11 +236,10 @@ static int ws2812_buffer_fill(lua_State* L) { } static int ws2812_buffer_fade(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); + 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, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); luaL_argcheck(L, fade > 0, 2, "fade value should be a strict positive number"); uint8_t * p = &buffer->values[0]; @@ -260,17 +265,25 @@ static int ws2812_buffer_fade(lua_State* L) { static int ws2812_buffer_shift(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); + 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 ); - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); - luaL_argcheck(L, shiftValue > 0-buffer->size && shiftValue < buffer->size, 2, "shifting more elements than buffer size"); + ptrdiff_t start = posrelat(luaL_optinteger(L, 4, 1), buffer->size); + ptrdiff_t end = posrelat(luaL_optinteger(L, 5, -1), buffer->size); + if (start < 1) start = 1; + if (end > (ptrdiff_t)buffer->size) end = (ptrdiff_t)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; // check if we want to shift at all - if (shift == 0) + if (shift == 0 || size <= 0) { return 0; } @@ -280,38 +293,38 @@ static int ws2812_buffer_shift(lua_State* L) { size_t shift_len, remaining_len; // calculate length of shift section and remaining section shift_len = shift*buffer->colorsPerLed; - remaining_len = (buffer->size-shift)*buffer->colorsPerLed; + remaining_len = (size-shift)*buffer->colorsPerLed; if (shiftValue > 0) { // Store the values which are moved out of the array (last n pixels) - c_memcpy(tmp_pixels, &buffer->values[(buffer->size-shift)*buffer->colorsPerLed], shift_len); + c_memcpy(tmp_pixels, &buffer->values[offset + (size-shift)*buffer->colorsPerLed], shift_len); // Move pixels to end - os_memmove(&buffer->values[shift*buffer->colorsPerLed], &buffer->values[0], remaining_len); + os_memmove(&buffer->values[offset + shift*buffer->colorsPerLed], &buffer->values[offset], remaining_len); // Fill beginning with temp data if (shift_type == SHIFT_LOGICAL) { - c_memset(&buffer->values[0], 0, shift_len); + c_memset(&buffer->values[offset], 0, shift_len); } else { - c_memcpy(&buffer->values[0], tmp_pixels, shift_len); + c_memcpy(&buffer->values[offset], tmp_pixels, shift_len); } } else { // Store the values which are moved out of the array (last n pixels) - c_memcpy(tmp_pixels, &buffer->values[0], shift_len); + c_memcpy(tmp_pixels, &buffer->values[offset], shift_len); // Move pixels to end - os_memmove(&buffer->values[0], &buffer->values[shift*buffer->colorsPerLed], remaining_len); + os_memmove(&buffer->values[offset], &buffer->values[offset + shift*buffer->colorsPerLed], remaining_len); // Fill beginning with temp data if (shift_type == SHIFT_LOGICAL) { - c_memset(&buffer->values[(buffer->size-shift)*buffer->colorsPerLed], 0, shift_len); + c_memset(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], 0, shift_len); } else { - c_memcpy(&buffer->values[(buffer->size-shift)*buffer->colorsPerLed], tmp_pixels, shift_len); + c_memcpy(&buffer->values[offset + (size-shift)*buffer->colorsPerLed], tmp_pixels, shift_len); } } // Free memory @@ -320,13 +333,107 @@ static int ws2812_buffer_shift(lua_State* L) { 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"); + size_t l = buffer->size; + ptrdiff_t start = posrelat(luaL_optinteger(L, 3, 1), l); + + 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"); + + c_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++) { + int val = 0; + for (src = 0; src < n_sources; src++) { + val += ((int)(source[src].values[i] * source[src].factor) >> 8); + } + + if (val < 0) { + val = 0; + } else if (val > 255) { + val = 255; + } + buffer->values[i] = 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_pushnumber(L, total); + + return 1; +} static int ws2812_buffer_get(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); const int led = luaL_checkinteger(L, 2) - 1; - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range"); int i; @@ -339,10 +446,9 @@ static int ws2812_buffer_get(lua_State* L) { } static int ws2812_buffer_set(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); const int led = luaL_checkinteger(L, 2) - 1; - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); luaL_argcheck(L, led >= 0 && led < buffer->size, 2, "index out of range"); int type = lua_type(L, 3); @@ -387,24 +493,94 @@ static int ws2812_buffer_set(lua_State* L) { } static int ws2812_buffer_size(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)lua_touserdata(L, 1); - - luaL_argcheck(L, buffer && buffer->canary == CANARY_VALUE, 1, "ws2812.buffer expected"); + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); lua_pushnumber(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 < 1) start = 1; + if (end > (ptrdiff_t)l) end = (ptrdiff_t)l; + if (start <= end) { + ws2812_buffer *result = allocate_buffer(L, end - start + 1, lhs->colorsPerLed); + c_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); + + c_memcpy(buffer->values, lhs->values, lhs->colorsPerLed * lhs->size); + c_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]; + c_sprintf(numbuf, "%d", buffer->values[p]); + luaL_addstring(&result, numbuf); + } + luaL_addchar(&result, ')'); + } + + luaL_addchar(&result, ']'); + luaL_pushresult(&result); + + return 1; +} + static const LUA_REG_TYPE ws2812_buffer_map[] = { + { LSTRKEY( "dump" ), LFUNCVAL( ws2812_buffer_dump )}, { LSTRKEY( "fade" ), LFUNCVAL( ws2812_buffer_fade )}, { LSTRKEY( "fill" ), LFUNCVAL( ws2812_buffer_fill )}, { LSTRKEY( "get" ), LFUNCVAL( ws2812_buffer_get )}, + { LSTRKEY( "replace" ), LFUNCVAL( ws2812_buffer_replace )}, + { LSTRKEY( "mix" ), LFUNCVAL( ws2812_buffer_mix )}, + { LSTRKEY( "power" ), LFUNCVAL( ws2812_buffer_power )}, { LSTRKEY( "set" ), LFUNCVAL( ws2812_buffer_set )}, - { LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )}, { LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift )}, + { LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )}, + { LSTRKEY( "sub" ), LFUNCVAL( ws2812_buffer_sub )}, + { LSTRKEY( "__concat" ),LFUNCVAL( ws2812_buffer_concat )}, { LSTRKEY( "__index" ), LROVAL( ws2812_buffer_map )}, + { LSTRKEY( "__tostring" ), LFUNCVAL( ws2812_buffer_tostring )}, { LNILKEY, LNILVAL} }; diff --git a/docs/en/modules/ws2812.md b/docs/en/modules/ws2812.md index ed8a8a2f..21f32ae5 100644 --- a/docs/en/modules/ws2812.md +++ b/docs/en/modules/ws2812.md @@ -71,7 +71,7 @@ ws2812.write(nil, string.char(0, 255, 0, 0, 255, 0)) -- turn the two first RGB l 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. +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. #### Example Led chaser with a RGBW strip @@ -181,6 +181,92 @@ The number of given bytes must match the number of bytesPerLed of the buffer 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. @@ -200,14 +286,17 @@ buffer:fade(2) buffer:fade(2, ws2812.FADE_IN) ``` ## ws2812.buffer:shift() -Shift the content of the buffer in positive or negative direction. This allows simple animation effects. +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])` +`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` @@ -216,3 +305,42 @@ Shift the content of the buffer in positive or negative direction. This allows s ```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) +``` + + +