Skip to content

Instantly share code, notes, and snippets.

@Staars
Last active September 18, 2025 14:56
Show Gist options
  • Select an option

  • Save Staars/d96600eb8cd67d0dbb0adf5b4011595e to your computer and use it in GitHub Desktop.

Select an option

Save Staars/d96600eb8cd67d0dbb0adf5b4011595e to your computer and use it in GitHub Desktop.
Matrix tests
# === Setup ===
leds = Leds(8 * 32, gpio.pin(gpio.WS2812, 0))
buf = leds.pixels_buffer()
m = Matrix(buf, 32, 8, 3, true) # serpentine = true
# === Helper: show and pause ===
def show_pause(ms)
leds.show()
tasmota.delay(ms)
end
# === 1. Fill background ===
m.clear(0x101010) # dim grey background
show_pause(500)
# === 2. Draw a perfect blue diagonal ===
for i: 0..7
m.set(i, i, 0x0000FF) # blue
end
show_pause(1000)
# === 3. Moving vertical bar ===
for x: 0..31
# Clear previous bar
m.clear(0x101010)
# Redraw diagonal
for i: 0..7
m.set(i, i, 0x0000FF)
end
# Draw vertical bar in yellow
for y: 0..7
m.set(x, y, 0xFFFF00)
end
show_pause(50)
end
# === 4. Scroll test ===
# Scroll right
for s: 1..5
m.scroll(3, 1) # dir=3 (right), steps=1
show_pause(100)
end
# Scroll down
for s: 1..3
m.scroll(2, 1) # dir=2 (down), steps=1
show_pause(100)
end
# Scroll left
for s: 1..5
m.scroll(1, 1) # dir=1 (left), steps=1
show_pause(100)
end
# Scroll up
for s: 1..3
m.scroll(0, 1) # dir=0 (up), steps=1
show_pause(100)
end
# === End pattern ===
m.clear(0x000000) # clear to black
leds.show()
#########################################
#-
- LED driver for Ulanzi clock written in Berry
- Matrix rain animation using Matrix + blit()
- Uses packed integer hex colours
-#
#@ solidify:MATRIX_ANIM
class MATRIX_ANIM
var strip
var matrix
var drop
var positions
var wait
def init()
import crypto
# Create LED strip and wrap in Matrix (serpentine handled in C++)
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
# Create a 1×4 drop pattern (head bright green, tail dimmer)
# Allocate exact size: width × height × bpp
self.drop = Matrix(drop_buf, 1, 4, bpp, false)
self.drop.set(0, 0, 0x001800) # head
self.drop.set(0, 1, 0x001200) # tail 1
self.drop.set(0, 2, 0x000a00) # tail 2
self.drop.set(0, 3, 0x000500) # tail 3
# Initialise drop positions
self.wait = 0
self.positions = []
for i: 0..31
var y = (crypto.random(1)[0] % 10) - 3
if i > 0 && y == self.positions[i - 1]
y += 3
end
self.positions.push(y)
end
# Register driver
tasmota.add_driver(self)
end
def deinit()
self.strip.clear()
tasmota.remove_driver(self)
end
def every_50ms()
if self.wait == 0
self.wait = 4
return
end
self.wait -= 1
# Clear the whole matrix to black
self.matrix.clear(0x000000)
# Draw each drop
var x = 0
for y: self.positions
if y >= 9
self.positions[x] = -3
else
# Blit the 1×4 drop into the main matrix at (x, y)
self.matrix.blit(self.drop, x, y)
self.positions[x] = y + 1
end
x += 1
end
self.strip.show()
end
end
# Instantiate and return the driver
var anim = MATRIX_ANIM()
#################################################
import math
#@ solidify:ULANIM
class ULANIM
var matrix, clist, cmatrix
var strip
var animation, current_animation, delay
def init()
# Create LED strip and wrap in Matrix (serpentine handled in C++)
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
self.initCMatrix()
self.loadAnimation()
tasmota.add_driver(self)
end
def deinit()
self.strip.clear()
tasmota.remove_driver(self)
end
def every_50ms()
if self.current_animation == nil
return
end
if self.delay > 0
self.delay -= 1
return
end
self.updateCList()
self.draw()
self.nextAnimation()
end
def loadAnimation()
self.animation = [ # color, cycles, delay, mode
[0x0000ff,8,2,4],
[0x0000ff,7,2,5],
[0x00ffff,8,2,4],
[0x00ffff,7,2,5],
[0x00ff00,8,0,4],
[0x00ff00,7,1,5],
[0xffff00,8,0,4],
[0xffff00,7,1,5],
[0xff0000,8,0,4],
[0xff0000,7,1,5],
[0xffffff,8,0,4],
[0xffffff,8,1,5],
]
self.current_animation = [0,0,0,0]
self.delay = 0
self.colorFromSeed()
self.clist = self.current_animation[4]
end
def updateCList()
var mode = self.current_animation[3]
if mode == 0 || mode == 2
var last = self.clist[0]
for i:0..6
self.clist[i] = self.clist[i+1]
end
self.clist[7] = last
elif mode == 1 || mode == 3
var first = self.clist[7]
for i:range(6,0,-1)
self.clist[i+1] = self.clist[i]
end
self.clist[0] = first
elif mode == 4
var _clist = self.current_animation[4]
var c = 7 - self.current_animation[1] % 8
self.clist = [0,0,0,0,0,0,0,0]
for i:range(7,7-c,-1)
self.clist[i] = _clist[i]
end
elif mode == 5
var _clist = self.current_animation[4]
var c = self.current_animation[1] % 8
self.clist = [0,0,0,0,0,0,0,0]
for i:range(0,c)
self.clist[7-i] = _clist[i]
end
end
end
def nextAnimation()
if self.current_animation[1] > 0
self.current_animation[1] -= 1
else
if size(self.animation) == 0
self.current_animation = nil
return
end
self.current_animation = self.animation[0]
self.animation = self.animation[1..]
self.colorFromSeed()
self.clist = self.current_animation[4]
end
self.delay = self.current_animation[2]
end
def colorFromSeed()
var color = self.current_animation[0]
var divider = 8
var mode = self.current_animation[3]
if mode == 2 || mode == 3
divider = 9
end
var r = (((color >> 16) / divider) << 16) & 0xff0000
var g = ((((color >> 8) & 0xff) / divider) << 8) & 0xff00
var b = (((color & 0xff) / divider))
var step = r | g | b
var clist = [color,0,0,0,0,0,0,0]
for i:1..7
clist[i] = clist[i-1] - step
end
if mode == 2 || mode == 3
clist = clist[4..7] + clist[4..7].reverse()
end
self.current_animation.push(clist)
self.clist = clist
end
def draw()
var cl = self.clist
for i:0..255
var slot = self.cmatrix[i]
self.matrix.set(i % 32, i / 32, cl[slot])
end
self.strip.show()
end
def initCMatrix()
var offset = [0]
self.cmatrix = bytes(-256)
for y:0..7
for x:0..31
for o:offset
if x >= o && x < (32 - o)
self.cmatrix[32*y + x] = o
end
end
end
offset.push(offset[-1]+1)
end
end
end
var anim = ULANIM()
#############################
import math
#@ solidify:PULSE_RINGS
class PULSE_RINGS
var strip, matrix
var frame, bpp
def init()
# Create LED strip and wrap in Matrix (serpentine handled in C++)
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
self.bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, self.bpp, true)
self.frame = 0
tasmota.add_driver(self)
end
def deinit()
self.strip.clear()
tasmota.remove_driver(self)
end
def every_50ms()
self.draw()
self.frame += 1
if self.frame > 14
self.frame = 0
end
end
def draw()
self.matrix.clear(0x000000)
# Centre of the panel
var cx = 15.5
var cy = 3.5
# Draw rings at radii based on frame
for y:0..7
for x:0..31
var dx = x - cx
var dy = y - cy
var dist = math.sqrt(dx*dx + dy*dy)
# Pulse: ring radius grows each frame
var radius = (self.frame % 8) + 1
# If pixel is near the current radius, colour it
if math.abs(dist - radius) < 0.5
# Fade colour based on radius
var fade = 255 - (radius * 30)
if fade < 0
fade = 0
end
var col = (fade << 16) | (fade << 8) | fade # white fade
self.matrix.set(x, y, col)
end
end
end
self.strip.show()
end
end
var anim = PULSE_RINGS()
############################################
import math
#@ solidify:AURORA_DUAL_WAVES
class AURORA_DUAL_WAVES
var strip, matrix
var tick, frame_div
var fast_loop_closure
def init()
# LED strip + Matrix
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
self.tick = 0
self.frame_div = 1 # update every fast_loop tick
# Register fast loop
self.fast_loop_closure = def () self.fast_loop() end
tasmota.add_fast_loop(self.fast_loop_closure)
end
def deinit()
self.strip.clear()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
self.tick += 1
if self.tick % self.frame_div != 0
return
end
self.draw()
end
# Integer HSV (s=255). h: 0..359, v: 0..255
def hsvInt(h, v)
var s = 255
var sector = h / 60
var f = h % 60
var p = (v * (255 - s)) / 255
var sf = (s * f) / 60
var q = (v * (255 - sf)) / 255
var s60f = (s * (60 - f)) / 60
var t = (v * (255 - s60f)) / 255
var r = 0
var g = 0
var b = 0
if sector == 0
r = v
g = t
b = p
elif sector == 1
r = q
g = v
b = p
elif sector == 2
r = p
g = v
b = t
elif sector == 3
r = p
g = q
b = v
elif sector == 4
r = t
g = p
b = v
else
r = v
g = p
b = q
end
return (r << 16) | (g << 8) | b
end
def draw()
var t = self.tick
# Wave parameters
var speed1 = 2.0
var speed2 = 1.3
var hue_shift1 = 0
var hue_shift2 = 180
for y:0..7
for x:0..31
# First wave: horizontal bias
var wave1 = math.sin((x * 0.3) + (t / speed1))
# Second wave: diagonal bias
var wave2 = math.sin(((x + y) * 0.25) - (t / speed2))
# Combine waves for brightness modulation
var combined = (wave1 + wave2) / 2.0
var v = int(128 + combined * 127)
# Hue also shifts with position and time
var hue = int((x * 6 + y * 4 + t) % 360)
# Alternate hue shift for second wave influence
if wave2 > 0
hue = (hue + hue_shift2) % 360
else
hue = (hue + hue_shift1) % 360
end
var col = self.hsvInt(hue, v)
self.matrix.set(x, y, col)
end
end
self.strip.show()
end
end
var anim = AURORA_DUAL_WAVES()
#########################################
#@ solidify:MULTI_SPRITE_DEMO
class MULTI_SPRITE_DEMO
var strip, matrix
var fast_loop_closure
var tick, frame_div
# Sprite definitions: width, height, pixel data (0 = transparent)
var sprites
var actors # list of [sprite_index, x, y, vx, vy]
def init()
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
# Define sprites
var smiley = [
0, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0,
0xFFFF00, 0, 0x000000, 0, 0xFFFF00,
0xFFFF00, 0, 0x000000, 0, 0xFFFF00,
0xFFFF00, 0, 0, 0, 0xFFFF00,
0, 0xFFFF00, 0xFFFF00, 0xFFFF00, 0
]
var heart = [
0xFF0000, 0xFF0000, 0, 0xFF0000, 0xFF0000,
0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000,
0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000, 0xFF0000,
0, 0xFF0000, 0xFF0000, 0xFF0000, 0,
0, 0, 0xFF0000, 0, 0
]
var star = [
0, 0, 0xFFFFFF, 0, 0,
0, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0,
0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF,
0, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0,
0, 0, 0xFFFFFF, 0, 0
]
self.sprites = [
[5, 5, smiley],
[5, 5, heart],
[5, 5, star]
]
# Actors: [sprite_index, x, y, vx, vy]
self.actors = [
[0, 0, 0, 1, 1],
[1, 10, 2, -1, 1],
[2, 20, 5, 1, -1]
]
self.tick = 0
self.frame_div = 10 # update every 2 fast_loop ticks
# Register fast loop
self.fast_loop_closure = def () self.fast_loop() end
tasmota.add_fast_loop(self.fast_loop_closure)
end
def deinit()
self.strip.clear()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
self.tick += 1
if self.tick % self.frame_div != 0
return
end
self.update()
self.draw()
end
def update()
for a: self.actors
a[1] += a[3] # x += vx
a[2] += a[4] # y += vy
var sprite_w = self.sprites[a[0]][0]
var sprite_h = self.sprites[a[0]][1]
# Bounce horizontally
if a[1] <= 0 || a[1] + sprite_w >= 32
a[3] = -a[3]
a[1] += a[3]
end
# Bounce vertically
if a[2] <= 0 || a[2] + sprite_h >= 8
a[4] = -a[4]
a[2] += a[4]
end
end
end
def draw()
self.matrix.clear(0x000000)
for a: self.actors
self.blitSprite(a[0], a[1], a[2])
end
self.strip.show()
end
def blitSprite(index, x, y)
var sprite_w = self.sprites[index][0]
var sprite_h = self.sprites[index][1]
var data = self.sprites[index][2]
for sy:0..sprite_h-1
for sx:0..sprite_w-1
var col = data[sy * sprite_w + sx]
if col != 0
var px = x + sx
var py = y + sy
if px >= 0 && px < 32 && py >= 0 && py < 8
self.matrix.set(px, py, col)
end
end
end
end
end
end
var anim = MULTI_SPRITE_DEMO()
###############################
import math
#@ solidify:PSYCHEDELIC_SCROLL_LAYERED
class PSYCHEDELIC_SCROLL_LAYERED
var strip, matrix
var fast_loop_closure
var tick, frame_div
var offset_x, offset_y
var dir_x, dir_y
var change_dir_tick
var zoom_phase_x, zoom_phase_y
def init()
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
self.tick = 0
self.frame_div = 3 # slower updates
self.offset_x = 0.0
self.offset_y = 0.0
self.dir_x = 0.2
self.dir_y = 0.0
self.change_dir_tick = 0
self.zoom_phase_x = 0.0
self.zoom_phase_y = math.pi / 2
self.fast_loop_closure = def () self.fast_loop() end
tasmota.add_fast_loop(self.fast_loop_closure)
end
def deinit()
self.strip.clear()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
self.tick += 1
if self.tick % self.frame_div != 0
return
end
# Occasionally change scroll direction
if self.tick - self.change_dir_tick > 150
import crypto
self.dir_x = ((crypto.random(1)[0] % 3) - 1) * 0.3
self.dir_y = ((crypto.random(1)[0] % 3) - 1) * 0.3
if self.dir_x == 0 && self.dir_y == 0
self.dir_x = 0.3
end
self.change_dir_tick = self.tick
end
self.offset_x += self.dir_x
self.offset_y += self.dir_y
# Advance zoom phases
self.zoom_phase_x += 0.04
self.zoom_phase_y += 0.05
if self.zoom_phase_x > math.pi * 2
self.zoom_phase_x -= math.pi * 2
end
if self.zoom_phase_y > math.pi * 2
self.zoom_phase_y -= math.pi * 2
end
self.draw()
end
def hsvInt(h, v)
var s = 255
var sector = h / 60
var f = h % 60
var p = (v * (255 - s)) / 255
var sf = (s * f) / 60
var q = (v * (255 - sf)) / 255
var s60f = (s * (60 - f)) / 60
var t = (v * (255 - s60f)) / 255
var r = 0
var g = 0
var b = 0
if sector == 0
r = v
g = t
b = p
elif sector == 1
r = q
g = v
b = p
elif sector == 2
r = p
g = v
b = t
elif sector == 3
r = p
g = q
b = v
elif sector == 4
r = t
g = p
b = v
else
r = v
g = p
b = q
end
return (r << 16) | (g << 8) | b
end
def draw()
var t = self.tick
var zoom_x = 1.0 + 0.25 * math.sin(self.zoom_phase_x)
var zoom_y = 1.0 + 0.25 * math.sin(self.zoom_phase_y)
for y:0..7
for x:0..31
var zx = (x - 16) * zoom_x + 16
var zy = (y - 4) * zoom_y + 4
# First plasma layer
var v1a = math.sin((zx + self.offset_x) / 6.0 + t / 40.0)
var v1b = math.sin((zy + self.offset_y) / 6.0 - t / 55.0)
var v1c = math.sin(((zx + self.offset_x) + (zy + self.offset_y)) / 6.0 + t / 70.0)
var hue1 = ((v1a + v1b + v1c) / 3.0 + 1) * 180
# Second plasma layer (different scale/speed/offset)
var v2a = math.sin((zx * 1.3 + self.offset_x * 0.7) / 5.0 - t / 60.0)
var v2b = math.sin((zy * 1.3 + self.offset_y * 0.7) / 5.0 + t / 45.0)
var v2c = math.sin(((zx * 1.3 + self.offset_x * 0.7) - (zy * 1.3 + self.offset_y * 0.7)) / 5.0 - t / 80.0)
var hue2 = ((v2a + v2b + v2c) / 3.0 + 1) * 180 + 120 # offset hue
# Blend hues by averaging
var hue = int(((hue1 + hue2) / 2.0) % 360)
var col = self.hsvInt(hue, 255)
self.matrix.set(x, y, col)
end
end
self.strip.show()
end
end
var anim = PSYCHEDELIC_SCROLL_LAYERED()
################################
import math
#@ solidify:MOIRE_DRIFT
class MOIRE_DRIFT
var strip, matrix
var fast_loop_closure
var tick, frame_div
var offset1_x, offset1_y
var offset2_x, offset2_y
def init()
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
self.tick = 0
self.frame_div = 1 # update every fast_loop tick
self.offset1_x = 0.0
self.offset1_y = 0.0
self.offset2_x = 0.0
self.offset2_y = 0.0
self.fast_loop_closure = def () self.fast_loop() end
tasmota.add_fast_loop(self.fast_loop_closure)
end
def deinit()
self.strip.clear()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
self.tick += 1
if self.tick % self.frame_div != 0
return
end
# Move pattern offsets at slightly different speeds
self.offset1_x += 0.07
self.offset1_y += 0.04
self.offset2_x += 0.05
self.offset2_y += 0.06
self.draw()
end
def hsvInt(h, v)
var s = 255
var sector = h / 60
var f = h % 60
var p = (v * (255 - s)) / 255
var sf = (s * f) / 60
var q = (v * (255 - sf)) / 255
var s60f = (s * (60 - f)) / 60
var t = (v * (255 - s60f)) / 255
var r = 0
var g = 0
var b = 0
if sector == 0
r = v
g = t
b = p
elif sector == 1
r = q
g = v
b = p
elif sector == 2
r = p
g = v
b = t
elif sector == 3
r = p
g = q
b = v
elif sector == 4
r = t
g = p
b = v
else
r = v
g = p
b = q
end
return (r << 16) | (g << 8) | b
end
def draw()
var t = self.tick
for y:0..7
for x:0..31
# First grid pattern
var g1 = math.sin((x + self.offset1_x) / 2.5) + math.sin((y + self.offset1_y) / 2.5)
# Second grid pattern (different scale)
var g2 = math.sin((x + self.offset2_x) / 3.0) + math.sin((y + self.offset2_y) / 3.0)
# Combine patterns to get moiré interference
var combined = (g1 + g2) / 4.0 # normalize to ~-1..1
var hue = int(((combined + 1.0) * 180 + t) % 360)
var v = 200
var col = self.hsvInt(hue, v)
self.matrix.set(x, y, col)
end
end
self.strip.show()
end
end
var anim = MOIRE_DRIFT()
#####################################
import math
#@ solidify:SPECTRUM_ANALYZER_RANDOM
class SPECTRUM_ANALYZER_RANDOM
var strip, matrix
var fast_loop_closure
var tick, frame_div
# Layout
var cols, rows
var bands, band_w
# Signal model
var level # current bar heights (0..rows)
var target # target heights (signal input proxy)
var peak # peak-hold per band (0..rows)
var phase # per-band phase for sine driver
var speed # per-band phase speed
var jitter # per-band small noise accumulator
# Tuning
var rise, fall, peak_fall
var jitter_amt, jitter_every
# PRNG seed
var rng_seed
def init()
# LED strip + Matrix
self.cols = 32
self.rows = 8
self.strip = Leds(self.cols * self.rows, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, self.cols, self.rows, bpp, true)
# Analyzer layout
self.bands = 8
self.band_w = self.cols / self.bands # 4
# Arrays
self.level = []
self.target = []
self.peak = []
self.phase = []
self.speed = []
self.jitter = []
# Dynamics tuning
self.rise = 0.35
self.fall = 0.15
self.peak_fall = 0.30
self.jitter_amt = 0.20
self.jitter_every = 6
# Seed arrays
self.rng_seed = 123456789
for i:0..self.bands-1
self.level.push(0.0)
self.target.push(0.0)
self.peak.push(0.0)
var base_phase = 2.0 * math.pi * (i / self.bands)
self.phase.push(base_phase)
var sp = 0.08 + 0.02 * i
self.speed.push(sp)
self.jitter.push(0.0)
end
# Timing
self.tick = 0
self.frame_div = 1
# Register fast loop
self.fast_loop_closure = def () self.fast_loop() end
tasmota.add_fast_loop(self.fast_loop_closure)
end
def deinit()
self.strip.clear()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
self.tick += 1
if self.tick % self.frame_div != 0
return
end
self.updateSignal()
self.updateBars()
self.draw()
end
def rand01()
self.rng_seed = (1103515245 * self.rng_seed + 12345) % 2147483647
return self.rng_seed / 2147483647.0
end
def updateSignal()
var t = self.tick
for i:0..self.bands-1
self.phase[i] += self.speed[i]
var base = (math.sin(self.phase[i]) + 1.0) * 0.5
var tilt = 1.0 - (i / (self.bands * 1.5))
if t % self.jitter_every == 0
self.jitter[i] = (self.rand01() - 0.5) * 2.0 * self.jitter_amt
end
var val = base * tilt + self.jitter[i]
if val < 0.0 val = 0.0 end
if val > 1.0 val = 1.0 end
var shaped = math.pow(val, 0.85)
self.target[i] = shaped * self.rows
end
end
def updateBars()
for i:0..self.bands-1
var cur = self.level[i]
var tar = self.target[i]
if tar > cur
cur = cur + self.rise * (tar - cur)
else
cur = cur - self.fall * (cur - tar)
end
if cur < 0.0 cur = 0.0 end
if cur > self.rows cur = self.rows end
self.level[i] = cur
if cur > self.peak[i]
self.peak[i] = cur
else
self.peak[i] = self.peak[i] - self.peak_fall
if self.peak[i] < 0.0
self.peak[i] = 0.0
end
end
end
end
def hsvInt(h, v)
var s = 255
var sector = h / 60
var f = h % 60
var p = (v * (255 - s)) / 255
var sf = (s * f) / 60
var q = (v * (255 - sf)) / 255
var s60f = (s * (60 - f)) / 60
var t = (v * (255 - s60f)) / 255
var r = 0
var g = 0
var b = 0
if sector == 0
r = v
g = t
b = p
elif sector == 1
r = q
g = v
b = p
elif sector == 2
r = p
g = v
b = t
elif sector == 3
r = p
g = q
b = v
elif sector == 4
r = t
g = p
b = v
else
r = v
g = p
b = q
end
return (r << 16) | (g << 8) | b
end
def barColor(y, h)
var ratio = 0.0
if h > 0.0
ratio = y / (self.rows - 1.0)
end
var hue = int(120 - 120 * ratio)
var v = 180 + int(75 * (1.0 - ratio))
if v > 255 v = 255 end
if v < 0 v = 0 end
return self.hsvInt(hue, v)
end
def draw()
self.matrix.clear(0x000000)
for i:0..self.bands-1
var h = self.level[i]
var p = self.peak[i]
var x0 = i * self.band_w
var x1 = x0 + self.band_w - 1
for y:0..self.rows-1
var y_pix = self.rows - 1 - y
if y < int(h + 0.5)
var col = self.barColor(y, h)
for x:x0..x1
self.matrix.set(x, y_pix, col)
end
end
end
var peak_y = int(p)
if peak_y > 0
var py = self.rows - 1 - peak_y
var peak_col = 0xFFFFFF
for x:x0..x1
self.matrix.set(x, py, peak_col)
end
end
end
self.strip.show()
end
end
var anim = SPECTRUM_ANALYZER_RANDOM()
#########################
import math
#@ solidify:KALEIDOSCOPE_SPIN
class KALEIDOSCOPE_SPIN
var strip, matrix
var fast_loop_closure
var tick, frame_div
var cx, cy # center coordinates
var wedges # number of mirrored segments
var spin_speed
def init()
self.strip = Leds(32 * 8, gpio.pin(gpio.WS2812, 32))
var bpp = self.strip.pixel_size()
var buf = self.strip.pixels_buffer()
self.matrix = Matrix(buf, 32, 8, bpp, true)
self.cx = (32 - 1) / 2.0
self.cy = (8 - 1) / 2.0
self.wedges = 6 # number of symmetrical slices
self.spin_speed = 0.02 # radians per frame
self.tick = 0
self.frame_div = 1
self.fast_loop_closure = def () self.fast_loop() end
tasmota.add_fast_loop(self.fast_loop_closure)
end
def deinit()
self.strip.clear()
tasmota.remove_fast_loop(self.fast_loop_closure)
tasmota.remove_driver(self)
end
def fast_loop()
self.tick += 1
if self.tick % self.frame_div != 0
return
end
self.draw()
end
def hsvInt(h, v)
var s = 255
var sector = h / 60
var f = h % 60
var p = (v * (255 - s)) / 255
var sf = (s * f) / 60
var q = (v * (255 - sf)) / 255
var s60f = (s * (60 - f)) / 60
var t = (v * (255 - s60f)) / 255
var r = 0
var g = 0
var b = 0
if sector == 0
r = v
g = t
b = p
elif sector == 1
r = q
g = v
b = p
elif sector == 2
r = p
g = v
b = t
elif sector == 3
r = p
g = q
b = v
elif sector == 4
r = t
g = p
b = v
else
r = v
g = p
b = q
end
return (r << 16) | (g << 8) | b
end
def draw()
var angle_offset = self.tick * self.spin_speed
for y:0..7
for x:0..31
# Translate to center
var dx = x - self.cx
var dy = y - self.cy
# Polar coordinates
var r = math.sqrt(dx*dx + dy*dy)
var ang = math.atan2(dy, dx) + angle_offset
# Wrap angle into one wedge
var wedge_angle = (2.0 * math.pi) / self.wedges
ang = ang % wedge_angle
if ang < 0
ang += wedge_angle
end
# Map wedge angle back to 0..1
var norm_ang = ang / wedge_angle
var norm_r = r / (math.sqrt(self.cx*self.cx + self.cy*self.cy))
# Colour mapping: hue from angle, brightness from radius
var hue = int((norm_ang * 360) % 360)
var val = int(255 * (1.0 - norm_r))
if val < 0 val = 0 end
if val > 255 val = 255 end
var col = self.hsvInt(hue, val)
self.matrix.set(x, y, col)
end
end
self.strip.show()
end
end
var anim = KALEIDOSCOPE_SPIN()
-------------------------------------------------------------------------------
Notes
- Coordinates are 0-based; (0,0) is top-left.
- For RGBW, the white channel is in the least significant byte.
- Always call strip.show() after drawing to update the LEDs.
- serpentine=true mirrors every odd row automatically in get/set/store logic.
- blit() supports mono→colour with optional tint; other bpp conversions are rejected.
def l(name)
var cl = webclient()
cl.begin(f"http://192.168.0.108:8000/{name}")
var r = cl.GET()
print(r)
cl.write_file(f"/{name}")
cl.close()
end
files = [
"AlertScreen.be",
"autoexec.be",
"BaseScreen.be",
"BasicScreen.be",
"BatteryScreen.be",
"cal.bin",
"CalendarScreen.be",
"caution.bin",
"fonts.be",
"ImgScreen.be",
"MatrixController.be",
"NetScreen.be",
"red_eye.bin",
"ScreenManager.be",
"StartScreen.be",
"Tasmota.bin",
"TsensScreen.be",
"util.be",
"weather.bin",
"WeatherScreen.be"
]
for f:files
l(f)
Matrix (Berry Binding)
The Matrix class provides fast, low‑level access to an LED matrix buffer from Berry scripts.
It is designed for performance‑critical operations such as pixel manipulation, scrolling, flipping, and blitting, while leaving higher‑level drawing to Berry or other modules.
Constructor:
Matrix(buf:bytes, width:int, height:int, bpp:int[, serpentine:bool])
Matrix(width:int, height:int, bpp:int[, serpentine:bool])
- buf: existing byte buffer to wrap (no allocation, external = true)
- width, height: matrix dimensions in pixels
- bpp: bytes per pixel (1, 3, or 4 supported)
- serpentine: optional, true if rows alternate direction in physical layout
Methods:
clear([val:int])
Fast fill of the entire buffer with a single byte value.
- val: optional, 0–255. Defaults to 0 (black).
- Ignores serpentine mapping — writes raw bytes directly.
Example:
m.clear() # black
m.clear(255) # white (mono) or max channel value
get(x:int, y:int) -> int|list
Returns the pixel value at (x, y).
- bpp == 1: returns an integer 0–255
- bpp == 3 or 4: returns a packed integer (RGB or RGBA)
- other bpp: returns a list of channel values
set(x:int, y:int, val:int|list[, bri:int])
Sets the pixel at (x, y) to the given value.
- val: packed integer or list of channel values
- bri: optional brightness scaling (0–255, default 255)
blit(src:Matrix, dx:int, dy:int[, bri:int][, tint:list])
Copies pixels from src into this matrix at offset (dx, dy).
- Fast path if same bpp, no brightness change, no tint
- Supports mono→color conversion with optional tint
scroll(direction:int[, src:Matrix])
Scrolls the matrix in the given direction:
- 0 = up, 1 = left, 2 = down, 3 = right
- If src is provided, fills the vacated row/column from it
- If src is omitted, wraps from the opposite edge of the same matrix
flip(axis:str)
Flips the matrix in place.
- "h" = horizontal flip
- "v" = vertical flip
Notes:
- All coordinates are logical (x, y); serpentine mapping is handled internally by load()/store()
- clear() is the fastest way to reset the matrix — it uses a single memset over the buffer
- For drawing shapes, use Berry loops with set() or higher‑level APIs in your Leds module
Matrix C++ Driver (Native Berry Binding)
Purpose:
Provides a high‑performance native backend for the Berry Matrix class, enabling direct manipulation of LED matrix frame buffers with minimal overhead.
Implements memory‑safe allocation/wrapping, pixel access, and optimized bulk operations.
Core Structure:
struct MatrixCore
- data: pointer to raw pixel buffer (uint8_t*)
- width, height: matrix dimensions in pixels
- bpp: bytes per pixel (1, 3, or 4 supported)
- serpentine: bool, true if physical wiring alternates row direction
- external: bool, true if wrapping an external buffer (no free on deinit)
- index(x, y): returns byte offset for logical coordinates
- load(x, y, out): reads pixel at logical (x, y) into out[]
- store(x, y, in): writes pixel at logical (x, y) from in[]
- bytes_size(): total buffer size in bytes
- blit(src, dx, dy): fast copy from src to this buffer with bounds checking
Helper Functions:
- self_core(vm): fetches native MatrixCore* from Berry object
- in_bounds(mc, x, y): coordinate bounds check
- read_tint_or_default(vm, idx, dest_bpp, tint[]): parses tint list or sets default
- apply_brightness(px[], bpp, bri): scales pixel channels by brightness factor
Berry Entry Points:
be_matrix_init(buf|w, h, bpp[, serpentine])
- Wraps existing bytes or allocates new buffer
- Validates dimensions, bpp, and buffer size
- Sets .p member to native MatrixCore*
be_matrix_deinit()
- Frees buffer if owned (external == false)
- Deletes MatrixCore and clears .p
be_matrix_get(x, y)
- Returns pixel at (x, y) as int or list depending on bpp
be_matrix_set(x, y, val[, bri])
- Sets pixel at (x, y) from int or list, with optional brightness scaling
be_matrix_clear([val])
- Fast memset() over entire buffer to byte value (default 0)
be_matrix_blit(src, dx, dy[, bri][, tint])
- Fast path: same bpp, bri=255, no tint → direct MatrixCore::blit()
- Slow path: per‑pixel copy with brightness/tint or mono→color conversion
be_matrix_flip(axis)
- Horizontal ("h") or vertical ("v") in‑place flip
be_matrix_scroll(direction[, src])
- Scrolls in direction (0=up, 1=left, 2=down, 3=right)
- If src provided: fill vacated row/col from src
- If src omitted: wrap from opposite edge of same matrix
Performance Notes:
- All heavy operations (clear, blit, scroll, flip) are implemented in C++ for speed
- Serpentine mapping handled in load()/store(), transparent to higher‑level code
- Memory safety checks prevent buffer overruns
- Designed for minimal Berry VM overhead in hot paths
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment