9 Commits

Author SHA1 Message Date
364ea9971b Veits tihedam rgbt lihtsalt 2022-04-07 19:23:23 +03:00
siinus
33109455c9 Set paths absolute 2021-05-03 19:05:27 +03:00
103e04a1b4 Merge pull request 'dotenv' (#7) from feature/dotenv into master
Reviewed-on: #7
2021-05-03 10:50:23 +00:00
siinus
86dd419080 Pointless dependency 2021-05-03 00:44:35 +03:00
siinus
0a5e1a3a5d Fix readme 2021-05-02 01:45:04 +03:00
siinus
74b6cbe7e3 Create .env for config 2021-05-02 01:37:38 +03:00
e6fb8ea528 Merge pull request 'Update 210319' (#6) from feature/tempo into master
Reviewed-on: #6
2021-03-19 21:16:35 +00:00
siinus
0d59414104 Add program selecting for status update 2021-03-19 23:08:10 +02:00
siinus
3fa0bc376f Replace program selector with radios, because chrome for android sucks 2021-03-19 22:10:42 +02:00
11 changed files with 184 additions and 56 deletions

View File

@@ -8,20 +8,9 @@ Projekt Litsimaja - Lapikute tagatoa seintele programmeeritavad ARGB ribad
#### Running with emulation #### Running with emulation
This is mainly for testing, development. This is mainly for testing, development.
In ``lib/Litsimaja.py`` change the following: Create .env file:
``` ```cp .env.example .env```
# from lib.strip.TkinterStrip import TkinterStrip
def __init__(self): and see: ```USE_EMULATOR```
self._strip = PixelStrip(290, 18, 800000, 10, False, 255, 0, 4104)
```
to
```
from lib.strip.TkinterStrip import TkinterStrip
def __init__(self):
self._strip = TkinterStrip(290, 18, 800000, 10, False, 255, 0, 4104)
```
Now when you run the program, you will see a Tkinter window pop up with a rectangle simulating the LED strip. Now when you run the program, you will see a Tkinter window pop up with a rectangle simulating the LED strip.
Don't commit this change.

17
pyleds/.env.defaults Normal file
View File

@@ -0,0 +1,17 @@
ENVIRONMENT=prod
USE_EMULATOR=false
BIND_ADDR=127.0.0.1
BIND_PORT=8080
# Strip configuration
STRIP_PIXELS=290
STRIP_GPIO_PIN=18
STRIP_HZ=800000
STRIP_DMA=10
STRIP_INVERT=false
STRIP_BRIGHTNESS=255
STRIP_CHANNEL=0
STRIP_TYPE=4104
REGIONS=46,96,191,241
BPM_DEFAULT=60

2
pyleds/.env.example Normal file
View File

@@ -0,0 +1,2 @@
ENVIRONMENT=dev
USE_EMULATOR=true

1
pyleds/.gitignore vendored
View File

@@ -1 +1,2 @@
.env
litsimaja.log litsimaja.log

38
pyleds/lib/Config.py Normal file
View File

@@ -0,0 +1,38 @@
from dotenv import dotenv_values
import pathlib
def populate_values(load: {}):
conf = load
conf['IS_DEV']: bool = str.lower(load['ENVIRONMENT']) == 'dev'
conf['USE_EMULATOR']: bool = str.lower(load['USE_EMULATOR']) == 'true'
conf['BIND_PORT']: int = int(load['BIND_PORT'])
conf['STRIP_PIXELS']: int = int(load['STRIP_PIXELS'])
conf['STRIP_GPIO_PIN']: int = int(load['STRIP_GPIO_PIN'])
conf['STRIP_HZ']: int = int(load['STRIP_HZ'])
conf['STRIP_DMA']: int = int(load['STRIP_DMA'])
conf['STRIP_INVERT']: bool = str.lower(load['STRIP_INVERT']) == 'true'
conf['STRIP_BRIGHTNESS']: int = int(load['STRIP_BRIGHTNESS'])
conf['STRIP_CHANNEL']: int = int(load['STRIP_CHANNEL'])
conf['STRIP_TYPE']: int = int(load['STRIP_TYPE'])
conf['REGIONS']: [] = [int(x) for x in load['REGIONS'].split(',')]
conf['BPM_DEFAULT']: int = int(load['BPM_DEFAULT'])
return conf
class Config(object):
_config: {} = {}
def __init__(self):
rp = str(pathlib.Path(__file__).parent.parent.absolute())
load_conf = {
**dotenv_values(rp + "/.env.defaults"),
**dotenv_values(rp + "/.env"),
}
self._config = populate_values(load_conf)
def get(self, key: str):
return self._config[key]

View File

@@ -1,6 +1,4 @@
from rpi_ws281x import PixelStrip from lib.Config import Config
# from lib.strip.WindowStrip import WindowStrip
# from lib.strip.TkinterStrip import TkinterStrip
from lib.LoopSwitch import LoopSwitch from lib.LoopSwitch import LoopSwitch
from lib.Regions import Regions from lib.Regions import Regions
from lib.Tempo import Tempo from lib.Tempo import Tempo
@@ -8,11 +6,25 @@ from lib.Tempo import Tempo
class Litsimaja(object): class Litsimaja(object):
def __init__(self): def __init__(self):
self._strip = PixelStrip(290, 18, 800000, 10, False, 255, 0, 4104) self._config = Config()
if self.conf('USE_EMULATOR'):
module = __import__('lib.strip.TkinterStrip', None, None, ['TkinterStrip'])
class_name = 'TkinterStrip'
else:
module = __import__('rpi_ws281x', None, None, ['PixelStrip'])
class_name = 'PixelStrip'
loaded_class = getattr(module, class_name)
self._strip = loaded_class(
self.conf('STRIP_PIXELS'), self.conf('STRIP_GPIO_PIN'), self.conf('STRIP_HZ'), self.conf('STRIP_DMA'),
self.conf('STRIP_INVERT'), self.conf('STRIP_BRIGHTNESS'), self.conf('STRIP_CHANNEL'),
self.conf('STRIP_TYPE')
)
self._loops = [] self._loops = []
self._strip.begin() self._strip.begin()
self._regions: Regions = Regions(self.count_pixels(), [46, 96, 191, 241]) self._regions: Regions = Regions(self.count_pixels(), self.conf('REGIONS'))
self._tempo: Tempo = Tempo(60) self._tempo: Tempo = Tempo(self.conf('BPM_DEFAULT'))
self._selected_program = None
def count_pixels(self) -> int: def count_pixels(self) -> int:
return self._strip.numPixels() return self._strip.numPixels()
@@ -47,9 +59,10 @@ class Litsimaja(object):
def build_status_array(self): def build_status_array(self):
data = {'success': True} data = {'success': True}
features = { features = {
'program': self._selected_program,
'tempo': { 'tempo': {
'bpm': self.get_tempo().get_bpm() 'bpm': self.get_tempo().get_bpm()
} },
} }
regions = [] regions = []
for region_id in self._regions.list_region_ids(): for region_id in self._regions.list_region_ids():
@@ -60,3 +73,9 @@ class Litsimaja(object):
def get_tempo(self): def get_tempo(self):
return self._tempo return self._tempo
def set_selected_program(self, program_name: str):
self._selected_program = program_name
def conf(self, key: str):
return self._config.get(key)

View File

@@ -15,6 +15,7 @@ def run(namespace: str, class_name: str, lm: Litsimaja, logger, args: [] = None)
program = loaded_class(lm) program = loaded_class(lm)
logger.info('Loaded "' + module.name() + '" from ' + namespace + '.' + class_name + ' with args: ' + repr(args)) logger.info('Loaded "' + module.name() + '" from ' + namespace + '.' + class_name + ' with args: ' + repr(args))
lm.add_loop(program.get_loop()) lm.add_loop(program.get_loop())
lm.set_selected_program(namespace + '.' + class_name)
program.run(args) program.run(args)

View File

@@ -0,0 +1,35 @@
from lib.Program import Program
from rpi_ws281x import Color
import time
def name():
return 'Vikermasetsus'
def wheel(pos):
"""Generate rainbow colors across 0-255 positions."""
if pos < 85:
return Color(pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return Color(255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return Color(0, pos * 3, 255 - pos * 3)
class Vikermasetsus(Program):
def run(self, args: [] = None):
wait_ms = 20
iterations = 5
while self.get_loop().status():
"""Draw rainbow that uniformly distributes itself across all pixels."""
for j in range(256 * iterations):
if not self.get_loop().status():
break
for i in range(self._lm.count_pixels()):
self._lm.set_pixel_color(i, wheel(
(int(i * 2560 / self._lm.count_pixels()) + j) & 255))
self._lm.show()
time.sleep(wait_ms / 1000.0)

View File

@@ -1,25 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import logging import logging
import pathlib
import sys import sys
import lib.ProgramLoading as Pl import lib.ProgramLoading as Pl
from lib.Litsimaja import Litsimaja from lib.Litsimaja import Litsimaja
from flask import Flask, request, Response, render_template, json from flask import Flask, request, Response, render_template, json
from flask_accept import accept
root_path = pathlib.Path(__file__).parent.absolute()
# start litsimaja
lm = Litsimaja()
# logging # logging
logger = logging.getLogger('litsimaja') logger = logging.getLogger('litsimaja')
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG if lm.conf('IS_DEV') else logging.WARN)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler('litsimaja.log') file_handler = logging.FileHandler(str(root_path) + '/litsimaja.log')
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
logger.addHandler(file_handler) logger.addHandler(file_handler)
stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(formatter) stdout_handler.setFormatter(formatter)
logger.addHandler(stdout_handler) logger.addHandler(stdout_handler)
# start litsimaja
lm = Litsimaja()
app = Flask(__name__, static_url_path='', static_folder='templates') app = Flask(__name__, static_url_path='', static_folder='templates')
Pl.run('siinus', 'Wipes', lm, logger, {'color': [0, 0, 0]}) Pl.run('siinus', 'Wipes', lm, logger, {'color': [0, 0, 0]})
Pl.run('peter', 'DiskoPidu', lm, logger, {}) Pl.run('peter', 'DiskoPidu', lm, logger, {})
@@ -30,7 +31,6 @@ def lm_standard_xhr_response() -> Response:
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
@accept('text/html')
def respond_root(): def respond_root():
return render_template( return render_template(
'index.html', 'index.html',
@@ -41,7 +41,6 @@ def respond_root():
@app.route('/status', methods=['GET']) @app.route('/status', methods=['GET'])
@accept('application/json')
def respond_status(): def respond_status():
return lm_standard_xhr_response() return lm_standard_xhr_response()
@@ -79,4 +78,4 @@ def sync_beat():
return lm_standard_xhr_response() return lm_standard_xhr_response()
app.run('0.0.0.0', 8080) app.run(lm.conf('BIND_ADDR'), lm.conf('BIND_PORT'))

View File

@@ -1,13 +1,3 @@
.section-rgb {
height: 20rem;
text-align: center;
}
select {
width: 100%;
height: calc(100% - 1.5rem);
}
.colorpicker { .colorpicker {
display: inline-block; display: inline-block;
height: 38px; height: 38px;
@@ -36,3 +26,24 @@ button.region {
button.region_off { button.region_off {
background-color: gray !important background-color: gray !important
} }
div.program_select {
height: 300px;
overflow: auto;
border: 1px solid gray;
text-align: left;
}
div.program_select ul {
list-style: none;
border-bottom: 1px solid silver;
margin-bottom: 0;
}
div.program_select ul li {
margin: auto;
}
div.program_select ul li label {
margin: 3px auto;
}

View File

@@ -55,19 +55,29 @@
let status = JSON.parse(json); let status = JSON.parse(json);
document.getElementById('tempo').value = status.features.tempo.bpm; document.getElementById('tempo').value = status.features.tempo.bpm;
let regions = status.features.region; let regions = status.features.region;
let regLen = regions.length; for (let i = 0, len = regions.length; i < len; i++) {
for(let i = 0; i < regLen; i++) {
if (regions[i]) { if (regions[i]) {
document.getElementById('region_' + i).classList.remove('region_off'); document.getElementById('region_' + i).classList.remove('region_off');
} else { } else {
document.getElementById('region_' + i).classList.add('region_off'); document.getElementById('region_' + i).classList.add('region_off');
} }
} }
let programs = document.getElementsByName('program_select');
for (var i = 0, len = programs.length; i < len; i++) {
if (programs[i].value === status.features.program) {
programs[i].checked = true;
}
}
} }
function getProgramSelection() { function getProgramSelection() {
let select = document.getElementById('program'); let radios = document.getElementsByName('program_select');
return select.value; for (var i = 0, length = radios.length; i < length; i++) {
if (radios[i].checked) {
return radios[i].value;
}
}
return null;
} }
function isLoop() { function isLoop() {
@@ -124,19 +134,25 @@
{% endfor %} {% endfor %}
</div> </div>
<div class="row" id='loading' style="visibility: hidden"><b>LOADING!</b></div> <div class="row" id='loading' style="visibility: hidden"><b>LOADING!</b></div>
<div class="section-rgb"> <div class="row">
<div class="row"> <div class="program_select one-half column">
<div class="one-third column"> {% for group in programs %}
<select id="program" size="10"> <ul>
{% for group in programs %} <li>{{ group.group }}</li>
<optgroup label="{{ group.group }}"> {% for program in group.programs %}
{% for program in group.programs %} {% set pr_checked = '' %}
<option value="{{ program.prg }}">{{ program.name }}</option> {% if status.features.program == program.prg %}
{% endfor %} {% set pr_checked = ' checked' %}
</optgroup> {% endif %}
<li>
<label>
<input type="radio" name="program_select" value="{{ program.prg }}"{{ pr_checked }}>
{{ program.name }}
</label>
</li>
{% endfor %} {% endfor %}
</select> </ul>
</div> {% endfor %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">