Addressable LEDs in Berry~
Requires #define USE_WS2812, included in Tasmota32
Support for addressable leds strips and animation. Internally relies on optimized TasmotaLED library, currently supporting WS2812 and SK6812, 3 and 4 channels, over RMT and SPI.
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, 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).
Once a Leds object, you can use sub-objects:
| Method | Details |
|---|---|
| create_segment | <strip>.create_segment(offset:int, pixels:int) -> instance<Leds_segment>Creates a virtual segment from a physical Leds strip, from Led number offset with pixels leds. |
The Leds_segment class provides the same interface as the Leds class, with the following differences: - It doesn't have its own buffer, it's a view into the parent strip's buffer - The show() method takes an optional force parameter that, when true, forces a show even if the segment doesn't cover the entire strip - The pixels_buffer() method returns nil since segments don't have their own buffer
| 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:
stripobject representing the led strip (1-dimension, only RGB supported for now)briparameter (0..255) to control the overall brightnessframethe background frame buffer, instance ofanimate.framelayerthe current frame buffer being painted by apainter, instance ofanimate.frame. It is merged toframeonce painted, taking into account transparency (alpha channel)
The instance also does the following:
- register a
fast_loopfor quick animation, and iterate every 20ms (50Hz) - call each
animatorobject to compute new values of all parameters - call each
painterobject to paint layers on top of each others - apply brightness to frame buffer
- copy to
stripWS2812 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 indexiframe.set_pixel(i, r, g, b, alpha): set the pixel at indexifor valuer/g/b(0..255) and optionalalphachannel (opaque 0x00 if not specified)frame.fill_pixels(argb): fill the frame withargb32-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_bufferobject into a Leds strip. This is the final step before displaying the frame to the actual leds, and applybriandgammacorrection.
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 banimate.SQUARE: alternate values a and banimate.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.
pixmat class for 2D pixel buffers~
The pixmat class provides a native high-performance 2D pixel buffer abstraction for Berry.
It supports mono (1‑bpp), RGB (3‑bpp), and RGBW (4‑bpp) formats, with optional serpentine layout.
Constructor~
| Overload | Description |
|---|---|
pixmat(bitplane_bytes:bytes, bytes_per_line:int) | Creates a 1‑bpp mono matrix from packed bitplane data. Each bit becomes a pixel (0 or 255). |
pixmat(buf:bytes, width:int, height:int, bpp:int[, serpentine:bool]) | Wraps an existing pixel buffer. No copy is made. bpp is bytes per pixel (1=mono, 3=RGB, 4=RGBA). serpentine reverses odd rows if true. |
pixmat(width:int, height:int, bpp:int[, serpentine:bool]) | Allocates a new zero‑filled buffer with given dimensions and pixel format. |
bitplane_bytes: packed bits (1‑bpp), each bit becomes a pixel (0 or 255)buf: external buffer to wrap (no copy)width,height: pixel dimensionsbpp: bytes per pixel (1=mono, 3=RGB, 4=RGBW)serpentine: if true, odd rows are reversed in memory
Methods~
| Method | Description |
|---|---|
pixmat.clear([val:int]) | Fills the entire matrix with val (default 0). For mono: luminance. For RGB/RGBA: all channels. |
pixmat.get(x:int, y:int) → int or list | Returns pixel value at (x, y). Packed int for mono/RGB/RGBA, list for other bpp. |
pixmat.set(x:int, y:int, rgb:int[, bri:int]) | Sets pixel at (x, y) using packed RGB (0xRRGGBB). Optional brightness scaling. |
pixmat.set(x:int, y:int, h:int, s:int, v:int[, bri:int]) | Sets pixel using HSV values. Converts to RGB internally. Optional brightness. |
pixmat.blit(src:pixmat, dx:int, dy:int[, bri:int][, tint:int]) | Copies pixels from src matrix with optional brightness and RGB tint. Supports mono→color expansion. |
pixmat.scroll(dir:int[, src:pixmat]) | Scrolls matrix content by one pixel. dir: 0=up, 1=left, 2=down, 3=right. Optional src fills vacated row/column. |
clear(val): fills matrix with value (default 0)get(x,y): returns pixel value (packed int or list)set(x,y,rgb): sets pixel with packed RGBset(x,y,h,s,v): sets pixel with HSV (converted internally)blit(src, dx, dy): copies pixels from another matrix, for mono source 0 becomes transparentscroll(dir): scrolls content by one pixel (0=up, 1=left, 2=down, 3=right)
Notes~
- All operations are in-place and use integer math
- Brightness and tinting are supported in
set()andblit() - Mono→color expansion is automatic when blitting
- Ideal for use with
Leds.pixels_buffer()to drive 2D LED panels
Example~
var strip = Leds(256, gpio.pin(gpio.WS2812, 32))
var m = pixmat(strip.pixels_buffer(), 32, 8, strip.pixel_size(), true)
m.set(0, 0, 0xFF0000) # top-left pixel red
strip.show()
A few more examples can be found here.