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 4bebb2e955333a0d5188d1cceb643f5b98c837ec
parent a8b2ba411221193108d12dd40908322cfedc1879
Author: Gerd Beuster <gerd@frombelow.net>
Date:   Thu,  2 Apr 2020 22:55:38 +0200

Added flask server and RGB mode

Diffstat:
Ablue_green_flames.py | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acellular_automaton.py | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.py | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpatterns.py | 355++++++++-----------------------------------------------------------------------
Atemplates/index.html | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 436 insertions(+), 321 deletions(-)

diff --git a/blue_green_flames.py b/blue_green_flames.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 + +import time +from rpi_ws281x import Color +import random +import sys +import PyIgnition, pygame, os +import subprocess +import math + +import patterns + +class BlueGreenFlames(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) + # This mode works great with LED_BRIGHTNESS set to 16. + self.SCALE=40 # Scaling factor for screen + + # Also show in window? + # self.HEADLESS=False + self.HEADLESS=True + + if self.HEADLESS: + # On Raspbian, setting a dummy videodriver + # does not prevent pygame from trying to + # open a X display. Therefore we use a + # virtual framebuffer. + os.environ["SDL_VIDEODRIVER"] = "dummy" + subprocess.Popen(["Xvfb", ":1", "-screen", "0", "1024x768x24"], + stderr=subprocess.DEVNULL) + time.sleep(1) + os.environ["DISPLAY"] = ":1" + + pygame.display.init() + self.screen = pygame.Surface((math.ceil(self.led_matrix_width), + math.ceil(self.led_matrix_height))) + self.screen_scaled = pygame.display.set_mode((math.ceil(self.led_matrix_width*self.SCALE), + math.ceil(self.led_matrix_height*self.SCALE))) + pygame.display.set_caption("Blue Green Fire") + self.clock = pygame.time.Clock() + + self.fire = PyIgnition.ParticleEffect(self.screen, (0, 0), + (math.ceil(self.led_matrix_width), + math.ceil(self.led_matrix_height))) + gravity = self.fire.CreateDirectedGravity(strength=0.1, direction=[0, -1]) + + # Behavior of blue particles + self.blue_flames = [] + blue_flames_scale_factor = 4 + for x_pos in range(0, math.ceil(self.led_matrix_width), + math.ceil(self.led_matrix_width//5)): + new_flame = self.fire.CreateSource(pos=(x_pos, self.led_matrix_height*1.1), + initspeed = .01, initdirection = 0.0, + initspeedrandrange = .1, + initdirectionrandrange = 0.5, + particlesperframe = 1, + particlelife = 100, + drawtype = PyIgnition.DRAWTYPE_CIRCLE, + colour = (50, 20, 200), + radius = self.led_matrix_width/32.0*blue_flames_scale_factor) + new_flame.CreateParticleKeyframe(1, colour = (50, 20, 200), + radius = self.led_matrix_width/32.0*blue_flames_scale_factor) + new_flame.CreateParticleKeyframe(3, colour = (0, 0, 150), + radius = self.led_matrix_width/32.0*blue_flames_scale_factor) + new_flame.CreateParticleKeyframe(6, colour = (20, 20, 50), + 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) + self.blue_flames += [new_flame] + + # Behavior of green particle + self.green_flame_x = self.led_matrix_width / 2 + self.green_flame_y = self.led_matrix_height + 1 + self.green_flame = self.fire.CreateSource(pos=(self.green_flame_x, self.green_flame_y), + initspeed = .3, initdirection = 0.0, + initspeedrandrange = .3, + initdirectionrandrange = 0.5, + particlesperframe = 1, particlelife = 100, + drawtype = PyIgnition.DRAWTYPE_CIRCLE, + colour = (255, 200, 100), + radius = self.led_matrix_width/36.0) + self.green_flame.CreateParticleKeyframe(5, colour = (50, 200, 20), + radius = self.led_matrix_width/36.0) + self.green_flame.CreateParticleKeyframe(10, colour = (0, 150, 0), + radius = self.led_matrix_width/36.0) + self.green_flame.CreateParticleKeyframe(15, colour = (20, 50, 20), + radius = self.led_matrix_width/36.0) + self.green_flame.CreateParticleKeyframe(20, colour = (0, 0, 0), + radius = self.led_matrix_width/36.0) + + def step(self): + # Draw on screen + self.screen.fill((0, 0, 0)) + self.green_flame_x = random.randint(0, math.ceil(self.led_matrix_width)-1) + self.green_flame.pos = (self.green_flame_x, self.green_flame_y) + if self.green_flame.curframe % 10 == 0: + self.green_flame.ConsolidateKeyframes() + for b in self.blue_flames: + if b.curframe % 30 == 0: + b.ConsolidateKeyframes() + self.fire.Update() + self.fire.Redraw() + pygame.transform.scale(self.screen, (math.ceil(self.led_matrix_width*self.SCALE), + math.ceil(self.led_matrix_height*self.SCALE)), + self.screen_scaled) + pygame.display.update() + # Draw on LED matrix + fb = pygame.PixelArray(self.screen) + for y in range(math.ceil(self.led_matrix_height)): + for x in range(math.ceil(self.led_matrix_width)): + r = fb[x][y] >> 16 & 0xFF + g = fb[x][y] >> 8 & 0xFF + b = fb[x][y] & 0xFF + self.strip.setPixelColor(self.xy2i(x, + math.ceil(self.led_matrix_height)-1-y), + Color(r, g, b)) + self.strip.show() + self.clock.tick(5) diff --git a/cellular_automaton.py b/cellular_automaton.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +import math +import random +from rpi_ws281x import Color +import time + +import patterns + +class CellularAutomaton(patterns.Pattern): + + CELL_ON_COLOR = (0x00, 0xFF, 0x00) + CELL_OFF_COLOR = (0x00, 0x00, 0x00) + + def cell_is_on(self, buf, x, y): + x = (x + self.led_matrix_width) % self.led_matrix_width + y = (y + self.led_matrix_height) % self.led_matrix_height + return (buf[self.xy2i(x, y)] == self.CELL_ON_COLOR) + + + def show_buf(self, buf): + for i in range(len(buf)): + if(buf[i]): + self.strip.setPixelColor(i, Color(buf[i][0], buf[i][1], buf[i][2])) + else: + self.strip.setPixelColor(i, Color(0x00, 0x00, 0x00)) + self.strip.show() + + + 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.matrix_buf = [[None] * (math.ceil(self.led_matrix_width) * math.ceil(self.led_matrix_height)), + [None] * (math.ceil(self.led_matrix_width) * math.ceil(self.led_matrix_height))] + self.number_of_updates = 0 + self.stable_pattern = True + self.current_buf = self.matrix_buf[0] + for index in range(math.ceil(self.led_matrix_width) * math.ceil(self.led_matrix_height)): + self.current_buf[index] = self.CELL_OFF_COLOR + + + def step(self): + self.current_buf = self.matrix_buf[self.number_of_updates % 2] + self.next_buf = self.matrix_buf[(self.number_of_updates + 1) % 2] + # Since we get a stable pattern after sometime, we restart with a + # new random cellular automaton after a fixed number of steps or + # when we detect a stable situation. + if self.stable_pattern or (self.number_of_updates == 100): + if True: + # Initialize with random values + for index in range(math.ceil(self.led_matrix_width) * math.ceil(self.led_matrix_height)): + if (random.random() < .5): + self.current_buf[index] = self.CELL_ON_COLOR + else: + self.current_buf[index] = self.CELL_OFF_COLOR + else: + # Start with a glider + self.current_buf[self.xy2i(3,6)] = self.CELL_ON_COLOR + self.current_buf[self.xy2i(4,6)] = self.CELL_ON_COLOR + self.current_buf[self.xy2i(5,6)] = self.CELL_ON_COLOR + self.current_buf[self.xy2i(5,7)] = self.CELL_ON_COLOR + self.current_buf[self.xy2i(4,8)] = self.CELL_ON_COLOR + self.number_of_updates = 0 + self.stable_pattern = False + + self.number_of_updates += 1 + # Cycle over all cells + for y in range(math.ceil(self.led_matrix_height)): + for x in range(math.ceil(self.led_matrix_width)): + # Count neighbors + neighbors = 0 + for y_neighbor in range(3): + for x_neighbor in range(3): + if (((x_neighbor != 1) or (y_neighbor != 1)) and + self.cell_is_on(self.current_buf, + (x-x_neighbor+1) % self.led_matrix_width, + (y-y_neighbor+1) % self.led_matrix_height)): + neighbors += 1 + # Update cell + if (neighbors < 2) or (neighbors > 3): + self.next_buf[self.xy2i(x, y)] = self.CELL_OFF_COLOR + elif neighbors == 3: + self.next_buf[self.xy2i(x, y)] = self.CELL_ON_COLOR + else: + self.next_buf[self.xy2i(x, y)] = self.current_buf[self.xy2i(x, y)] + # Check if pattern has changed since last update. + # (We will exit the loop if the pattern becomes stable) + self.stable_pattern = True + for i in range(math.ceil(self.led_matrix_height) * math.ceil(self.led_matrix_width)): + if self.next_buf[i] != self.current_buf[i]: + self.stable_pattern = False + break + self.show_buf(self.current_buf) + time.sleep(.1) + + diff --git a/main.py b/main.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 + +from multiprocessing import Queue, Process +from flask import Flask, render_template, request +from rpi_ws281x import PixelStrip, Color +import math +import pdb + +# Patterns +import patterns +import cellular_automaton +import blue_green_flames + +# LED strip/matrix configuration + +# Matrix +#LED_MATRIX_WIDTH = 16 +#LED_MATRIX_HEIGHT = 16 +# Is row layout of LEDs is left-right, right-left, left-right, ... +# or left-right, left-right, left-right, ...? +#LED_STRIP_SNAKE = True + +# Strip +#LED_MATRIX_WIDTH = 299 +#LED_MATRIX_HEIGHT = 1 +#LED_STRIP_SNAKE = False + +# Pipe with 5 m LED strip, density 60 LEDs / m +#LED_MATRIX_WIDTH = 14.5 +#LED_MATRIX_HEIGHT = 21 +#LED_STRIP_SNAKE = False + +# Pipe with 5 m LED strip, density 144 LEDs / m +LED_MATRIX_WIDTH = 34.7 +LED_MATRIX_HEIGHT = 21 +LED_STRIP_SNAKE = False + +# LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!) - requires root! +LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0) - user must be in group gpio! +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 = 4 +# 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 + + + +# Pattern display + +# The mode is passed to the run function via a queue. Modes are passed +# as lists, where the first element is the mode number and subsquent +# numbers can be used to pass additional information for a specific +# mode. + +MODE_RGB = 0 +MODE_WORM = 1 +MODE_CELLULAR_AUTOMATON = 2 +MODE_BLUE_GREEN_FLAMES = 3 +MODE_TEST_PATTERN = 4 + +mode = [MODE_BLUE_GREEN_FLAMES] +# mode = [MODE_WORM] +# mode = [MODE_CELLULAR_AUTOMATON] + +def run(cmd_queue=None): + global mode + # Create NeoPixel object with appropriate configuration. + strip = PixelStrip(math.ceil(LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT), LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL) + # Intialize the library (must be called once before other functions). + strip.begin() + # This will be immediately replaced by the actual initial mode. + p = patterns.Pattern(strip, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT, LED_STRIP_SNAKE) + current_mode = [-1] + try: + while True: + while cmd_queue and (not cmd_queue.empty()): + mode = cmd_queue.get_nowait() + if current_mode != mode: + # External mode switch + p.stop() + current_mode = mode + p = { + MODE_RGB : patterns.RGB, + MODE_WORM : patterns.Worm, + MODE_CELLULAR_AUTOMATON : cellular_automaton.CellularAutomaton, + MODE_BLUE_GREEN_FLAMES : blue_green_flames.BlueGreenFlames, + MODE_TEST_PATTERN : patterns.TestPattern + }[current_mode[0]](strip, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT, LED_STRIP_SNAKE, current_mode[1:]) + p.step() + except KeyboardInterrupt: + p.clear_strip() + p.stop() + +# Run patterns in separate process +cmd_queue = Queue() +run = Process(target=run, args=(cmd_queue,)) +run.start() + +# Run webserver in this process +app = Flask(__name__) + +@app.route('/', methods=['GET', 'POST']) +@app.route('/index.php', methods=['GET', 'POST']) +def hello_world(): + # In general, we accept both GET and POST request, + # but for technical reasons, RGB values come only as GET requests. + (r, g, b) = (int(request.args.get('red', '0')), + int(request.args.get('green', '0')), + int(request.args.get('blue', '0'))) + mode = request.args.get('mode', None) + if not mode: + mode = request.form.get('mode', None) + if mode != None: + mode_number = {'rgb': [MODE_RGB, r, g, b], + 'worm': [MODE_WORM], + 'cellular': [MODE_CELLULAR_AUTOMATON], + 'flames': [MODE_BLUE_GREEN_FLAMES], + 'test': [MODE_TEST_PATTERN] + }[mode] + cmd_queue.put(mode_number) + # pdb.set_trace() + return render_template('index.html') diff --git a/patterns.py b/patterns.py @@ -6,86 +6,52 @@ import time from rpi_ws281x import PixelStrip, Color -import argparse import random import sys sys.path.append('pyignition') -import PyIgnition, pygame, sys, os +import PyIgnition, pygame, os import subprocess import math import pdb -import multiprocessing - -# LED strip/matrix configuration - -# Matrix -#LED_MATRIX_WIDTH = 16 -#LED_MATRIX_HEIGHT = 16 -# Is row layout of LEDs is left-right, right-left, left-right, ... -# or left-right, left-right, left-right, ...? -#LED_STRIP_SNAKE = True - -# Strip -#LED_MATRIX_WIDTH = 299 -#LED_MATRIX_HEIGHT = 1 -#LED_STRIP_SNAKE = False - -# Pipe with 5 m LED strip, density 60 LEDs / m -#LED_MATRIX_WIDTH = 14.5 -#LED_MATRIX_HEIGHT = 21 -#LED_STRIP_SNAKE = False - -# Pipe with 5 m LED strip, density 144 LEDs / m -LED_MATRIX_WIDTH = 34.7 -LED_MATRIX_HEIGHT = 21 -LED_STRIP_SNAKE = False - -# LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!) - requires root! -LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0) - user must be in group gpio! -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 = 4 -# 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 class Pattern(): - def __init__(self, strip): + def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): self.strip = strip - self.clear_strip() + self.led_matrix_width = led_matrix_width + self.led_matrix_height = led_matrix_height + self.led_strip_snake = led_strip_snake + self.opts = opts + # self.clear_strip() def step(self): pass def stop(self): - self.clear_strip() + pass def clear_strip(self): - for i in range(math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)): + for i in range(math.ceil(self.led_matrix_width) * math.ceil(self.led_matrix_height)): self.strip.setPixelColor(i, Color(0x00, 0x00, 0x00)) self.strip.show() def xy2i(self, x, y): - i = y * LED_MATRIX_WIDTH - if LED_STRIP_SNAKE and (y % 2): - i += LED_MATRIX_WIDTH - x -1 + i = y * self.led_matrix_width + if self.led_strip_snake and (y % 2): + i += self.led_matrix_width - x -1 else: i += x return math.floor(i) + class TestPattern(Pattern): CELL_ON = Color(0x00, 0xFF, 0x00) CELL_OFF = Color(0x00, 0x00, 0x00) - def __init__(self, strip): - Pattern.__init__(self, strip=strip) + def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): + 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) # Initialize dummy display os.environ["SDL_VIDEODRIVER"] = "dummy" subprocess.Popen(["Xvfb", ":1", "-screen", "0", "1024x768x24"], @@ -99,29 +65,32 @@ class TestPattern(Pattern): def step(self): print("({x}/{y})".format(x=self.x, y=self.y)) - for i in range(math.ceil(LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT)): + for i in range(math.ceil(self.led_matrix_width * self.led_matrix_height)): self.strip.setPixelColor(i, self.CELL_OFF) - for col in range(math.ceil(LED_MATRIX_WIDTH)): + for col in range(math.ceil(self.led_matrix_width)): self.strip.setPixelColor(self.xy2i(col, self.y), self.CELL_ON) - for row in range(math.ceil(LED_MATRIX_HEIGHT)): + for row in range(math.ceil(self.led_matrix_height)): self.strip.setPixelColor(self.xy2i(self.x, row), self.CELL_ON) self.strip.show() time.sleep(1) - self.x = (self.x + 1) % LED_MATRIX_WIDTH - self.y = (self.y + 1) % LED_MATRIX_HEIGHT + self.x = (self.x + 1) % self.led_matrix_width + self.y = (self.y + 1) % self.led_matrix_height + -class AllOn(Pattern): - def __init__(self, strip): - Pattern.__init__(self, strip=strip) - for i in range(math.ceil(LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT)): - strip.setPixelColor(i, Color(LED_BRIGHTNESS, LED_BRIGHTNESS, LED_BRIGHTNESS)) +class RGB(Pattern): + def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): + 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) + (r, g, b) = opts + for i in range(math.ceil(self.led_matrix_width*self.led_matrix_height)): + strip.setPixelColor(i, Color(r, g, b)) strip.show() + class Worm(Pattern): - def __init__(self, strip): - Pattern.__init__(self, strip=strip) + def __init__(self, strip, led_matrix_width, led_matrix_height, led_strip_snake, opts=None): + 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) # LED_BRIGHTNESS should be at least 15. self.worm_pattern = list(map(lambda x: Color(x[0], x[1], x[2]), [(0x00, 0x05, 0x00), @@ -137,11 +106,11 @@ class Worm(Pattern): (0x00, 0x05, 0x00), ])) # Set background color - for i in range(math.ceil(LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT)): + for i in range(math.ceil(self.led_matrix_width*self.led_matrix_height)): self.strip.setPixelColor(i, Color(0x00, 0x05, 0x00)) self.strip.show() self.direction = 1 - self.pos = LED_MATRIX_WIDTH // 2 + self.pos = self.led_matrix_width // 2 def step(self): # Larson worm @@ -151,271 +120,15 @@ class Worm(Pattern): self.direction *= -1 if self.pos == 0: self.direction = 1 - if (self.pos + len(self.worm_pattern)) == LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT: + if (self.pos + len(self.worm_pattern)) == self.led_matrix_width*self.led_matrix_height: self.direction = -1 self.pos += self.direction # Random set blue pixel if (self.pos % 8) == 0: - self.strip.setPixelColor(random.randint(0, math.ceil(LED_MATRIX_WIDTH*LED_MATRIX_HEIGHT-1)), Color(0x00, 0x00, 0x10)) + self.strip.setPixelColor(random.randint(0, math.ceil(self.led_matrix_width*self.led_matrix_height-1)), Color(0x00, 0x00, 0x10)) # Update Strip self.strip.show() time.sleep(.04) -class CellularAutomaton(Pattern): - - CELL_ON_COLOR = (0x00, 0xFF, 0x00) - CELL_OFF_COLOR = (0x00, 0x00, 0x00) - - def cell_is_on(self, buf, x, y): - x = (x + LED_MATRIX_WIDTH) % LED_MATRIX_WIDTH - y = (y + LED_MATRIX_HEIGHT) % LED_MATRIX_HEIGHT - return (buf[self.xy2i(x, y)] == self.CELL_ON_COLOR) - - - def show_buf(self, buf): - for i in range(len(buf)): - if(buf[i]): - self.strip.setPixelColor(i, Color(buf[i][0], buf[i][1], buf[i][2])) - else: - self.strip.setPixelColor(i, Color(0x00, 0x00, 0x00)) - self.strip.show() - - - def __init__(self, strip): - Pattern.__init__(self, strip=strip) - self.matrix_buf = [[None] * (math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)), - [None] * (math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT))] - self.number_of_updates = 0 - self.stable_pattern = True - self.current_buf = self.matrix_buf[0] - for index in range(math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)): - self.current_buf[index] = self.CELL_OFF_COLOR - - - def step(self): - self.current_buf = self.matrix_buf[self.number_of_updates % 2] - self.next_buf = self.matrix_buf[(self.number_of_updates + 1) % 2] - # Since we get a stable pattern after sometime, we restart with a - # new random cellular automaton after a fixed number of steps or - # when we detect a stable situation. - if self.stable_pattern or (self.number_of_updates == 100): - if True: - # Initialize with random values - for index in range(math.ceil(LED_MATRIX_WIDTH) * math.ceil(LED_MATRIX_HEIGHT)): - if (random.random() < .5): - self.current_buf[index] = self.CELL_ON_COLOR - else: - self.current_buf[index] = self.CELL_OFF_COLOR - else: - # Start with a glider - self.current_buf[self.xy2i(3,6)] = self.CELL_ON_COLOR - self.current_buf[self.xy2i(4,6)] = self.CELL_ON_COLOR - self.current_buf[self.xy2i(5,6)] = self.CELL_ON_COLOR - self.current_buf[self.xy2i(5,7)] = self.CELL_ON_COLOR - self.current_buf[self.xy2i(4,8)] = self.CELL_ON_COLOR - self.number_of_updates = 0 - self.stable_pattern = False - - self.number_of_updates += 1 - # Cycle over all cells - for y in range(math.ceil(LED_MATRIX_HEIGHT)): - for x in range(math.ceil(LED_MATRIX_WIDTH)): - # Count neighbors - neighbors = 0 - for y_neighbor in range(3): - for x_neighbor in range(3): - if (((x_neighbor != 1) or (y_neighbor != 1)) and - self.cell_is_on(self.current_buf, - (x-x_neighbor+1) % LED_MATRIX_WIDTH, - (y-y_neighbor+1) % LED_MATRIX_HEIGHT)): - neighbors += 1 - # Update cell - if (neighbors < 2) or (neighbors > 3): - self.next_buf[self.xy2i(x, y)] = self.CELL_OFF_COLOR - elif neighbors == 3: - self.next_buf[self.xy2i(x, y)] = self.CELL_ON_COLOR - else: - self.next_buf[self.xy2i(x, y)] = self.current_buf[self.xy2i(x, y)] - # Check if pattern has changed since last update. - # (We will exit the loop if the pattern becomes stable) - self.stable_pattern = True - for i in range(math.ceil(LED_MATRIX_HEIGHT) * math.ceil(LED_MATRIX_WIDTH)): - if self.next_buf[i] != self.current_buf[i]: - self.stable_pattern = False - break - self.show_buf(self.current_buf) - time.sleep(.1) - - -class BlueGreenFlames(Pattern): - def __init__(self, strip): - Pattern.__init__(self, strip=strip) - # This mode works great with LED_BRIGHTNESS set to 16. - self.SCALE=40 # Scaling factor for screen - - # Also show in window? - # self.HEADLESS=False - self.HEADLESS=True - - if self.HEADLESS: - # On Raspbian, setting a dummy videodriver - # does not prevent pygame from trying to - # open a X display. Therefore we use a - # virtual framebuffer. - os.environ["SDL_VIDEODRIVER"] = "dummy" - subprocess.Popen(["Xvfb", ":1", "-screen", "0", "1024x768x24"], - stderr=subprocess.DEVNULL) - time.sleep(1) - os.environ["DISPLAY"] = ":1" - - pygame.display.init() - self.screen = pygame.Surface((math.ceil(LED_MATRIX_WIDTH), - math.ceil(LED_MATRIX_HEIGHT))) - self.screen_scaled = pygame.display.set_mode((math.ceil(LED_MATRIX_WIDTH*self.SCALE), - math.ceil(LED_MATRIX_HEIGHT*self.SCALE))) - pygame.display.set_caption("Blue Green Fire") - self.clock = pygame.time.Clock() - - self.fire = PyIgnition.ParticleEffect(self.screen, (0, 0), - (math.ceil(LED_MATRIX_WIDTH), - math.ceil(LED_MATRIX_HEIGHT))) - gravity = self.fire.CreateDirectedGravity(strength=0.1, direction=[0, -1]) - - # Behavior of blue particles - self.blue_flames = [] - blue_flames_scale_factor = 4 - for x_pos in range(0, math.ceil(LED_MATRIX_WIDTH), - math.ceil(LED_MATRIX_WIDTH//5)): - new_flame = self.fire.CreateSource(pos=(x_pos, LED_MATRIX_HEIGHT*1.1), - initspeed = .01, initdirection = 0.0, - initspeedrandrange = .1, - initdirectionrandrange = 0.5, - particlesperframe = 1, - particlelife = 100, - drawtype = PyIgnition.DRAWTYPE_CIRCLE, - colour = (50, 20, 200), - radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(1, colour = (50, 20, 200), - radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(3, colour = (0, 0, 150), - radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(6, colour = (20, 20, 50), - radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor) - new_flame.CreateParticleKeyframe(9, colour = (0, 0, 0), - radius = LED_MATRIX_WIDTH/32.0*blue_flames_scale_factor) - self.blue_flames += [new_flame] - - - # Behavior of green particle - self.green_flame_x = LED_MATRIX_WIDTH / 2 - self.green_flame_y = LED_MATRIX_HEIGHT + 1 - self.green_flame = self.fire.CreateSource(pos=(self.green_flame_x, self.green_flame_y), - initspeed = .3, initdirection = 0.0, - initspeedrandrange = .3, - initdirectionrandrange = 0.5, - particlesperframe = 1, particlelife = 100, - drawtype = PyIgnition.DRAWTYPE_CIRCLE, - colour = (255, 200, 100), - radius = LED_MATRIX_WIDTH/36.0) - self.green_flame.CreateParticleKeyframe(5, colour = (50, 200, 20), - radius = LED_MATRIX_WIDTH/36.0) - self.green_flame.CreateParticleKeyframe(10, colour = (0, 150, 0), - radius = LED_MATRIX_WIDTH/36.0) - self.green_flame.CreateParticleKeyframe(15, colour = (20, 50, 20), - radius = LED_MATRIX_WIDTH/36.0) - self.green_flame.CreateParticleKeyframe(20, colour = (0, 0, 0), - radius = LED_MATRIX_WIDTH/36.0) - - - - - def step(self): - for event in pygame.event.get(): - if event.type == pygame.QUIT: - sys.exit() - # Draw on screen - self.screen.fill((0, 0, 0)) - self.green_flame_x = random.randint(0, math.ceil(LED_MATRIX_WIDTH)-1) - self.green_flame.pos = (self.green_flame_x, self.green_flame_y) - if self.green_flame.curframe % 10 == 0: - self.green_flame.ConsolidateKeyframes() - for b in self.blue_flames: - if b.curframe % 30 == 0: - b.ConsolidateKeyframes() - self.fire.Update() - self.fire.Redraw() - pygame.transform.scale(self.screen, (math.ceil(LED_MATRIX_WIDTH*self.SCALE), - math.ceil(LED_MATRIX_HEIGHT*self.SCALE)), - self.screen_scaled) - pygame.display.update() - # Draw on LED matrix - fb = pygame.PixelArray(self.screen) - for y in range(math.ceil(LED_MATRIX_HEIGHT)): - for x in range(math.ceil(LED_MATRIX_WIDTH)): - r = fb[x][y] >> 16 & 0xFF - g = fb[x][y] >> 8 & 0xFF - b = fb[x][y] & 0xFF - self.strip.setPixelColor(self.xy2i(x, - math.ceil(LED_MATRIX_HEIGHT)-1-y), - Color(r, g, b)) - self.strip.show() - self.clock.tick(5) - -MODE_ALL_ON = 0 -MODE_WORM = 1 -MODE_CELLULAR_AUTOMATON = 2 -MODE_BLUE_GREEN_FLAMES = 3 -MODE_TEST_PATTERN = 4 - -mode = MODE_BLUE_GREEN_FLAMES -# mode = MODE_WORM -# mode = MODE_CELLULAR_AUTOMATON - -def run(cmd_queue): - global mode - # Create NeoPixel object with appropriate configuration. - strip = PixelStrip(math.ceil(LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT), LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL) - # Intialize the library (must be called once before other functions). - strip.begin() - # This will be immediately replaced by the actual initial mode. - p = Pattern(strip) - current_mode = -1 - try: - while True: - if not cmd_queue.empty(): - mode = cmd_queue.get_nowait() - print("Got {}".format(mode)) - if current_mode != mode: - # External mode switch - p.stop() - current_mode = mode - p = { - MODE_ALL_ON : AllOn, - MODE_WORM : Worm, - MODE_CELLULAR_AUTOMATON : CellularAutomaton, - MODE_BLUE_GREEN_FLAMES : BlueGreenFlames, - MODE_TEST_PATTERN : TestPattern - }[current_mode](strip) - p.step() - except KeyboardInterrupt: - p.stop() - -def cmd_demo(cmd_queue): - mode = 0 - while True: - mode = (mode + 1) % 5 - print("Putting {}".format(mode)) - cmd_queue.put(mode) - time.sleep(10) - -# Main program logic follows: -if __name__ == '__main__': - cmd_queue = multiprocessing.Queue() - run = multiprocessing.Process(target=run, args=(cmd_queue,)) - run.start() - if False: - cmd_demo = multiprocessing.Process(target=cmd_demo, args=(cmd_queue,)) - cmd_demo.start() - run.join() diff --git a/templates/index.html b/templates/index.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html lang="de"> + <head> + <meta charset="utf-8"> + <title>LED-Steuerung</title> + <style> +button{width:10em;} +@media only screen and (max-device-width: 1024px){ +html{font-size : 300%; line-height: 50%;} +button{font-size : 190%; width:10em;} +.slider{ + height: 5ex; + width: 40em; + autoScaleSlider:false, + autoHeight: false +} + </style> + </head> + <body> + <h1>LED-Steuerung</h1> + <form action="/" method="POST"> + <div class="slidecontainer"> + R <input type="range" min="0" max="255" value="0" class="slider" id="slider_red"> + <label id="label_red"></label><br /> + G <input type="range" min="0" max="255" value="0" class="slider" id="slider_green"> + <label id="label_green"></label><br /> + B <input type="range" min="0" max="255" value="0" class="slider" id="slider_blue"> + <label id="label_blue"></label><br /> + </div><br /> + <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="test">Testmuster</button><br /><br /> + </form> + <p><a href="config.html"</a>Konfigurieren</a></p> + <!-- Update slider on the fly --> + <script> + var slider_red = document.getElementById("slider_red"); + var label_red = document.getElementById("label_red"); + var slider_green = document.getElementById("slider_green"); + var label_green = document.getElementById("label_green"); + var slider_blue = document.getElementById("slider_blue"); + var label_blue = document.getElementById("label_blue"); + label_red.innerHTML = slider_red.value; + label_green.innerHTML = slider_green.value; + label_blue.innerHTML = slider_blue.value; + slider_red.oninput = function() { + label_red.innerHTML = slider_red.value; + label_green.innerHTML = slider_green.value; + label_blue.innerHTML = slider_blue.value; + var i = document.createElement("img"); + // Didn't figure out how to do this with a POST request without + // resorting to external libraries + i.src = "?mode=rgb&red=" + + slider_red.value + "&green=" + slider_green.value + + "&blue=" + slider_blue.value; + }; + slider_green.oninput = slider_red.oninput; + slider_blue.oninput = slider_red.oninput; + </script> + </body> +</html>