From ed56d949ee4d00ec10e29ec91d87bcc9b76b9d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konrad=20H=C3=BCbner?= Date: Tue, 23 Jan 2018 21:05:04 +0100 Subject: [PATCH] ws2812 effects library (#2215) * ws2812 effects and color utils modules added * Added documentation for new modules to mkdocs.yml * changed mode option to string, documentation, default modules fixed * updated user_modules.h --- app/include/user_modules.h | 2 + app/modules/color_utils.c | 246 +++++++ app/modules/color_utils.h | 39 ++ app/modules/ws2812.c | 85 ++- app/modules/ws2812.h | 32 + app/modules/ws2812_effects.c | 1060 +++++++++++++++++++++++++++++ docs/en/modules/color-utils.md | 61 ++ docs/en/modules/ws2812-effects.md | 179 +++++ mkdocs.yml | 2 + 9 files changed, 1675 insertions(+), 31 deletions(-) create mode 100644 app/modules/color_utils.c create mode 100644 app/modules/color_utils.h create mode 100644 app/modules/ws2812.h create mode 100644 app/modules/ws2812_effects.c create mode 100644 docs/en/modules/color-utils.md create mode 100644 docs/en/modules/ws2812-effects.md diff --git a/app/include/user_modules.h b/app/include/user_modules.h index d2811d8e..1afdde13 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -28,6 +28,7 @@ //#define LUA_USE_MODULES_BME280 //#define LUA_USE_MODULES_BME680 //#define LUA_USE_MODULES_COAP +//#define LUA_USE_MODULES_COLOR_UTILS //#define LUA_USE_MODULES_CRON //#define LUA_USE_MODULES_CRYPTO #define LUA_USE_MODULES_DHT @@ -82,6 +83,7 @@ //#define LUA_USE_MODULES_WPS //#define LUA_USE_MODULES_WS2801 //#define LUA_USE_MODULES_WS2812 +//#define LUA_USE_MODULES_WS2812_EFFECTS //#define LUA_USE_MODULES_XPT2046 #endif /* LUA_CROSS_COMPILER */ diff --git a/app/modules/color_utils.c b/app/modules/color_utils.c new file mode 100644 index 00000000..ce244b0a --- /dev/null +++ b/app/modules/color_utils.c @@ -0,0 +1,246 @@ +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" +#include "c_stdlib.h" +#include "c_math.h" +#include "c_string.h" +#include "user_interface.h" +#include "osapi.h" + +#include "color_utils.h" + +#define min(a,b) ((a) < (b) ? (a) : (b)) +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define abs(a) ((a) > 0 ? (a) : (0-a)) +#define min3(a,b, c) min((a), min((b), (c))) +#define max3(a,b, c) max((a), max((b), (c))) + +#define CANARY_VALUE 0x37383132 + + +// convert hsv to grb value +uint32_t hsv2grb(uint16_t hue, uint8_t sat, uint8_t val) +{ + uint16_t H_accent = (hue % 360) / 60; + uint16_t bottom = ((255 - sat) * val)>>8; + uint16_t top = val; + uint8_t rising = ((top-bottom) *(hue%60 ) ) / 60 + bottom; + uint8_t falling = ((top-bottom) *(60-hue%60) ) / 60 + bottom; + + uint8_t r; + uint8_t g; + uint8_t b; + + switch(H_accent) { + case 0: + r = top; + g = rising; + b = bottom; + break; + + case 1: + r = falling; + g = top; + b = bottom; + break; + + case 2: + r = bottom; + g = top; + b = rising; + break; + + case 3: + r = bottom; + g = falling; + b = top; + break; + + case 4: + r = rising; + g = bottom; + b = top; + break; + + case 5: + r = top; + g = bottom; + b = falling; + break; + } + uint32_t result = (g << 16) | (r << 8) | b; + return result; +} + +// convert hsv to grbw value +uint32_t hsv2grbw(uint16_t hue, uint8_t sat, uint8_t val) { + + uint32_t grb = hsv2grb(hue, sat, val); + + uint8_t g = ((grb & 0x00FF0000) >> 16); + uint8_t r = ((grb & 0x0000FF00) >> 8); + uint8_t b = (grb & 0x000000FF); + + // calculate white component + uint8_t w = min3(g, r, b); + g = g - w; + r = r - w; + b = b - w; + uint32_t grbw = (g << 24) | (r << 16) | (b << 8) | w; + + return grbw; +} + + +// convert grb to hsv value +uint32_t grb2hsv(uint8_t g, uint8_t r, uint8_t b) { + uint8_t m = min3(r, g, b); + uint8_t M = max3(r, g, b); + uint8_t delta = M - m; + + int hue = 0; + int saturation = 0; + int value = 0; + + if(delta == 0) { + /* Achromatic case (i.e. grayscale) */ + hue = -1; /* undefined */ + saturation = 0; + } else { + int h; + + if(r == M) + h = ((g-b)*60) / delta; + else if(g == M) + h = ((b-r)*60) / delta + 120; + else /*if(b == M)*/ + h = ((r-g)*60) / delta + 240; + + if(h < 0) + h += 360; + + hue = h; + + /* The constatnt 8 is tuned to statistically cause as little + * tolerated mismatches as possible in RGB -> HSV -> RGB conversion. + * (See the unit test at the bottom of this file.) + */ + saturation = (256*delta-8) / M; + } + value = M; + + uint32_t result = (hue << 16) | (saturation << 8) | value; + return result; + +} + +/* + * Put a value 0 to 360 in to get a color value. + * The colours are a transition r -> g -> b -> back to r + * Inspired by the Adafruit examples. + */ +uint32_t color_wheel(uint16_t pos) { + return hsv2grb(pos, 255, 255); +} + + +// convert hsv to grb value +static int cu_hsv2grb(lua_State *L) { + const int hue = luaL_checkint(L, 1); + const int sat = luaL_checkint(L, 2); + const int val = luaL_checkint(L, 3); + + luaL_argcheck(L, hue >= 0 && hue <= 360, 1, "should be a 0-360"); + luaL_argcheck(L, sat >= 0 && sat <= 255, 2, "should be 0-255"); + luaL_argcheck(L, val >= 0 && val <= 255, 3, "should be 0-255"); + + // convert to grb + uint32_t tmp_color = hsv2grb(hue, sat, val); + + // return + lua_pushnumber(L, (tmp_color & 0x00FF0000) >> 16); + lua_pushnumber(L, (tmp_color & 0x0000FF00) >> 8); + lua_pushnumber(L, (tmp_color & 0x000000FF)); + + return 3; +} + + +// convert hsv to grbw value +static int cu_hsv2grbw(lua_State *L) { + const int hue = luaL_checkint(L, 1); + const int sat = luaL_checkint(L, 2); + const int val = luaL_checkint(L, 3); + + luaL_argcheck(L, hue >= 0 && hue <= 360, 1, "should be a 0-360"); + luaL_argcheck(L, sat >= 0 && sat <= 255, 2, "should be 0-255"); + luaL_argcheck(L, val >= 0 && val <= 255, 3, "should be 0-255"); + + // convert to grbw + uint32_t tmp_color = hsv2grbw(hue, sat, val); + + // return g, r, b, w + lua_pushnumber(L, (tmp_color & 0xFF000000) >> 24); + lua_pushnumber(L, (tmp_color & 0x00FF0000) >> 16); + lua_pushnumber(L, (tmp_color & 0x0000FF00) >> 8); + lua_pushnumber(L, (tmp_color & 0x000000FF)); + + return 4; +} + + +// create a color wheel value +static int cu_color_wheel(lua_State *L) { + const int wheel_index = luaL_checkint(L, 1); + + luaL_argcheck(L, wheel_index >= 0 && wheel_index <= 360, 1, "should be a 0-360"); + + uint32_t color = color_wheel(wheel_index); + + uint8_t r = (color & 0x00FF0000) >> 16; + uint8_t g = (color & 0x0000FF00) >> 8; + uint8_t b = (color & 0x000000FF) >> 0; + + // return + lua_pushnumber(L, g); + lua_pushnumber(L, r); + lua_pushnumber(L, b); + + return 3; +} + +// convert grb values to hsv +static int cu_grb2hsv(lua_State *L) { + + const int g = luaL_checkint(L, 1); + const int r = luaL_checkint(L, 2); + const int b = luaL_checkint(L, 3); + + luaL_argcheck(L, g == r && g == b, 1, "greyscale value cannot be converted to hsv"); + + uint32_t hsv = grb2hsv(g, r, b); + + uint16_t h = (hsv & 0xFFFF0000) >> 16; + uint8_t s = (hsv & 0x0000FF00) >> 8; + uint8_t v = (hsv & 0x000000FF) >> 0; + + // return + lua_pushnumber(L, h); + lua_pushnumber(L, s); + lua_pushnumber(L, v); + + return 3; +} + + +static const LUA_REG_TYPE color_utils_map[] = +{ + { LSTRKEY( "hsv2grb" ), LFUNCVAL( cu_hsv2grb )}, + { LSTRKEY( "hsv2grbw" ), LFUNCVAL( cu_hsv2grbw )}, + { LSTRKEY( "colorWheel" ), LFUNCVAL( cu_color_wheel )}, + { LSTRKEY( "grb2hsv" ), LFUNCVAL( cu_grb2hsv )}, + { LNILKEY, LNILVAL} +}; + +NODEMCU_MODULE(COLOR_UTILS, "color_utils", color_utils_map, NULL); diff --git a/app/modules/color_utils.h b/app/modules/color_utils.h new file mode 100644 index 00000000..c937cd7f --- /dev/null +++ b/app/modules/color_utils.h @@ -0,0 +1,39 @@ +#ifndef APP_MODULES_COLOR_UTILS_H_ +#define APP_MODULES_COLOR_UTILS_H_ + +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" +#include "c_stdlib.h" +#include "c_math.h" +#include "c_string.h" +#include "user_interface.h" +#include "osapi.h" + +/** +* Convert hsv to grb +* hue is 0-360, sat and val are 0-255 +*/ +uint32_t hsv2grb(uint16_t hue, uint8_t sat, uint8_t val); +/** +* Convert hsv to grbw +* hue is 0-360, sat and val are 0-255 +*/ +uint32_t hsv2grbw(uint16_t hue, uint8_t sat, uint8_t val); +/** +* Convert grb to hsv +* g, r, b are 0-255 +*/ +uint32_t grb2hsv(uint8_t g, uint8_t r, uint8_t b); + + +/** +* The color wheel function provides colors from r -> g -> b -> r. +* They are fully saturated and with full brightness. +* degree is from 0-360 +*/ +uint32_t color_wheel(uint16_t degree); + + +#endif /* APP_MODULES_COLOR_UTILS_H_ */ diff --git a/app/modules/ws2812.c b/app/modules/ws2812.c index 88b71970..3ca69c51 100644 --- a/app/modules/ws2812.c +++ b/app/modules/ws2812.c @@ -3,26 +3,22 @@ #include "lmem.h" #include "platform.h" #include "c_stdlib.h" +#include "c_math.h" #include "c_string.h" #include "user_interface.h" #include "driver/uart.h" #include "osapi.h" +#include "ws2812.h" + #define CANARY_VALUE 0x32383132 #define MODE_SINGLE 0 #define MODE_DUAL 1 -#define FADE_IN 1 -#define FADE_OUT 0 -#define SHIFT_LOGICAL 0 -#define SHIFT_CIRCULAR 1 -typedef struct { - int size; - uint8_t colorsPerLed; - uint8_t values[0]; -} ws2812_buffer; + + // 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 @@ -65,7 +61,7 @@ static int ws2812_init(lua_State* L) { // ws2812.init() should be called first // // NODE_DEBUG should not be activated because it also uses UART1 -static void ICACHE_RAM_ATTR ws2812_write_data(const uint8_t *pixels, uint32_t length, const uint8_t *pixels2, uint32_t length2) { +void ICACHE_RAM_ATTR ws2812_write_data(const uint8_t *pixels, uint32_t length, const uint8_t *pixels2, uint32_t length2) { // Data are sent LSB first, with a start bit at 0, an end bit at 1 and all inverted // 0b00110111 => 110111 => [0]111011[1] => 10001000 => 00 @@ -194,6 +190,7 @@ static ws2812_buffer *allocate_buffer(lua_State *L, int leds, int 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); @@ -209,17 +206,11 @@ static int ws2812_new_buffer(lua_State *L) { return 1; } -static int ws2812_buffer_fill(lua_State* L) { - ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); + +int ws2812_buffer_fill(ws2812_buffer * buffer, int * colors) { // Grab colors int i, j; - int * colors = luaM_malloc(L, buffer->colorsPerLed * sizeof(int)); - - for (i = 0; i < buffer->colorsPerLed; i++) - { - colors[i] = luaL_checkinteger(L, 2+i); - } // Fill buffer uint8_t * p = &buffer->values[0]; @@ -231,6 +222,23 @@ static int ws2812_buffer_fill(lua_State* L) { } } + 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); @@ -266,13 +274,10 @@ static int ws2812_buffer_fade(lua_State* L) { } -static int ws2812_buffer_shift(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 ); +int ws2812_buffer_shift(ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end) { - ptrdiff_t start = posrelat(luaL_optinteger(L, 4, 1), buffer->size); - ptrdiff_t end = posrelat(luaL_optinteger(L, 5, -1), buffer->size); + ptrdiff_t start = posrelat(pos_start, buffer->size); + ptrdiff_t end = posrelat(pos_end, buffer->size); if (start < 1) start = 1; if (end > (ptrdiff_t)buffer->size) end = (ptrdiff_t)buffer->size; @@ -280,7 +285,7 @@ static int ws2812_buffer_shift(lua_State* L) { 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"); + //luaL_argcheck(L, shiftValue > 0-size && shiftValue < size, 2, "shifting more elements than buffer size"); int shift = shiftValue >= 0 ? shiftValue : -shiftValue; @@ -290,7 +295,7 @@ static int ws2812_buffer_shift(lua_State* L) { return 0; } - uint8_t * tmp_pixels = luaM_malloc(L, buffer->colorsPerLed * sizeof(uint8_t) * shift); + uint8_t * tmp_pixels = c_malloc(buffer->colorsPerLed * sizeof(uint8_t) * shift); int i,j; size_t shift_len, remaining_len; // calculate length of shift section and remaining section @@ -330,11 +335,27 @@ static int ws2812_buffer_shift(lua_State* L) { } } // Free memory - luaM_free(L, tmp_pixels); + c_free(tmp_pixels); return 0; } + +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(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"); @@ -392,7 +413,7 @@ static int ws2812_buffer_mix(lua_State* L) { 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; } @@ -528,7 +549,7 @@ static int ws2812_buffer_concat(lua_State* L) { 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); @@ -569,17 +590,18 @@ static int ws2812_buffer_tostring(lua_State* L) { 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( "fill" ), LFUNCVAL( ws2812_buffer_fill_lua )}, { 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( "shift" ), LFUNCVAL( ws2812_buffer_shift )}, + { LSTRKEY( "shift" ), LFUNCVAL( ws2812_buffer_shift_lua )}, { LSTRKEY( "size" ), LFUNCVAL( ws2812_buffer_size )}, { LSTRKEY( "sub" ), LFUNCVAL( ws2812_buffer_sub )}, { LSTRKEY( "__concat" ),LFUNCVAL( ws2812_buffer_concat )}, @@ -588,6 +610,7 @@ static const LUA_REG_TYPE ws2812_buffer_map[] = { LNILKEY, LNILVAL} }; + static const LUA_REG_TYPE ws2812_map[] = { { LSTRKEY( "init" ), LFUNCVAL( ws2812_init )}, diff --git a/app/modules/ws2812.h b/app/modules/ws2812.h new file mode 100644 index 00000000..40c0fb26 --- /dev/null +++ b/app/modules/ws2812.h @@ -0,0 +1,32 @@ +#ifndef APP_MODULES_WS2812_H_ +#define APP_MODULES_WS2812_H_ + +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" +#include "c_stdlib.h" +#include "c_math.h" +#include "c_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 + + +typedef struct { + int size; + uint8_t colorsPerLed; + uint8_t values[0]; +} ws2812_buffer; + + +void ICACHE_RAM_ATTR ws2812_write_data(const uint8_t *pixels, uint32_t length, const uint8_t *pixels2, uint32_t length2); +int ws2812_buffer_shift(ws2812_buffer * buffer, int shiftValue, unsigned shift_type, int pos_start, int pos_end); +int ws2812_buffer_fill(ws2812_buffer * buffer, int * colors); + +#endif /* APP_MODULES_WS2812_H_ */ diff --git a/app/modules/ws2812_effects.c b/app/modules/ws2812_effects.c new file mode 100644 index 00000000..f2281b87 --- /dev/null +++ b/app/modules/ws2812_effects.c @@ -0,0 +1,1060 @@ +#include "module.h" +#include "lauxlib.h" +#include "lmem.h" +#include "platform.h" +#include "c_stdlib.h" +#include "c_math.h" +#include "c_string.h" +#include "user_interface.h" +#include "driver/uart.h" +#include "osapi.h" +#include "swTimer/swTimer.h" + +#include "ws2812.h" +#include "color_utils.h" + +#define CANARY_VALUE 0x32372132 + +#define DEFAULT_MODE 0 +#define DEFAULT_COLOR 0xFF0000 + +#define UINT32_MAX 4294967295U + +#define SPEED_MIN 0 +#define SPEED_MAX 255 +#define SPEED_DEFAULT 150 +#define DELAY_DEFAULT 100 + +#define BRIGHTNESS_MIN 0 +#define BRIGHTNESS_MAX 255 +#define BRIGHTNESS_DEFAULT 100 + +#define EFFECT_PARAM_INVALID -10000 + +#define LIBRARY_NOT_INITIALIZED_ERROR_MSG "please call init() first" + + +#define min(a,b) ((a) < (b) ? (a) : (b)) +#define max(a,b) ((a) > (b) ? (a) : (b)) +#define abs(a) ((a) > 0 ? (a) : (0-a)) +#define min3(a,b, c) min((a), min((b), (c))) +#define max3(a,b, c) max((a), max((b), (c))) + + + +typedef struct { + ws2812_buffer *buffer; + int buffer_ref; + uint32_t mode_delay; + uint32_t counter_mode_call; + uint32_t counter_mode_step; + uint8_t mode_color_index; + uint8_t speed; + uint8_t brightness; + os_timer_t os_t; + uint8_t running; + uint8_t effect_type; + uint8_t color[4]; + int effect_int_param1; +} ws2812_effects; + + + +enum ws2812_effects_type { + WS2812_EFFECT_STATIC, + WS2812_EFFECT_BLINK, + WS2812_EFFECT_GRADIENT, + WS2812_EFFECT_GRADIENT_RGB, + WS2812_EFFECT_RANDOM_COLOR, + WS2812_EFFECT_RAINBOW, + WS2812_EFFECT_RAINBOW_CYCLE, + WS2812_EFFECT_FLICKER, + WS2812_EFFECT_FIRE_FLICKER, + WS2812_EFFECT_FIRE_FLICKER_SOFT, + WS2812_EFFECT_FIRE_FLICKER_INTENSE, + WS2812_EFFECT_HALLOWEEN, + WS2812_EFFECT_CIRCUS_COMBUSTUS, + WS2812_EFFECT_LARSON_SCANNER, + WS2812_EFFECT_CYCLE, + WS2812_EFFECT_COLOR_WIPE, + WS2812_EFFECT_RANDOM_DOT +}; + + +static ws2812_effects *state; + + +//----------------- +// UTILITY METHODS +//----------------- + + +static int ws2812_write(ws2812_buffer* buffer) { + size_t length1, length2; + const char *buffer1, *buffer2; + + buffer1 = 0; + length1 = 0; + + buffer1 = buffer->values; + length1 = buffer->colorsPerLed*buffer->size; + + buffer2 = 0; + length2 = 0; + + // Send the buffers + ws2812_write_data(buffer1, length1, buffer2, length2); + + return 0; +} + + +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; + + int offset = pixel * buffer->colorsPerLed; + buffer->values[offset] = g; + buffer->values[offset+1] = r; + buffer->values[offset+2] = b; + if (buffer->colorsPerLed == 4) { + buffer->values[offset+3] = w; + } + + return 0; +} + + +/* +* Returns a new, random color wheel index with a minimum distance of 42 from pos. +*/ +static uint8_t get_random_wheel_index(uint8_t pos) +{ + uint8_t r = 0; + uint8_t x = 0; + uint8_t y = 0; + uint8_t d = 0; + + while(d < 42) { + r = rand() % 360; + x = abs(pos - r); + y = 360 - x; + d = min(x, y); + } + + return r; +} + +//----------------- +// EFFECTS LIBRARY +//----------------- + +/** +* initialized ws2812_effects with the buffer to use +*/ +static int ws2812_effects_init(lua_State *L) { + ws2812_buffer * buffer = (ws2812_buffer*)luaL_checkudata(L, 1, "ws2812.buffer"); + luaL_argcheck(L, buffer != NULL, 1, "no valid buffer provided"); + // get rid of old state + if (state != NULL) { + luaL_unref(L, LUA_REGISTRYINDEX, state->buffer_ref); + os_free((void *) state); + } + // Allocate memory and set all to zero + size_t size = sizeof(ws2812_effects) + buffer->colorsPerLed*sizeof(uint8_t); + state = (ws2812_effects *) os_zalloc(size); + // initialize + state->speed = SPEED_DEFAULT; + state->mode_delay = DELAY_DEFAULT; + state->brightness = BRIGHTNESS_DEFAULT; + state->buffer = buffer; + + state->buffer_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + return 0; +} + + + +/* +* set color for single color effects +*/ +static int ws2812_effects_set_color(lua_State* L) { + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + + uint8_t g = luaL_checkinteger(L, 1); + uint8_t r = luaL_checkinteger(L, 2); + uint8_t b = luaL_checkinteger(L, 3); + uint8_t w = luaL_optinteger(L, 4, 0 ); + + state->color[0] = g; + state->color[1] = r; + state->color[2] = b; + state->color[3] = w; + return 0; +} + + +static int ws2812_effects_get_speed(lua_State* L) { + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + lua_pushnumber(L, state->speed); + return 1; +} + +static int ws2812_effects_set_speed(lua_State* L) { + uint8_t speed = luaL_checkinteger(L, 1); + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + luaL_argcheck(L, speed >= 0 && speed <= 255, 1, "should be a 0-255"); + state->speed = speed; + state->mode_delay = 10; + return 0; +} + +static int ws2812_effects_get_delay(lua_State* L) { + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + lua_pushnumber(L, state->mode_delay); + return 1; +} + +static int ws2812_effects_set_delay(lua_State* L) { + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + const int delay = luaL_checkinteger(L, 1); + luaL_argcheck(L, delay >= 10, 1, "must be equal / larger than 10"); + + state->mode_delay = delay; + state->speed = 0; + return 1; +} + + + +static int ws2812_effects_set_brightness(lua_State* L) { + uint8_t brightness = luaL_checkint(L, 1); + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + luaL_argcheck(L, brightness >= 0 && brightness < 256, 1, "should be a 0-255"); + state->brightness = brightness; + return 0; +} + + + +static int ws2812_effects_fill_buffer(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; + + // Fill buffer + int i; + uint8_t * p = &buffer->values[0]; + for(i = 0; i < buffer->size; i++) { + *p++ = g * state->brightness / 255; + *p++ = r * state->brightness / 255; + *p++ = b * state->brightness / 255; + if (buffer->colorsPerLed == 4) { + *p++ = w * state->brightness / 255; + } + } + + return 0; +} + + +//------------------ +// basic methods +//------------------ + + +/* +* Cycles all LEDs at once through a rainbow. +*/ +static int ws2812_effects_fill_color() { + + uint8_t g = state->color[0]; + uint8_t r = state->color[1]; + uint8_t b = state->color[2]; + uint8_t w = state->color[3]; + + uint32_t color = (w << 24) | (g << 16) | (r << 8) | b; + + ws2812_effects_fill_buffer(color); + + return 0; +} + + + +//----------------- +// EFFECFTS +//----------------- + + + +/* +* blink with set color +*/ +static int ws2812_effects_mode_blink() { + if(state->counter_mode_call % 2 == 1) { + // on + ws2812_effects_fill_color(); + } + else { + // off + ws2812_buffer * buffer = state->buffer; + c_memset(&buffer->values[0], 0, buffer->size * buffer->colorsPerLed); + } + return 0; +} + + + +static int ws2812_effects_gradient(const char *gradient_spec, size_t length1) { + + ws2812_buffer * buffer = state->buffer; + + int segments = (length1 / buffer->colorsPerLed) - 1; + int segmentSize = buffer->size / segments; + + uint8_t g1, r1, b1, g2, r2, b2; + int i,j,k; + + g2 = *gradient_spec++; + r2 = *gradient_spec++; + b2 = *gradient_spec++; + // skip non-rgb components + for (j = 3; j < buffer->colorsPerLed; j++) + { + *gradient_spec++; + } + + // reference to buffer memory + uint8_t * p = &buffer->values[0]; + + uint16_t h1,h2; + uint8_t s,v,s1,v1,s2,v2; + + for (k = 0; k < segments; k++) { + g1 = g2; + r1 = r2; + b1 = b2; + uint32_t hsv1 = grb2hsv(g1, r1, b1); + h1 = (hsv1 & 0xFFFF0000) >> 16; + s1 = (hsv1 & 0x0000FF00) >> 8; + v1 = (hsv1 & 0x000000FF); + + g2 = *gradient_spec++; + r2 = *gradient_spec++; + b2 = *gradient_spec++; + for (j = 3; j < buffer->colorsPerLed; j++) + { + *gradient_spec++; + } + + uint32_t hsv2 = grb2hsv(g2, r2, b2); + h2 = (hsv2 & 0xFFFF0000) >> 16; + s2 = (hsv1 & 0x0000FF00) >> 8; + v2 = (hsv1 & 0x000000FF); + + // get distance and direction to use + int maxCCW = h1 > h2 ? h1 - h2 : 360 + h1 - h2; + int maxCW = h1 > h2 ? 360 + h2 - h1 : h2 - h1; + + // Fill buffer + 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)); + } + + int steps = numPixels - 1; + + for(i = 0; i < numPixels; i++) { + // calculate HSV values + //h = h1 + ((h2-h1) * i / fillSize); + int h = maxCCW > maxCW ? h1 + ((maxCW * i / steps) % 360) : h1 - (maxCCW * i / steps); + if (h < 0) h = h + 360; + if (h > 359) h = h - 360; + s = s1 + ((s2-s1) * i / steps); + v = v1 + ((v2-v1) * i / steps); + // convert to RGB + uint32_t grb = hsv2grb(h, s, v); + + *p++ = ((grb & 0x00FF0000) >> 16) * state->brightness / 255; + *p++ = ((grb & 0x0000FF00) >> 8) * state->brightness / 255; + *p++ = (grb & 0x000000FF) * state->brightness / 255; + + for (j = 3; j < buffer->colorsPerLed; j++) { + *p++ = 0; + } + } + } + + return 0; +} + + + +static int ws2812_effects_gradient_rgb(const char *buffer1, size_t length1) { + + ws2812_buffer * buffer = state->buffer; + + int segments = (length1 / buffer->colorsPerLed) - 1; + int segmentSize = buffer->size / segments; + + uint8_t g1, r1, b1, g2, r2, b2; + int i,j,k; + + g2 = *buffer1++; + r2 = *buffer1++; + b2 = *buffer1++; + // skip non-rgb components + for (j = 3; j < buffer->colorsPerLed; j++) + { + *buffer1++; + } + + // reference to buffer memory + uint8_t * p = &buffer->values[0]; + for (k = 0; k < segments; k++) { + g1 = g2; + r1 = r2; + b1 = b2; + + g2 = *buffer1++; + r2 = *buffer1++; + b2 = *buffer1++; + + for (j = 3; j < buffer->colorsPerLed; j++) { + *buffer1++; + } + + // Fill buffer + 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)); + } + + int steps = numPixels - 1; + + for(i = 0; i < numPixels; i++) { + *p++ = (g1 + ((g2-g1) * i / steps)) * state->brightness / 255; + *p++ = (r1 + ((r2-r1) * i / steps)) * state->brightness / 255; + *p++ = (b1 + ((b2-b1) * i / steps)) * state->brightness / 255; + for (j = 3; j < buffer->colorsPerLed; j++) + { + *p++ = 0; + } + } + } + + return 0; +} + + +/* +* Lights all LEDs in one random color up. Then switches them +* to the next random color. +*/ +static int ws2812_effects_mode_random_color() { + state->mode_color_index = get_random_wheel_index(state->mode_color_index); + ws2812_buffer * buffer = state->buffer; + + uint32_t color = color_wheel(state->mode_color_index); + uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / 255; + uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / 255; + uint8_t b = ((color & 0x000000FF) >> 0) * state->brightness / 255; + + // Fill buffer + int i,j; + uint8_t * p = &buffer->values[0]; + for(i = 0; i < buffer->size; i++) { + *p++ = g; + *p++ = r; + *p++ = b; + for (j = 3; j < buffer->colorsPerLed; j++) + { + *p++ = 0; + } + } +} + + +/* +* Cycles all LEDs at once through a rainbow. +*/ +static int ws2812_effects_mode_rainbow() { + + ws2812_buffer * buffer = state->buffer; + + uint32_t color = color_wheel(state->counter_mode_step); + uint8_t r = (color & 0x00FF0000) >> 16; + uint8_t g = (color & 0x0000FF00) >> 8; + uint8_t b = (color & 0x000000FF) >> 0; + + // Fill buffer + int i,j; + uint8_t * p = &buffer->values[0]; + for(i = 0; i < buffer->size; i++) { + *p++ = g * state->brightness / 255; + *p++ = r * state->brightness / 255; + *p++ = b * state->brightness / 255; + for (j = 3; j < buffer->colorsPerLed; j++) + { + *p++ = 0; + } + } + + state->counter_mode_step = (state->counter_mode_step + 1) % 360; + return 0; +} + + +/* +* Cycles a rainbow over the entire string of LEDs. +*/ +static int ws2812_effects_mode_rainbow_cycle(int repeat_count) { + + ws2812_buffer * 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; + uint32_t color = color_wheel(wheel_index); + uint8_t r = ((color & 0x00FF0000) >> 16) * state->brightness / 255; + uint8_t g = ((color & 0x0000FF00) >> 8) * state->brightness / 255; + uint8_t b = ((color & 0x000000FF) >> 0) * state->brightness / 255; + *p++ = g; + *p++ = r; + *p++ = b; + for (j = 3; j < buffer->colorsPerLed; j++) + { + *p++ = 0; + } + } + + return 0; +} + + + +/* +* Random flickering. +*/ +static int ws2812_effects_mode_flicker_int(uint8_t max_flicker) { + + ws2812_buffer * buffer = state->buffer; + + uint8_t p_g = state->color[0]; + uint8_t p_r = state->color[1]; + uint8_t p_b = state->color[2]; + + // Fill buffer + int i,j; + uint8_t * p = &buffer->values[0]; + for(i = 0; i < buffer->size; i++) { + int flicker = rand() % (max_flicker > 0 ? max_flicker : 1); + int r1 = p_r-flicker; + int g1 = p_g-flicker; + int b1 = p_b-flicker; + if(g1<0) g1=0; + if(r1<0) r1=0; + if(b1<0) b1=0; + *p++ = g1 * state->brightness / 255; + *p++ = r1 * state->brightness / 255; + *p++ = b1 * state->brightness / 255; + for (j = 3; j < buffer->colorsPerLed; j++) { + *p++ = 0; + } + } + + return 0; +} + +/** +* Halloween effect +*/ +static int ws2812_effects_mode_halloween() { + ws2812_buffer * buffer = state->buffer; + + int g1 = 50 * state->brightness / 255; + int r1 = 255 * state->brightness / 255; + int b1 = 0 * state->brightness / 255; + + int g2 = 0 * state->brightness / 255; + int r2 = 255 * state->brightness / 255; + int b2 = 130 * state->brightness / 255; + + + // Fill buffer + int i,j; + uint8_t * p = &buffer->values[0]; + for(i = 0; i < buffer->size; 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++) + { + *p++ = 0; + } + } + + return 0; +} + + + +static int ws2812_effects_mode_circus_combustus() { + ws2812_buffer * buffer = state->buffer; + + int g1 = 0 * state->brightness / 255; + int r1 = 255 * state->brightness / 255; + int b1 = 0 * state->brightness / 255; + + int g2 = 255 * state->brightness / 255; + int r2 = 255 * state->brightness / 255; + int b2 = 255 * state->brightness / 255; + + // Fill buffer + int i,j; + uint8_t * p = &buffer->values[0]; + for(i = 0; i < buffer->size; i++) { + if (i % 6 < 2) { + *p++ = g1; + *p++ = r1; + *p++ = b1; + } + else if (i % 6 < 4) { + *p++ = g2; + *p++ = r2; + *p++ = b2; + } + else { + *p++ = 0; + *p++ = 0; + *p++ = 0; + } + for (j = 3; j < buffer->colorsPerLed; j++) + { + *p++ = 0; + } + } + + return 0; +} + + + + +/* +* K.I.T.T. +*/ +static int ws2812_effects_mode_larson_scanner() { + + ws2812_buffer * buffer = state->buffer; + int led_index = 0; + + for(int i=0; i < buffer->size * buffer->colorsPerLed; i++) { + buffer->values[i] = buffer->values[i] >> 1; + } + + uint16_t pos = 0; + + if(state->counter_mode_step < buffer->size) { + pos = state->counter_mode_step; + } else { + pos = (buffer->size * 2) - state->counter_mode_step - 2; + } + pos = pos * buffer->colorsPerLed; + 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); +} + + + +static int ws2812_effects_mode_color_wipe() { + + ws2812_buffer * buffer = state->buffer; + + int led_index = (state->counter_mode_step % buffer->size) * buffer->colorsPerLed; + + if (state->counter_mode_step >= buffer->size) + { + buffer->values[led_index] = 0; + buffer->values[led_index + 1] = 0; + buffer->values[led_index + 2] = 0; + } + else + { + uint8_t px_r = state->color[1] * state->brightness / 255; + uint8_t px_g = state->color[0] * state->brightness / 255; + uint8_t px_b = state->color[2] * state->brightness / 255; + buffer->values[led_index] = px_g; + 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); +} + +static int ws2812_effects_mode_random_dot(uint8_t dots) { + + ws2812_buffer * buffer = state->buffer; + + // fade out + for(int i=0; i < buffer->size * buffer->colorsPerLed; i++) { + buffer->values[i] = buffer->values[i] >> 1; + } + + for(int i=0; i < dots; i++) { + // pick random pixel + int led_index = rand() % buffer->size; + + uint32_t color = (state->color[0] << 16) | (state->color[1] << 8) | state->color[2]; + if (buffer->colorsPerLed == 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); +} + + + +static uint32_t ws2812_effects_mode_delay() +{ + // check if delay has been set explicitly + if (state->speed == 0 && state->mode_delay > 0) + { + return state->mode_delay; + } + + uint32_t delay = 10; + switch (state->effect_type) { + case WS2812_EFFECT_BLINK: + case WS2812_EFFECT_RAINBOW: + case WS2812_EFFECT_RAINBOW_CYCLE: + delay = 10 + ((1000 * (uint32_t)(SPEED_MAX - state->speed)) / SPEED_MAX); + break; + case WS2812_EFFECT_FLICKER: + case WS2812_EFFECT_FIRE_FLICKER: + case WS2812_EFFECT_FIRE_FLICKER_SOFT: + case WS2812_EFFECT_FIRE_FLICKER_INTENSE: + delay = 30 + (rand() % 100) + (200 * (SPEED_MAX - state->speed) / SPEED_MAX); + break; + + case WS2812_EFFECT_RANDOM_COLOR: + case WS2812_EFFECT_HALLOWEEN: + case WS2812_EFFECT_CIRCUS_COMBUSTUS: + case WS2812_EFFECT_LARSON_SCANNER: + case WS2812_EFFECT_CYCLE: + case WS2812_EFFECT_COLOR_WIPE: + case WS2812_EFFECT_RANDOM_DOT: + delay = 10 + ((1000 * (uint32_t)(SPEED_MAX - state->speed)) / SPEED_MAX); + break; + + } + return delay; +} + + +/** +* run loop for the effects. +*/ +static void ws2812_effects_loop(void *p) +{ + + if (state->effect_type == WS2812_EFFECT_BLINK) + { + ws2812_effects_mode_blink(); + } + else if (state->effect_type == WS2812_EFFECT_RAINBOW) + { + ws2812_effects_mode_rainbow(); + } + else if (state->effect_type == WS2812_EFFECT_RAINBOW_CYCLE) + { + // the rainbow cycle effect can be achieved by shifting the buffer + ws2812_buffer_shift(state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + } + else if (state->effect_type == WS2812_EFFECT_FLICKER) + { + int flicker_value = state->effect_int_param1 != EFFECT_PARAM_INVALID ? state->effect_int_param1 : 100; + if (flicker_value == 0) { + flicker_value = 50; + } + ws2812_effects_mode_flicker_int(flicker_value); + state->counter_mode_step = (state->counter_mode_step + 1) % 256; + } + else if (state->effect_type == WS2812_EFFECT_FIRE_FLICKER) + { + ws2812_effects_mode_flicker_int(110); + state->counter_mode_step = (state->counter_mode_step + 1) % 256; + } + else if (state->effect_type == WS2812_EFFECT_FIRE_FLICKER_SOFT) + { + ws2812_effects_mode_flicker_int(70); + state->counter_mode_step = (state->counter_mode_step + 1) % 256; + } + else if (state->effect_type == WS2812_EFFECT_FIRE_FLICKER_INTENSE) + { + ws2812_effects_mode_flicker_int(170); + state->counter_mode_step = (state->counter_mode_step + 1) % 256; + } + else if (state->effect_type == WS2812_EFFECT_RANDOM_COLOR) + { + ws2812_effects_mode_random_color(); + } + else if (state->effect_type == WS2812_EFFECT_HALLOWEEN) + { + ws2812_buffer_shift(state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + } + else if (state->effect_type == WS2812_EFFECT_CIRCUS_COMBUSTUS) + { + ws2812_buffer_shift(state->buffer, 1, SHIFT_CIRCULAR, 1, -1); + } + else if (state->effect_type == WS2812_EFFECT_LARSON_SCANNER) + { + ws2812_effects_mode_larson_scanner(); + } + else if (state->effect_type == WS2812_EFFECT_CYCLE) + { + ws2812_buffer_shift(state->buffer, state->effect_int_param1, SHIFT_CIRCULAR, 1, -1); + } + else if (state->effect_type == WS2812_EFFECT_COLOR_WIPE) + { + ws2812_effects_mode_color_wipe(); + } + else if (state->effect_type == WS2812_EFFECT_RANDOM_DOT) + { + uint8_t dots = state->effect_int_param1 != EFFECT_PARAM_INVALID ? state->effect_int_param1 : 1; + ws2812_effects_mode_random_dot(dots); + } + + // set the new delay for this effect + state->mode_delay = ws2812_effects_mode_delay(); + // call count + state->counter_mode_call = (state->counter_mode_call + 1) % UINT32_MAX; + // write the buffer + ws2812_write(state->buffer); + // set the timer + if (state->running == 1 && state->mode_delay >= 10) + { + os_timer_disarm(&(state->os_t)); + os_timer_arm(&(state->os_t), state->mode_delay, FALSE); + } +} + + +/** +* Set the active effect mode +*/ +static int ws2812_effects_set_mode(lua_State* L) { + + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + + // opts must be same order as effect type enum + static const char * const opts[] = {"static", "blink", "gradient", "gradient_rgb", "random_color", "rainbow", + "rainbow_cycle", "flicker", "fire", "fire_soft", "fire_intense", "halloween", "circus_combustus", + "larson_scanner", "cycle", "color_wipe", "random_dot", NULL}; + + int type = luaL_checkoption(L, 1, NULL, opts); + + state->effect_type = type; + int effect_param = EFFECT_PARAM_INVALID; + // check additional int parameter + // First mandatory parameter + int arg_type = lua_type(L, 2); + if (arg_type == LUA_TNONE || arg_type == LUA_TNIL) + { + // we don't have a second parameter + } + else if(arg_type == LUA_TNUMBER) + { + effect_param = luaL_optinteger( L, 2, EFFECT_PARAM_INVALID ); + } + + // initialize the effect + state->counter_mode_step = 0; + + switch (state->effect_type) { + case WS2812_EFFECT_STATIC: + // fill with currently set color + ws2812_effects_fill_color(); + state->mode_delay = 250; + break; + case WS2812_EFFECT_BLINK: + ws2812_effects_mode_blink(); + break; + case WS2812_EFFECT_GRADIENT: + if(arg_type == LUA_TSTRING) + { + size_t length1; + const char *buffer1 = lua_tolstring(L, 2, &length1); + + if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 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); + } + else + { + luaL_argerror(L, 2, "string expected"); + } + + break; + case WS2812_EFFECT_GRADIENT_RGB: + if(arg_type == LUA_TSTRING) + { + size_t length1; + const char *buffer1 = lua_tolstring(L, 2, &length1); + + if ((length1 / state->buffer->colorsPerLed < 2) || (length1 % state->buffer->colorsPerLed != 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); + } + else + { + luaL_argerror(L, 2, "string expected"); + } + + break; + case WS2812_EFFECT_RANDOM_COLOR: + ws2812_effects_mode_random_color(); + break; + case WS2812_EFFECT_RAINBOW: + ws2812_effects_mode_rainbow(); + break; + case WS2812_EFFECT_RAINBOW_CYCLE: + ws2812_effects_mode_rainbow_cycle(effect_param != EFFECT_PARAM_INVALID ? effect_param : 1); + break; + // flicker + case WS2812_EFFECT_FLICKER: + state->effect_int_param1 = effect_param; + break; + case WS2812_EFFECT_FIRE_FLICKER: + case WS2812_EFFECT_FIRE_FLICKER_SOFT: + case WS2812_EFFECT_FIRE_FLICKER_INTENSE: + { + state->color[0] = 255-40; + state->color[1] = 255; + state->color[2] = 40; + state->color[3] = 0; + } + break; + case WS2812_EFFECT_HALLOWEEN: + ws2812_effects_mode_halloween(); + break; + case WS2812_EFFECT_CIRCUS_COMBUSTUS: + ws2812_effects_mode_circus_combustus(); + break; + case WS2812_EFFECT_LARSON_SCANNER: + ws2812_effects_mode_larson_scanner(); + break; + case WS2812_EFFECT_CYCLE: + if (effect_param != EFFECT_PARAM_INVALID) { + state->effect_int_param1 = effect_param; + } + break; + case WS2812_EFFECT_COLOR_WIPE: + { + uint32_t black = 0; + ws2812_effects_fill_buffer(black); + ws2812_effects_mode_color_wipe(); + break; + } + case WS2812_EFFECT_RANDOM_DOT: + { + // check if more than 1 dot shall be set + state->effect_int_param1 = effect_param; + uint32_t black = 0; + ws2812_effects_fill_buffer(black); + break; + } + } + +} + + + +/* +* Start the effect execution +*/ +static int ws2812_effects_start(lua_State* L) { + + //NODE_DBG("pin:%d, level:%d \n", pin, level); + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + if (state != NULL) { + os_timer_disarm(&(state->os_t)); + state->running = 1; + state->counter_mode_call = 0; + state->counter_mode_step = 0; + // set the timer + os_timer_setfn(&(state->os_t), ws2812_effects_loop, NULL); + os_timer_arm(&(state->os_t), state->mode_delay, FALSE); + } + return 0; +} + +/* +* Stop the effect execution +*/ +static int ws2812_effects_stop(lua_State* L) { + luaL_argcheck(L, state != NULL, 1, LIBRARY_NOT_INITIALIZED_ERROR_MSG); + if (state != NULL) { + os_timer_disarm(&(state->os_t)); + state->running = 0; + } + return 0; +} + +static int ws2812_effects_tostring(lua_State* L) { + + luaL_Buffer result; + luaL_buffinit(L, &result); + + luaL_addchar(&result, '['); + luaL_addstring(&result, "effects"); + luaL_addchar(&result, ']'); + luaL_pushresult(&result); + + return 1; +} + +static const LUA_REG_TYPE ws2812_effects_map[] = +{ + { LSTRKEY( "init" ), LFUNCVAL( ws2812_effects_init )}, + { LSTRKEY( "set_brightness" ), LFUNCVAL( ws2812_effects_set_brightness )}, + { LSTRKEY( "set_color" ), LFUNCVAL( ws2812_effects_set_color )}, + { LSTRKEY( "set_speed" ), LFUNCVAL( ws2812_effects_set_speed )}, + { LSTRKEY( "set_delay" ), LFUNCVAL( ws2812_effects_set_delay )}, + { LSTRKEY( "set_mode" ), LFUNCVAL( ws2812_effects_set_mode )}, + { LSTRKEY( "start" ), LFUNCVAL( ws2812_effects_start )}, + { LSTRKEY( "stop" ), LFUNCVAL( ws2812_effects_stop )}, + { LSTRKEY( "get_delay" ), LFUNCVAL( ws2812_effects_get_delay )}, + { LSTRKEY( "get_speed" ), LFUNCVAL( ws2812_effects_get_speed )}, + + { LSTRKEY( "__index" ), LROVAL( ws2812_effects_map )}, + { LSTRKEY( "__tostring" ), LFUNCVAL( ws2812_effects_tostring )}, + { LNILKEY, LNILVAL} +}; + + +NODEMCU_MODULE(WS2812_EFFECTS, "ws2812_effects", ws2812_effects_map, NULL); diff --git a/docs/en/modules/color-utils.md b/docs/en/modules/color-utils.md new file mode 100644 index 00000000..0cb01707 --- /dev/null +++ b/docs/en/modules/color-utils.md @@ -0,0 +1,61 @@ +# COLOR_UTILS Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-12-30 | [Konrad Huebner](https://github.com/skycoders) | [Konrad Huebner](https://github.com/skycoders) | [color_utils.c](../../../app/modules/color_utils.c)| + +The color_utils module provides some basic color transformations useful for color LEDs. It is used by the WS2812_EFFECTS module. + + +## color_utils.hsv2grb() +Convert HSV color to GRB color. + +#### Syntax +`color_utils.hsv2grb(hue, saturation, value)` + +#### Parameters +- `hue` is the hue value, between 0 and 360. +- `saturation` is the saturation value, between 0 and 255. +- `value` is the value value, between 0 and 255. + +#### Returns +`green`, `red`, `blue` as values between 0 and 255 + +## color\_utils.hsv2grbw() +Convert HSV color to GRB color and explicitly return a white value. This can be useful for RGB+W LED strips. The white value is simply calculated as min(g, r, b) and then removed from the colors. This does NOT take into account if the white chip used later creates an appropriate color. + +#### Syntax +`color_utils.hsv2grbw(hue, saturation, value)` + +#### Parameters +- `hue` is the hue value, between 0 and 360. +- `saturation` is the saturation value, between 0 and 255. +- `value` is the value value, between 0 and 255. + +#### Returns +`green`, `red`, `blue`, `white` as values between 0 and 255 + +## color\_utils.grb2hsv() +Convert GRB color to HSV color. + +#### Syntax +`color_utils.grb2hsv(green, red, blue)` + +#### Parameters +- `green` is the green value, between 0 and 255. +- `red` is the red value, between 0 and 255. +- `blue` is the blue value, between 0 and 255. + +#### Returns +`hue`, `saturation`, `value` as values between 0 and 360, respective 0 and 255 + +## color\_utils.colorWheel() +The color wheel function makes use of the HSV color space and calculates colors based on the color circle. The colors are created with full saturation and value. This function is a convenience function of the hsv2grb function and can be used to create rainbow colors. + +#### Syntax +`color_utils.colorWheel(angle)` + +#### Parameters +- `angle` is the angle on the color circle, between 0 and 359 + +#### Returns +`green`, `red`, `blue` as values between 0 and 255. diff --git a/docs/en/modules/ws2812-effects.md b/docs/en/modules/ws2812-effects.md new file mode 100644 index 00000000..10984647 --- /dev/null +++ b/docs/en/modules/ws2812-effects.md @@ -0,0 +1,179 @@ +# WS2812_EFFECTS Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-11-01 | [Konrad Huebner](https://github.com/skycoders) | [ws2812_effects.c](../../../app/modules/ws2812_effects.c)| + +The ws2812_effects module provides effects based on the ws2812 library. Some effects are inspired by / based on the [WS2812FX Library](https://github.com/kitesurfer1404/WS2812FX) but have been adopted to the specifics of the ws2812 library. The effects library works based on a buffer created through the ws2812 library and performs the operations on this buffer. + +ATTENTION: Dual mode is currently not supported for effects. + +#### Example usage +```lua +-- init the ws2812 module +ws2812.init(ws2812.MODE_SINGLE) +-- create a buffer, 60 LEDs with 3 color bytes +strip_buffer = ws2812.newBuffer(60, 3) +-- init the effects module, set color to red and start blinking +ws2812_effects.init(strip_buffer) +ws2812_effects.set_speed(100) +ws2812_effects.set_brightness(50) +ws2812_effects.set_color(0,255,0) +ws2812_effects.set_mode("blink") +ws2812_effects.start() +``` + +## ws2812_effects.init() +Initialize the effects library with the provided buffer for the connected LED strip. + +#### Syntax +`ws2812_effects.init(buffer)` + +#### Parameters +- `buffer` is a `ws2812.buffer` for the connected strip. + +#### Returns +`nil` + +## ws2812_effects.start() +Start the animation effect. + +#### Syntax +`ws2812_effects.start()` + +#### Parameters +`none` + +#### Returns +`nil` + +## ws2812_effects.stop() +Stop the animation effect. + +#### Syntax +`ws2812_effects.stop()` + +#### Parameters +`none` + +#### Returns +`nil` + + +## ws2812_effects.set_brightness() +Set the brightness + +#### Syntax +`ws2812_effects.set_brightness(brightness)` + +#### Parameters +- `brightness` brightness between 0 and 255 + +#### Returns +`nil` + +## ws2812_effects.set_color() +Set the color + +#### Syntax +`ws2812_effects.set_color(g, r, b, [w])` + +#### Parameters +- `g` is the green value between 0 and 255 +- `r` is the red value between 0 and 255 +- `b` is the blue value between 0 and 255 +- `w` (optional) is the white value between 0 and 255 + +#### Returns +`nil` + +## ws2812_effects.set_speed() +Set the speed + +#### Syntax +`ws2812_effects.set_speed(speed)` + +#### Parameters +- `speed` speed between 0 and 255 + +#### Returns +`nil` + + +## ws2812_effects.get_speed() +Get current speed + +#### Syntax +`ws2812_effects.get_speed()` + +#### Parameters +`none` + +#### Returns +`speed` between 0 and 255 + + +## ws2812_effects.set_delay() +Set the delay between two effect steps in milliseconds. + +#### Syntax +`ws2812_effects.set_delay(delay)` + +#### Parameters +- `delay` is the delay in milliseconds, minimum 10ms + +#### Returns +`nil` + +## ws2812_effects.get_delay() +Get current delay + +#### Syntax +`ws2812_effects.get_delay()` + +#### Parameters +`none` + +#### Returns +`delay` is the current effect delay in milliseconds + + +## ws2812_effects.set_mode() +Set the active effect mode. + +#### Syntax +`ws2812_effects.set_mode(mode, [effect_param])` + +#### Parameters +- `mode` is the effect mode as a string, can be one of + - `static` fills the buffer with the color set through `ws2812_effects.set_color()` + - `blink` fills the buffer with the color set through `ws2812_effects.set_color()` and starts blinking + - `gradient` fills the buffer with a gradient defined by the color values provided with the `effect_param`. This parameter must be a string containing the color values with same pixel size as the current buffer configuration. Minimum two colors must be provided. If more are provided, the strip is split in equal parts and the colors are used as intermediate colors. The gradient is calculated based on HSV color space, so no greyscale colors are supported as those cannot be converted to HSV. + - `gradient_rgb` similar to `gradient` but uses simple RGB value interpolation instead of conversions to the HSV color space. + - `random_color` fills the buffer completely with a random color and changes this color constantly + - `rainbow` animates through the full color spectrum, with the entire strip having the same color + - `rainbow_cycle` fills the buffer with a rainbow gradient. The optional second parameter states the number of repetitions (integer). + - `flicker` fills the buffer with the color set through `ws2812_effects.set_color()` and begins random flickering of pixels with a maximum flicker amount defined by the second parameter (integer, e.g. 50 to flicker with 50/255 of the color) + - `fire` is a fire flickering effect + - `fire_soft` is a soft fire flickering effect + - `fire_intense` is an intense fire flickering effect + - `halloween` fills the strip with purple and orange pixels and circles them + - `circus_combustus` fills the strip with red/white/black pixels and circles them + - `larson_scanner` is the K.I.T.T. scanner effect, based on the color set through `ws2812_effects.set_color()` + - `color_wipe` fills the strip pixel by pixel with the color set through `ws2812_effects.set_color()` and then starts turning pixels off again from beginning to end. + - `random_dot` sets random dots to the color set through `ws2812_effects.set_color()` and fades them out again + - `cycle` takes the buffer as-is and cycles it. With the second parameter it can be defined how many pixels the shift will be. Negative values shift to opposite direction. +- `effect_param` is an optional effect parameter. See the effect modes for further explanations. It can be an integer value or a string. + +#### Returns +`nil` + +#### Examples +Full initialization code for the strip, a buffer and the effect library can be found at top of this documentation. Only effect examples are shown here. +```lua +-- rainbow cycle with two repetitions +ws2812_effects.set_mode("rainbow_cycle", 2) +-- gradient from red to yellow to red +ws2812_effects.set_mode("gradient", string.char(0,200,0,200,200,0,0,200,0)) +-- random dots with fading +ws2812_effects.set_mode("random_dot",3) +``` diff --git a/mkdocs.yml b/mkdocs.yml index adb8f82e..b9c70f28 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -46,6 +46,7 @@ pages: - 'bmp085': 'en/modules/bmp085.md' - 'cjson': 'en/modules/cjson.md' - 'coap': 'en/modules/coap.md' + - 'color-utils': 'en/modules/color-utils.md' - 'cron': 'en/modules/cron.md' - 'crypto': 'en/modules/crypto.md' - 'dht': 'en/modules/dht.md' @@ -99,6 +100,7 @@ pages: - 'wps': 'en/modules/wps.md' - 'ws2801': 'en/modules/ws2801.md' - 'ws2812': 'en/modules/ws2812.md' + - 'ws2812-effects': 'en/modules/ws2812-effects.md' - 'xpt2046': 'en/modules/xpt2046.md' #- Deutsch: # - Home: 'de/index.md'