Skip to content

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.

Class Details
Leds Leds(pixels:int, gpio:int [,model:int ,rmt:int]) -> instance<Leds>
Creates a Leds instance for a linear leds strip
pixels: number of leds
gpio: physical gpio number
model: (optional) LED model, default: Leds.WS2812_GRB, alternative Leds.SK6812_GRBW
rmt: (optional) RMTchannel to use, or auto-select (see below)

Once a Leds object, you can use sub-objects:

Method Details
create_matrix <strip>.create_matrix(width:int, height:int [, offset:int]) -> instance<Leds_matrix>
Creates a Leds_matrix instance from a Leds instance
width: number of leds horizontally
height: number of leds vertically
offset: number of leds to skip until start of matrix
You can use set_alternate(true) to enabled alternate lines (i.e. zigzag mode).
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.
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).

Attributes Details
clear clear() -> nil
Clear all led (set to black)
clear_to clear_to(col:color [, bri:int]) -> nil
Set all leds to the specified color. bri (0..100) is optional and default to 100%
show show() -> nil
Pushes the internal buffer to leds. May be ignored if a show command is already in progress. Use can_show() to see if show() is possible
can_show can_show() -> bool
Indicates if show() is possible, i.e. no transfer is ongoing
is_dirty is_dirty() -> bool
Indicates if a led was changed since last show()
dirty dirty() -> nil
Forces a refresh during next show()
pixel_size pixel_size() -> int
Returns the number of bytes per pixel
pixel_count pixel_count() -> int
Returns the number of leds in the strip/matrix
clear_to clear_to(col:color [, bri:int]) -> nil
Clears all leds to the specified color. bri is optional and default to 100%
set_pixel_color set_pixel_color(idx:int, col:color [, bri:int]) -> nil
Set led number idx to the specified color. bri (0..100) is optional and default to 100%
set_matrix_pixel_color set_matrix_pixel_color(x:int, y:int, col:color [, bri:int]) -> nil
(only Leds_matrix) Set led number of coordinates x/y to the specified color. bri is optional and default to 100%
set_alternate set_alternate(bool) -> nil
(only Leds_matrix) Sets the matrix as alternate cabling (i.e. zigzag mode) instead of regular mode.
It is common for large led matrix to have every other line in reverse order.
get_alternate get_alternate() -> bool
(only Leds_matrix) Read the value set with set_alternate(bool).
get_pixel_color get_pixel_color(idx:int) -> color:int
Returns the color (including brightness and gamma correction) of led number idx
gamma gamma:bool
Applies gamma correction if true (default)
pixels_buffer pixels_buffer() -> bytes()
Returns the internal buffer used by NeoPixelBus. The byte() object points to the original buffer, no new buffer is allocated; which means that raw data can be changed directly. Don't forget to call dirty() and show() afterwards
set_bytes set_bytes(row:int, buffer:bytes, offset:int, len:int) -> nil (matrix only)
Copy a bytes() buffer directly in the internal matrix buffer, for row row, skipping offset pixels and copying len bytes.

Animation framework~

The class Leds_animator sets the necessary methods to facilitate animations. You just need create a sub-class or Leds_animator, provide a Leds or Leds_matrix instance and implement the animate method. You can also register animators (see below).

The instance is automatically registered as driver. Call start() to start the animation, and stop() to stop it.

Attributes Details
Leds_animator Leds_animator(strip:instance) -> instance<Leds_animator>
Constructors only needs an instance of Leds or Leds_matrix
start start() -> nil
Register the animator as Tasmota driver (with tasmota.add_driver) and start the animation
stop stop() -> nil
Stop the animation and removes the driver from Tasmota drivers list
clear clear() -> nil
Call stop() and clear all leds (set to black)
remove remove() -> nil
Removes the instance from Tasmota's list of drivers, and stops the animation
set_bri set_bri(bri:int) -> nil
Sets the brightness of the animation (0..100)
add_anim add_anim(anim:instance) -> nil
Registers an animator to be called just before the call to animate (see below)
get_bri get_bri() -> int
Returns the brightness of the animation (0..100)
animate animate() -> nil
Place-holder for the actual animation. You need to override this method

Example:

import animate
class Rainbow_stripes : Leds_animator
  var cur_offset     # current offset in the palette
  static palette = [ 0xFF0000, #- red -#
                     0xFFA500, #- orange -#
                     0xFFFF00, #- yellow -#
                     0x008800, #- green -#
                     0x0000FF, #- blue -#
                     0x4B0082, #- indigo -#
                     0xEE82EE, #- violet -#
                  ]

  # duration in seconds
  def init(strip, duration)
    super(self).init(strip)
    self.cur_offset = 0
    # add an animator to change `self.cur_offset` to each value of the palette
    self.add_anim(animate.rotate(def(v) self.cur_offset = v end, 0, size(self.palette), int(duration * 1000)))
  end

  def animate()
    var i = 0
    while i < self.pixel_count    # doing a loop rather than a `for` prevents from allocating a new object
      var col = self.palette[(self.cur_offset + i) % size(self.palette)]
      self.strip.set_pixel_color(i, col, self.bri)   # simulate the method call without GETMET
      i += 1
    end
    self.strip.show()
  end
end

How to use:

var strip = Leds(5,5, gpio.pin(gpio.WS2812, 1))
var r = Rainbow_stripes(strip, 1.0)
r.start()

And here is another example that "breathes" the LED strip with a hardcoded colour:

class Breathe : Leds_animator
  var brightness
  var colour

  # duration in seconds
  def init(strip, duration)
    super(self).init(strip)
    self.brightness = 0
    self.colour = 0xFFFFFF
    self.add_anim(animate.back_forth(def(v) self.brightness = v end, 0, 100, int(duration * 1000)))
  end

  def animate()
    var i = 0
    while i < self.pixel_count    # doing a loop rather than a `for` prevents from allocating a new object
      self.strip.set_pixel_color(i, self.colour, self.brightness)
      i += 1
    end
    self.strip.show()
  end
end

And to use this one:

var strip = Leds(5,5, gpio.pin(gpio.WS2812, 1))
var r = Breathe(strip, 2.0)
r.start()

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.