forked from pvx/litsimaja
parent
1fc4303ae2
commit
103a5c9252
11 changed files with 3586 additions and 0 deletions
@ -0,0 +1 @@ |
|||||||
|
litsimaja.log |
@ -0,0 +1,94 @@ |
|||||||
|
from rpi_ws281x import PixelStrip |
||||||
|
import atexit |
||||||
|
|
||||||
|
|
||||||
|
class _LedData(object): |
||||||
|
_led: [] |
||||||
|
|
||||||
|
def __init__(self, channel, size): |
||||||
|
self.size = size |
||||||
|
self.channel = channel |
||||||
|
|
||||||
|
def __getitem__(self, pos): |
||||||
|
"""Return the 24-bit RGB color value at the provided position or slice |
||||||
|
of positions. |
||||||
|
""" |
||||||
|
# Handle if a slice of positions are passed in by grabbing all the values |
||||||
|
# and returning them in a list. |
||||||
|
if isinstance(pos, slice): |
||||||
|
return [self._led[n] for n in range(*pos.indices(self.size))] |
||||||
|
# Else assume the passed in value is a number to the position. |
||||||
|
else: |
||||||
|
return self._led[pos] |
||||||
|
|
||||||
|
def __setitem__(self, pos, value): |
||||||
|
"""Set the 24-bit RGB color value at the provided position or slice of |
||||||
|
positions. |
||||||
|
""" |
||||||
|
# Handle if a slice of positions are passed in by setting the appropriate |
||||||
|
# LED data values to the provided values. |
||||||
|
if isinstance(pos, slice): |
||||||
|
index = 0 |
||||||
|
for n in range(*pos.indices(self.size)): |
||||||
|
self._led[n] = value[index] |
||||||
|
index += 1 |
||||||
|
# Else assume the passed in value is a number to the position. |
||||||
|
# else: |
||||||
|
self._led[pos] = value |
||||||
|
|
||||||
|
|
||||||
|
class FakeStrip(PixelStrip): |
||||||
|
_brightness: int |
||||||
|
|
||||||
|
def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, |
||||||
|
brightness=255, channel=0, strip_type=None, gamma=None): |
||||||
|
|
||||||
|
if gamma is None: |
||||||
|
# Support gamma in place of strip_type for back-compat with |
||||||
|
# previous version of forked library |
||||||
|
if type(strip_type) is list and len(strip_type) == 256: |
||||||
|
gamma = strip_type |
||||||
|
# strip_type = None |
||||||
|
else: |
||||||
|
gamma = list(range(256)) |
||||||
|
|
||||||
|
# Initialize the channel in use |
||||||
|
self._channel = None |
||||||
|
self._gamma = gamma |
||||||
|
self._brightness = brightness |
||||||
|
|
||||||
|
# Grab the led data array. |
||||||
|
self._led_data = _LedData(self._channel, num) |
||||||
|
|
||||||
|
# Substitute for __del__, traps an exit condition and cleans up properly |
||||||
|
atexit.register(self._cleanup) |
||||||
|
|
||||||
|
def _cleanup(self): |
||||||
|
# Clean up memory used by the library when not needed anymore. |
||||||
|
if self._channel is not None: |
||||||
|
self._channel = None |
||||||
|
|
||||||
|
def setGamma(self, gamma): |
||||||
|
if type(gamma) is list and len(gamma) == 256: |
||||||
|
self._gamma = gamma |
||||||
|
|
||||||
|
def begin(self): |
||||||
|
"""Initialize library, must be called once before other functions are called.""" |
||||||
|
return |
||||||
|
|
||||||
|
def show(self): |
||||||
|
"""Update the display with the data from the LED buffer.""" |
||||||
|
return # render |
||||||
|
|
||||||
|
def getBrightness(self) -> int: |
||||||
|
return self._brightness |
||||||
|
|
||||||
|
def setBrightness(self, brightness: int): |
||||||
|
"""Scale each LED in the buffer by the provided brightness. |
||||||
|
A brightness of 0 is the darkest and 255 is the brightest. |
||||||
|
""" |
||||||
|
self._brightness = brightness |
||||||
|
|
||||||
|
def numPixels(self): |
||||||
|
"""Return the number of pixels in the display.""" |
||||||
|
return self._led_data.size |
@ -0,0 +1,33 @@ |
|||||||
|
from rpi_ws281x import PixelStrip |
||||||
|
from lib.FakeStrip import FakeStrip |
||||||
|
from lib.LoopSwitch import LoopSwitch |
||||||
|
|
||||||
|
|
||||||
|
class Litsimaja(object): |
||||||
|
_strip: PixelStrip |
||||||
|
_loops: [] |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self._strip = FakeStrip(290, 18, 800000, 10, False, 255, 0, 4104) |
||||||
|
self._loops = [] |
||||||
|
self._strip.begin() |
||||||
|
|
||||||
|
def count_pixels(self) -> int: |
||||||
|
return self._strip.numPixels() |
||||||
|
|
||||||
|
def get_strip(self) -> PixelStrip: |
||||||
|
return self._strip |
||||||
|
|
||||||
|
def set_pixel_color(self, n: int, color: int) -> None: |
||||||
|
self._strip.setPixelColor(n, color) |
||||||
|
|
||||||
|
def show(self) -> None: |
||||||
|
self._strip.show() |
||||||
|
|
||||||
|
def add_loop(self, loop: LoopSwitch): |
||||||
|
self._loops.append(loop) |
||||||
|
|
||||||
|
def clear_loops(self): |
||||||
|
for loop in self._loops: |
||||||
|
loop.stop() |
||||||
|
self._loops.clear() |
@ -0,0 +1,11 @@ |
|||||||
|
class LoopSwitch(object): |
||||||
|
_run: bool |
||||||
|
|
||||||
|
def __init__(self): |
||||||
|
self._run = True |
||||||
|
|
||||||
|
def stop(self): |
||||||
|
self._run = False |
||||||
|
|
||||||
|
def status(self) -> bool: |
||||||
|
return self._run |
@ -0,0 +1,17 @@ |
|||||||
|
from lib.Litsimaja import Litsimaja |
||||||
|
from lib.LoopSwitch import LoopSwitch |
||||||
|
|
||||||
|
|
||||||
|
class Program: |
||||||
|
_lm: Litsimaja |
||||||
|
_loop: LoopSwitch |
||||||
|
|
||||||
|
def __init__(self, lm: Litsimaja): |
||||||
|
self._lm = lm |
||||||
|
self._loop = LoopSwitch() |
||||||
|
|
||||||
|
def get_loop(self): |
||||||
|
return self._loop |
||||||
|
|
||||||
|
def run(self, args: [] = None): |
||||||
|
pass |
@ -0,0 +1,40 @@ |
|||||||
|
from os import scandir |
||||||
|
from lib.Litsimaja import Litsimaja |
||||||
|
from lib.Program import Program |
||||||
|
|
||||||
|
|
||||||
|
def resolve(namespace: str, class_name: str): |
||||||
|
m_name = 'program.' + namespace + '.' + class_name |
||||||
|
module = __import__(m_name, None, None, [m_name]) |
||||||
|
return module |
||||||
|
|
||||||
|
|
||||||
|
def run(namespace: str, class_name: str, lm: Litsimaja, logger, args: [] = None): |
||||||
|
module = resolve(namespace, class_name) |
||||||
|
loaded_class = getattr(module, class_name) |
||||||
|
program = loaded_class(lm) |
||||||
|
logger.info('Loaded "' + module.name() + '" from ' + namespace + '.' + class_name + ' with args: ' + repr(args)) |
||||||
|
lm.add_loop(program.get_loop()) |
||||||
|
program.run(args) |
||||||
|
|
||||||
|
|
||||||
|
def list_all(grouped: bool = False): |
||||||
|
all_programs = [] |
||||||
|
programs = [] |
||||||
|
for entry in scandir('program'): |
||||||
|
if entry.is_dir(): |
||||||
|
for file in scandir('program/' + entry.name): |
||||||
|
if file.is_file() and file.name[-3:] == '.py': |
||||||
|
try: |
||||||
|
program_name = file.name[: -3] |
||||||
|
module = resolve(entry.name, program_name) |
||||||
|
if hasattr(module, program_name) and issubclass(getattr(module, program_name), Program): |
||||||
|
programs.append({'prg': entry.name + '.' + program_name, 'name': module.name()}) |
||||||
|
except AttributeError: |
||||||
|
continue |
||||||
|
if grouped and len(programs) > 0: |
||||||
|
all_programs.append({'group': entry.name, 'programs': programs}) |
||||||
|
programs = [] |
||||||
|
if not grouped: |
||||||
|
all_programs = programs |
||||||
|
return all_programs |
@ -0,0 +1,13 @@ |
|||||||
|
from lib.Program import Program |
||||||
|
|
||||||
|
|
||||||
|
def name(): |
||||||
|
return 'Static color' |
||||||
|
|
||||||
|
|
||||||
|
class Static(Program): |
||||||
|
def run(self, args: [] = None) -> None: |
||||||
|
color_arr = args['color'] |
||||||
|
for i in range(self._lm.count_pixels()): |
||||||
|
self._lm.set_pixel_color(i, (color_arr[0] << 16) | (color_arr[1] << 8) | color_arr[2]) |
||||||
|
self._lm.show() |
@ -0,0 +1,37 @@ |
|||||||
|
from lib.Program import Program |
||||||
|
from lib.Litsimaja import Litsimaja |
||||||
|
from rpi_ws281x import Color |
||||||
|
|
||||||
|
|
||||||
|
def name(): |
||||||
|
return 'Wipey wipe' |
||||||
|
|
||||||
|
|
||||||
|
def color_wipe(lm: Litsimaja, color): |
||||||
|
for i in range(lm.count_pixels()): |
||||||
|
lm.set_pixel_color(i, color) |
||||||
|
if i % 4 == 0: |
||||||
|
lm.show() |
||||||
|
lm.show() |
||||||
|
|
||||||
|
|
||||||
|
class Wipes(Program): |
||||||
|
def run(self, args=None): |
||||||
|
loop = False |
||||||
|
if 'loop' in args and args['loop']: |
||||||
|
loop = args['loop'] |
||||||
|
if 'color' in args: |
||||||
|
end = Color(args['color'][0], args['color'][1], args['color'][2]) |
||||||
|
else: |
||||||
|
end = Color(100, 100, 50) |
||||||
|
|
||||||
|
r = Color(255, 0, 0) |
||||||
|
g = Color(0, 255, 0) |
||||||
|
b = Color(0, 0, 255) |
||||||
|
while self.get_loop().status(): |
||||||
|
color_wipe(self._lm, r) |
||||||
|
color_wipe(self._lm, g) |
||||||
|
color_wipe(self._lm, b) |
||||||
|
color_wipe(self._lm, end) |
||||||
|
if not loop: |
||||||
|
break |
@ -0,0 +1,57 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
import logging |
||||||
|
import sys |
||||||
|
import json |
||||||
|
|
||||||
|
import lib.ProgramLoading as Pl |
||||||
|
from lib.Litsimaja import Litsimaja |
||||||
|
from flask import Flask, request, Response, render_template |
||||||
|
|
||||||
|
# logging |
||||||
|
logger = logging.getLogger('litsimaja') |
||||||
|
logger.setLevel(logging.DEBUG) |
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') |
||||||
|
file_handler = logging.FileHandler('litsimaja.log') |
||||||
|
file_handler.setFormatter(formatter) |
||||||
|
logger.addHandler(file_handler) |
||||||
|
stdout_handler = logging.StreamHandler(sys.stdout) |
||||||
|
stdout_handler.setFormatter(formatter) |
||||||
|
logger.addHandler(stdout_handler) |
||||||
|
|
||||||
|
# start litsimaja |
||||||
|
lm = Litsimaja() |
||||||
|
app = Flask(__name__) |
||||||
|
# Pl.run('siinus', 'Static', lm, logger, {'color': [50, 50, 50]}) |
||||||
|
|
||||||
|
|
||||||
|
@app.route('/', methods=['GET']) |
||||||
|
def respondroot(): |
||||||
|
return render_template('index.html', programs=Pl.list_all(True)) |
||||||
|
|
||||||
|
|
||||||
|
@app.route('/jscolor.js', methods=['GET']) |
||||||
|
def respondjs(): |
||||||
|
return render_template('jscolor.js') |
||||||
|
|
||||||
|
|
||||||
|
@app.route('/run') |
||||||
|
def run(): |
||||||
|
Pl.run('siinus', 'MyProgram', lm, logger, {'color': [50, 50, 50]}) |
||||||
|
return Response(status=200) |
||||||
|
|
||||||
|
|
||||||
|
@app.route('/crash', methods=['GET']) |
||||||
|
def crash(): |
||||||
|
lm.clear_loops() |
||||||
|
return Response(status=200) |
||||||
|
|
||||||
|
|
||||||
|
@app.route('/program/<program>', methods=['POST']) |
||||||
|
def run_program(program): |
||||||
|
args = request.get_json(force=True) |
||||||
|
prg = program.split('.') |
||||||
|
Pl.run(prg[0], prg[1], lm, logger, args) |
||||||
|
return Response(status=200) |
||||||
|
|
||||||
|
|
||||||
|
app.run('0.0.0.0', 8080) |
@ -0,0 +1,103 @@ |
|||||||
|
<html> |
||||||
|
<head> |
||||||
|
<title>litsimaja</title> |
||||||
|
<script src="jscolor.js"></script> |
||||||
|
|
||||||
|
<script type="text/javascript"> |
||||||
|
jscolor.presets.default = { |
||||||
|
format:'rgb', previewPosition:'right', previewSize:700, width:800, |
||||||
|
height:400, sliderSize:50 |
||||||
|
}; |
||||||
|
|
||||||
|
function show_loading(visible) |
||||||
|
{ |
||||||
|
document.getElementById('loading').style.visibility = visible ? 'visible' : 'hidden'; |
||||||
|
} |
||||||
|
|
||||||
|
function send_get(url) |
||||||
|
{ |
||||||
|
show_loading(true); |
||||||
|
var http = new XMLHttpRequest(); |
||||||
|
http.open('GET', url, true); |
||||||
|
http.onreadystatechange = function() { |
||||||
|
if (http.readyState == 4 && http.status == 200) { |
||||||
|
show_loading(false); |
||||||
|
} |
||||||
|
} |
||||||
|
http.send(); |
||||||
|
} |
||||||
|
|
||||||
|
function send_post(url, data) |
||||||
|
{ |
||||||
|
show_loading(true); |
||||||
|
var http = new XMLHttpRequest(); |
||||||
|
http.open('POST', url, true); |
||||||
|
http.setRequestHeader('Content-Type', 'application/json'); |
||||||
|
http.onreadystatechange = function() { |
||||||
|
if (http.readyState == 4 && http.status == 200) { |
||||||
|
show_loading(false); |
||||||
|
} |
||||||
|
} |
||||||
|
let json = JSON.stringify(data); |
||||||
|
http.send(json); |
||||||
|
} |
||||||
|
|
||||||
|
function getProgramSelection() |
||||||
|
{ |
||||||
|
let select = document.getElementById('program'); |
||||||
|
return select.value; |
||||||
|
} |
||||||
|
|
||||||
|
function isLoop() |
||||||
|
{ |
||||||
|
let looping = document.getElementById('looping'); |
||||||
|
return looping.checked; |
||||||
|
} |
||||||
|
|
||||||
|
function isChanged(value) |
||||||
|
{ |
||||||
|
let rgb = value.split('(')[1].split(')')[0].split(','); |
||||||
|
let data = {color: [Number(rgb[0]), Number(rgb[1]), Number(rgb[2])], loop: isLoop()}; |
||||||
|
send_post('program/' + getProgramSelection(), data); |
||||||
|
} |
||||||
|
|
||||||
|
function doInit() |
||||||
|
{ |
||||||
|
send_post('/program/siinus/Wipes', {color: [100, 50, 0]}) |
||||||
|
} |
||||||
|
|
||||||
|
function doBlind() |
||||||
|
{ |
||||||
|
send_post('/program/siinus/Static', {color: [0, 0, 0]}) |
||||||
|
} |
||||||
|
|
||||||
|
function doCancel() |
||||||
|
{ |
||||||
|
send_get('/crash'); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
|
||||||
|
<button onclick="doInit()">init</button> |
||||||
|
<button onclick="doCancel()">end loop</button> |
||||||
|
<button onclick="doBlind()">off</button> |
||||||
|
<label><input type="checkbox" id="looping"/>loop</label> |
||||||
|
<div id='loading' style="visibility: hidden">LOADING!</div> |
||||||
|
<select id="program" size="10"> |
||||||
|
{% for group in programs %} |
||||||
|
<optgroup label="{{ group.group }}"> |
||||||
|
{% for program in group.programs %} |
||||||
|
<option label="{{ program.name }}">{{ program.prg }}</option> |
||||||
|
{% endfor %} |
||||||
|
</optgroup> |
||||||
|
{% endfor %} |
||||||
|
</select> |
||||||
|
<input id="rgbinput" data-jscolor="" onchange="isChanged(this.value)"> |
||||||
|
|
||||||
|
</body> |
||||||
|
</html> |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue