led_pillar

Documentation: http://frombelow.net/projects/led_pillar/
Clone: git clone https://git.frombelow.net/led_pillar.git
Log | Files | Refs | Submodules | README | LICENSE

commit d946424604e3872f9e28c087d26e82a22f5100df
parent 4bebb2e955333a0d5188d1cceb643f5b98c837ec
Author: Gerd Beuster <gerd@frombelow.net>
Date:   Sat, 11 Apr 2020 09:06:58 +0200

Flowing color patterns (RGB and HSV) added

Diffstat:
Mblue_green_flames.py | 16++++++++--------
Aflowing_colors.py | 171+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmain.py | 20+++++++++++++++-----
Mtemplates/index.html | 2++
4 files changed, 196 insertions(+), 13 deletions(-)

diff --git a/blue_green_flames.py b/blue_green_flames.py @@ -56,13 +56,13 @@ class BlueGreenFlames(patterns.Pattern): particlesperframe = 1, particlelife = 100, drawtype = PyIgnition.DRAWTYPE_CIRCLE, - colour = (50, 20, 200), + colour = (65, 25, 255), radius = self.led_matrix_width/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(1, colour = (50, 20, 200), + new_flame.CreateParticleKeyframe(1, colour = (65, 25, 255), radius = self.led_matrix_width/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(3, colour = (0, 0, 150), + new_flame.CreateParticleKeyframe(3, colour = (0, 0, 190), radius = self.led_matrix_width/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(6, colour = (20, 20, 50), + new_flame.CreateParticleKeyframe(6, colour = (25, 25, 65), radius = self.led_matrix_width/32.0*blue_flames_scale_factor) new_flame.CreateParticleKeyframe(9, colour = (0, 0, 0), radius = self.led_matrix_width/32.0*blue_flames_scale_factor) @@ -77,13 +77,13 @@ class BlueGreenFlames(patterns.Pattern): initdirectionrandrange = 0.5, particlesperframe = 1, particlelife = 100, drawtype = PyIgnition.DRAWTYPE_CIRCLE, - colour = (255, 200, 100), + colour = (255, 255, 130), radius = self.led_matrix_width/36.0) - self.green_flame.CreateParticleKeyframe(5, colour = (50, 200, 20), + self.green_flame.CreateParticleKeyframe(5, colour = (65, 255, 25), radius = self.led_matrix_width/36.0) - self.green_flame.CreateParticleKeyframe(10, colour = (0, 150, 0), + self.green_flame.CreateParticleKeyframe(10, colour = (0, 190, 0), radius = self.led_matrix_width/36.0) - self.green_flame.CreateParticleKeyframe(15, colour = (20, 50, 20), + self.green_flame.CreateParticleKeyframe(15, colour = (25, 65, 25), radius = self.led_matrix_width/36.0) self.green_flame.CreateParticleKeyframe(20, colour = (0, 0, 0), radius = self.led_matrix_width/36.0) diff --git a/flowing_colors.py b/flowing_colors.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 + +from rpi_ws281x import Color +import random +import math +import time + +import patterns + +# Utilitiy classes and functions + + +class RandomWalker(): + """Walk towards random target values + + Returns fixed value if min==max.""" + + def __init__(self, min, max, step): + self.min = min + self.max = max + self.step = step + if self.min == self.max: + self.val = self.min + self.target = self.min + self.val = random.uniform(self.min, self.max) + self.target = random.uniform(self.min, self.max) + + def next(self): + if self.min != self.max: + if abs(self.val - self.target) <= self.step*2: + self.target = random.uniform(self.min, self.max) + if self.val <= self.target: + self.val += self.step + else: + self.val -= self.step + return self.val + + +sin_norm_cache = {} +def sin_norm(x, m_in, m_out): + """Normalized sinus function: x in range [0..m_in] mapped to [0..m_out] + + x values > min are reduced mod m_in+1. + """ + if (x % (m_in + 1), m_in, m_out) in sin_norm_cache: + return(sin_norm_cache[(x % (m_in + 1), m_in, m_out)]) + else: + rad = (float(x % (m_in + 1)) / (m_in + 1) * (2 * math.pi)) - math.pi + out_percent = (math.sin(rad) + 1) / 2.0 + res = int(out_percent * m_out) + sin_norm_cache.update({(x % (m_in + 1), m_in, m_out): res}) + return(res) + + +def hsv2rgb(h, s, v): + if h == None: + return (0, 0, 0) + else: + hd = h / 60.0 + c = s * v + x = c * (1 - abs((hd % 2) - 1)) + if 0 <= hd <= 1: + (r, g, b) = (c, x, 0) + elif 1 < hd <= 2: + (r, g, b) = (x, c, 0) + elif 2 < hd <= 3: + (r, g, b) = (0, c, x) + elif 3 < hd <= 4: + (r, g, b) = (0, x, c) + elif 4 < hd <= 5: + (r, g, b) = (x, 0, c) + elif 5 < hd <= 6: + (r, g, b) = (c, 0, x) + m = v - c + return map(lambda x: x*0xFF, (r+m, g+m, b+m)) + + +# Pattern classes + + +class FlowingColorsRGB(patterns.Pattern): + + def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): + patterns.Pattern.__init__(self, strip=strip, led_matrix_width=led_matrix_width, led_matrix_height=led_matrix_height, led_strip_snake = led_strip_snake, opts=opts) + self.max_period = int(min(self.led_matrix_width, self.led_matrix_height)*2.0) + self.min_period = int(min(self.led_matrix_width, self.led_matrix_height)*1.0) + self.min_offset = 0 + self.max_offset = int(max(self.led_matrix_width, self.led_matrix_height)*1.0) + # For each color channel, the period and the offset of the sinus function + # mapping (x/y) values to color values changes in a random walk. + self.channels = [ + # Hue x period + [RandomWalker(min=self.min_period, + max=self.max_period, + step=self.max_period/100.0), + # y period + RandomWalker(min=self.min_period, + max=self.max_period, + step=self.max_period/100.0), + # x offset + RandomWalker(min=self.min_offset, + max=self.max_offset, + step=self.max_offset/100.0), + # y offset + RandomWalker(min=self.min_offset, + max=self.max_offset, + step=self.max_offset/100.0)] for _ in range(3)] + + def step(self): + # Change period and offset + for c in self.channels: + for w in c: + w.next() + r = self.sin_x_y_avg(period_offset=self.channels[0], m_out=255) + g = self.sin_x_y_avg(period_offset=self.channels[1], m_out=255) + b = self.sin_x_y_avg(period_offset=self.channels[2], m_out=255) + for x in range(math.ceil(self.led_matrix_width)): + for y in range((self.led_matrix_height)): + self.strip.setPixelColor(self.xy2i(x, y), + Color(int(r[x][y]), int(g[x][y]), int(b[x][y]))) + self.strip.show() + time.sleep(.1) + + def sin_x_y_avg(self, period_offset, m_out): + # period_offset are passed as instances of RandomWalker. We have to get the + # values. + (x_period, y_period, x_offset, y_offset) = map(lambda x: x.val, period_offset) + pixel = [[None for _ in range(math.ceil(self.led_matrix_height))] for _ in range(math.ceil(self.led_matrix_width))] + for x in range(math.ceil(self.led_matrix_width)): + for y in range(math.ceil(self.led_matrix_height)): + pixel_x = sin_norm(x=x+x_offset, m_in=1.0*x_period, m_out=m_out) + pixel_y = sin_norm(x=y+y_offset, m_in=1.0*y_period, m_out=m_out) + pixel[x][y] = (pixel_x + pixel_y) / 2 + return pixel + + +class FlowingColorsHSV(FlowingColorsRGB): + def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): + patterns.Pattern.__init__(self, strip=strip, led_matrix_width=led_matrix_width, led_matrix_height=led_matrix_height, led_strip_snake = led_strip_snake, opts=opts) + self.max_period = int(min(self.led_matrix_width, self.led_matrix_height)*3.0) + self.min_period = int(min(self.led_matrix_width, self.led_matrix_height)*0.5) + self.min_offset = 0 + self.max_offset = int(max(self.led_matrix_width, self.led_matrix_height)*20.0) + self.hue_sin = [RandomWalker(min=self.min_period, + max=self.max_period, + step=self.max_period/1000.0), + # y period + RandomWalker(min=self.min_period, + max=self.max_period, + step=self.max_period/1000.0), + # x offset + RandomWalker(min=self.min_offset, + max=self.max_offset, + step=self.max_offset/10000.0), + # y offset + RandomWalker(min=self.min_offset, + max=self.max_offset, + step=self.max_offset/10000.0)] + self.hue_offset = RandomWalker(min=0, max=360, step=1) + + def step(self): + for w in self.hue_sin: + w.next() + self.hue_offset.next() + h = self.sin_x_y_avg(period_offset=self.hue_sin, m_out=360) + for x in range(math.ceil(self.led_matrix_width)): + for y in range((self.led_matrix_height)): + (r, g, b) = hsv2rgb((h[x][y]+self.hue_offset.val) % 360, 1, 1) + self.strip.setPixelColor(self.xy2i(x, y), Color(int(r), int(g), int(b))) + self.strip.show() + time.sleep(.1) diff --git a/main.py b/main.py @@ -10,6 +10,7 @@ import pdb import patterns import cellular_automaton import blue_green_flames +import flowing_colors # LED strip/matrix configuration @@ -41,9 +42,10 @@ LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) LED_DMA = 10 # DMA channel to use for generating signal (try 10) # Set to 0 for darkest and 255 for brightest # LED_BRIGHTNESS = 255 -LED_BRIGHTNESS = 16 -# LED_BRIGHTNESS = 8 +# LED_BRIGHTNESS = 16 +LED_BRIGHTNESS = 8 # LED_BRIGHTNESS = 4 +# LED_BRIGHTNESS = 2 # LED_BRIGHTNESS = 1 LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53 @@ -61,11 +63,15 @@ MODE_RGB = 0 MODE_WORM = 1 MODE_CELLULAR_AUTOMATON = 2 MODE_BLUE_GREEN_FLAMES = 3 -MODE_TEST_PATTERN = 4 +MODE_FLOW_RGB = 4 +MODE_FLOW_HSV = 5 +MODE_TEST_PATTERN = 6 mode = [MODE_BLUE_GREEN_FLAMES] -# mode = [MODE_WORM] -# mode = [MODE_CELLULAR_AUTOMATON] +#mode = [MODE_WORM] +#mode = [MODE_CELLULAR_AUTOMATON] +#mode = [MODE_FLOW_RGB] +#mode = [MODE_FLOW_HSV] def run(cmd_queue=None): global mode @@ -89,6 +95,8 @@ def run(cmd_queue=None): MODE_WORM : patterns.Worm, MODE_CELLULAR_AUTOMATON : cellular_automaton.CellularAutomaton, MODE_BLUE_GREEN_FLAMES : blue_green_flames.BlueGreenFlames, + MODE_FLOW_RGB : flowing_colors.FlowingColorsRGB, + MODE_FLOW_HSV : flowing_colors.FlowingColorsHSV, MODE_TEST_PATTERN : patterns.TestPattern }[current_mode[0]](strip, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT, LED_STRIP_SNAKE, current_mode[1:]) p.step() @@ -120,6 +128,8 @@ def hello_world(): 'worm': [MODE_WORM], 'cellular': [MODE_CELLULAR_AUTOMATON], 'flames': [MODE_BLUE_GREEN_FLAMES], + 'flow_rgb': [MODE_FLOW_RGB], + 'flow_hsv': [MODE_FLOW_HSV], 'test': [MODE_TEST_PATTERN] }[mode] cmd_queue.put(mode_number) diff --git a/templates/index.html b/templates/index.html @@ -30,6 +30,8 @@ button{font-size : 190%; width:10em;} <button type="submit" name="mode" value="worm">Wurm</button><br /><br /> <button type="submit" name="mode" value="cellular">Zellulärer Automat</button><br /><br /> <button type="submit" name="mode" value="flames">Feuer</button><br /><br /> + <button type="submit" name="mode" value="flow_rgb">Flow (RGB)</button><br /><br /> + <button type="submit" name="mode" value="flow_hsv">Flow (HSV)</button><br /><br /> <button type="submit" name="mode" value="test">Testmuster</button><br /><br /> </form> <p><a href="config.html"</a>Konfigurieren</a></p>