Addressable LEDs in Berry~
Requires #define USE_WS2812
, included in Tasmota32
Support for addressable leds strips or matrix, including animation. Internally relies on NeoPixelBus library and currently supports WS2812 and SK6812.
How to use~
Compatibility with Templates~
You can control multiple LED strips. WS2812 - 1
is also controlled by Tasmota's light controls. It is still possible to control this light strip with Berry, but whenever you use Tasmota light controls they will temporarily overrid Berry animations.
To avoid any conflict between native WS2812 and Berry control, you can use Scheme 14
which disables native WS2812.
Led strips, matrix and sub-strips~
You first need to define the low-level Leds
object that describes the hardware strip of connected leds.
You can then define higher level objects like sub-strips (if there are actually several strips chained together like rings) or LED matrix.
Once a Leds
object, you can use sub-objects:
LED model | Details |
---|---|
Leds.WS2812_GRB | WS2812b Leds (GRB) - takes 24 bits RGB colors |
Leds.SK6812_GRBW | SK6812 Leds (GRBW) - takes 32 bits RGBW colors (with white channel) |
Methods are the equivalent low-level from NeoPixelBus. All colors are in 0xRRGGBB
format (24 bits) or 0xWWRRGGBB
format (32 bits).
animation framework - module animate
~
An offline emulator is available to test animation on a computer instead of an embedded device and generate animated images to show the final result
Based on the project above there as an online emulator with a minimal Tasmota environment running real Berry in a web browser, which will show animations in real time. Just copy and paste the code.
The module animate
provides a simple framework to build customizable animations. It is optimized for 1D animations on Led strips.
Note: import animate
is only available if Tasmota is compiled with #define USE_WS2812
, which is the case of most precompiled binaries.
The core class is animate.core
. You first need to create a Leds
object to describe the Led strip object and length.
import animate
var strip = Leds(25, gpio.pin(gpio.WS2812, 0))
var anim = animate.core(strip)
At each tick (50 times per second) the core
classes first executes the animators
. Each animator
can change a velue depending on the timestamp and internal parameters, and publishes the new values to a 'listener'. For example, a "palette animator" iterates through colors, and publishes color values to an object like a background or a dot.
The concept of animator
is inspired from audio modular synthesizers. An animator
is like a stand-alone oscillator and a waveform (square, triangle...) that feeds directly other components in cascade.
Once all animators
are called, core
then runs each layered painter
object. A painter
draws a layer into a Leds_frame
object (like a frame buffer). The frame supports transparency alpha channel in ARGB mode (see below). Each layer is flattened onto the background layer like a layered cake. Once all layers are rendered and flattened, the final frame buffer is availale.
Finally the frame buffer is copied to the physical WS2812 led strip, after applying brightness bri
and applying gamma correction (if required).
animate.core
class~
This is the main helper class to host all the animation components. It is composed of:
strip
object representing the led strip (1-dimension, only RGB supported for now)bri
parameter (0..255) to control the overall brightnessframe
the background frame buffer, instance ofanimate.frame
layer
the current frame buffer being painted by apainter
, instance ofanimate.frame
. It is merged toframe
once painted, taking into account transparency (alpha channel)
The instance also does the following:
- register a
fast_loop
for quick animation, and iterate every 20ms (50Hz) - call each
animator
object to compute new values of all parameters - call each
painter
object to paint layers on top of each others - apply brightness to frame buffer
- copy to
strip
WS2812 leds
Methods:
init(strip [, bri:int])
constructor, needs a strip, brightness defaults to 50%set_bri()
andget_bri()
to set/get brightnessadd_animator()
adds an animator object to be called at each tickadd_painter()
adds a painter objectstart()
andstop()
, by default the animation is stopped. It needs to be started explcitlyclear()
clear all leds and stop animationset_cb()
sets the callback at each tick to compute the animation. All animators have been processed before this call.set_cb(instance, method)
remove()
stop the animation and removes the object fromfast_loop
;clear()
is called internally
animate.frame
class~
This class is a helper class to manage RGB pixels frame, mix layers and compute the final image. All frames are computed in ARGB (alpha + RGB) at full brightness and with no gamma (full linear). It's only at the last moment that brightness and gamma correction are applied.
Leds_frame
is a super-class of bytes
and encapsulate a raw bytes buffer. Each pixel is in ARGB 32 bits format with alpha-channel.
Methods:
- constructor
Leds_frame(number_of_pixels:int)
: creates a frame buffer with the specified number of pixels (the actual bytes buffer is x4 this size). The buffer is filled with black opaque by default frame[i]
: read/write the 32-bit value of the ARGB pixel at indexi
frame.set_pixel(i, r, g, b, alpha)
: set the pixel at indexi
for valuer
/g
/b
(0..255) and optionalalpha
channel (opaque 0x00 if not specified)frame.fill_pixels(argb)
: fill the frame withargb
32-bit valueframe.blend_pixels(background, foreground)
: blends a background frame (considered opaque) with a front layer with alpha, and stores in the current object. It is common that the target and the background are the same objects, henceframe.blend_pixels(frame, fore)
frame.paste_pixels(strip_raw_bytes, bri:0..255, gamma:bool)
: pastes theLed_buffer
object into a Leds strip. This is the final step before displaying the frame to the actual leds, and applybri
andgamma
correction.
pre-built animators~
Currently the following animators are provided:
animate.oscillator
: generate a variable integer that can be used by painters as a cyclic value (brightness, size, speed...)animate.palette
: cycle through a color palette with smooth transitions
animate.oscillator
~
Methods | Description |
---|---|
set_duration_ms | set_duration_ms(int) -> nil sets the duration of the animation (in ms) |
set_cb | set_cb(object, method) -> nil sets the callback object and method to update after a new value is computed |
set_a set_b | set_a(int) -> nil or set_b(int) sets the start and end value |
set_form | set_form(int) -> nil sets the waveform among the following valuesanimate.SAWTOOTH : ramp from a to b and start overanimate.TRIANGLE : move back and forth from a to b animate.SQUARE : alternate values a and b animate.COSINE : move from a to b in a cosine waveanimate.SINE : move from half-way between a and b and move in SINE wave |
set_phase | set_phase(phase:0..100) -> nil set the phase between 0% and 100%, defaults to 0 % |
set_duty_cycle | set_duty_cycle(int:0..100) -> int sets the duty cycle between a and b values, defaults to 50 % |
animate.palette
~
Methods | Description |
---|---|
init | init(palette: bytes() or comptr [, duration_ms:int]) initialize the palette animator with a palette object, see below |
set_duration_ms | set_duration_ms(int) -> nil sets the duration of the animation (in ms) |
set_cb | set_cb(object, method) -> nil sets the callback object and method to update after a new value is computed |
set_bri | set_bri(bri:0..255) -> nil sets the brightness for the color, defaults to 255 |
palettes solidified in Flash
Palette | Description |
---|---|
animate.PALETTE_RAINBOW_WHITE | Cycle through 8 colors (including white) and keep colors steady |
animate.PALETTE_STANDARD_TAG | Standard palette cycling through 7 colors |
animate.PALETTE_STANDARD_VAL | Cycle through 6 colors as values |
animate.PALETTE_SATURATED_TAG | Cycle through 6 saturated colors |
animate.PALETTE_ib_jul01_gp | |
animate.PALETTE_ib_44 | |
animate.PALETTE_Fire_1 | |
animate.PALETTE_bhw1_sunconure | |
animate.PALETTE_bhw4_089 |
Palettes can be specified as a bytes()
object of via comptr
if they are solidified in Flash.
Palettes can follow to different formats:
1. Palette in time units
Bytes: <transition_time>|<RR><GG><BB>
(4 bytes per entry)
Each entry specifies the time in units to go from the current value to the next value. The last entry must have a <transition_time>
of 0x00
. The unit is abstract, and only ratio between value are meaningful - the actual duration is derived from duration_ms
where all indivudal <transition_time>
are stretched to cover the desired duration.
This format makes it easier to adjust the transition time between colors
Example:
var PALETTE_TAG = bytes(
"40" "FF0000" # red
"40" "FFA500" # orange
"40" "FFFF00" # yellow
"40" "00FF00" # green
"40" "0000FF" # blue
"40" "FF00FF" # indigo
"40" "EE44A5" # violet
"00" "FF0000" # red
)
2. Palette in values
Bytes: <value>|<RR><GG><BB>
(4 bytes per entry)
Each entry indicates what is the target color for a specific value. Values go from 0x00
to 0xFF
(0..255). The first entry must start with 0x00
and the last must use value 0xFF
.
This format is useful to use palettes that represent a color range.
Example:
var PALETTE_VAL = bytes(
"00" "FF0000" # red
"24" "FFA500" # orange
"49" "FFFF00" # yellow
"6E" "008800" # green
"92" "0000FF" # blue
"B7" "4B0082" # indigo
"DB" "EE82EE" # violet
"FF" "FF0000" # red
)
Note: you can generate a CSS linear-gradient of a palette with the following code:
import animate
print(animate.palette.to_css_gradient(animate.PALETTE_STANDARD_TAG))
# background:linear-gradient(to right,#FF0000 0.0%,#FFA500 14.3%,#FFFF00 28.6%,#00FF00 42.9%,#0000FF 57.1%,#FF00FF 71.4%,#FFFFFF 85.7%,#FF0000 100.0%);
Advanced features~
Hardware RMT
channels~
This library uses NeoPixelBus library, and RMT
hardware support in ESP32. The number of RMT
channels, hence the number of simultaneous strips, depends on the CPU type. Tasmota native support for WS2812 uses RMT
channel 0; it is not usable in such case.
CPU type | RMT channels |
---|---|
ESP32 | 8 |
ESP32S2 | 4 |
ESP32C3 | 2 |
Currently RMT
channel 0 is used by default if no GPIO WS2812-1
is configured, RMT
channel 1 otherwise.