Compare commits
2 Commits
7ccab6fbc4
...
3d6ef5c7b4
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d6ef5c7b4 | |||
| 78a4ce009c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -28,6 +28,7 @@ Thumbs.db
|
||||
scripts/.led-controller-venv
|
||||
docs/.help-print.html
|
||||
settings.json
|
||||
db/
|
||||
*.log
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
12
Pipfile
12
Pipfile
@@ -22,9 +22,11 @@ pytest = "*"
|
||||
python_version = "3.11"
|
||||
|
||||
[scripts]
|
||||
web = "python /home/pi/led-controller/tests/web.py"
|
||||
watch = "python -m watchfiles 'python tests/web.py' src tests"
|
||||
install = "pipenv install"
|
||||
web = "python tests/web.py"
|
||||
watch = "python -m watchfiles \"python tests/web.py\" src tests"
|
||||
run = "sh -c 'cd src && python main.py'"
|
||||
dev = "watchfiles \"sh -c 'cd src && python main.py'\" src"
|
||||
help-pdf = "sh scripts/build_help_pdf.sh"
|
||||
dev = "python -m watchfiles \"sh -c 'cd src && python main.py'\" src"
|
||||
test = "python -m pytest"
|
||||
test-browser = "sh -c 'python tests/web.py > /tmp/led-controller-web.log 2>&1 & pid=$!; trap \"kill $pid\" EXIT; sleep 2; LED_CONTROLLER_RUN_BROWSER_TESTS=1 LED_CONTROLLER_DEVICE_IP=http://127.0.0.1:5000 python -m pytest tests/test_browser.py'"
|
||||
test-browser-device = "sh -c 'LED_CONTROLLER_RUN_BROWSER_TESTS=1 python -m pytest tests/test_browser.py'"
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"188b0e1560a8": {"id": "188b0e1560a8", "name": "led-188b0e1560a8", "type": "led", "transport": "wifi", "address": "10.1.1.192", "default_pattern": null, "zones": []}, "dcb4d99988c8": {"id": "dcb4d99988c8", "name": "outside", "type": "led", "transport": "wifi", "address": "10.1.1.227", "default_pattern": null, "zones": []}}
|
||||
@@ -1 +1 @@
|
||||
{"on": {"min_delay": 10, "max_delay": 10000, "max_colors": 1}, "off": {"min_delay": 10, "max_delay": 10000, "max_colors": 0}, "rainbow": {"n1": "Step Rate", "min_delay": 10, "max_delay": 10000, "max_colors": 0}, "colour_cycle": {"n1": "Step Rate", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "transition": {"min_delay": 10, "max_delay": 10000, "max_colors": 10}, "chase": {"n1": "Colour 1 Length", "n2": "Colour 2 Length", "n3": "Step 1", "n4": "Step 2", "min_delay": 10, "max_delay": 10000, "max_colors": 2}, "pulse": {"n1": "Attack", "n2": "Hold", "n3": "Decay", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "circle": {"n1": "Head Rate", "n2": "Max Length", "n3": "Tail Rate", "n4": "Min Length", "min_delay": 10, "max_delay": 10000, "max_colors": 2}, "blink": {"min_delay": 10, "max_delay": 10000, "max_colors": 10}, "flicker": {"n1": "Min brightness", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "flame": {"n1": "Min brightness", "n2": "Breath period (ms)", "n3": "Spark gap min (ms, 0=default 10\u201330 s, -1=off)", "n4": "Spark gap max (ms)", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "twinkle": {"n1": "Twinkle activity (1\u2013255, higher = more changes)", "n2": "Density (0\u2013255, higher = more of the strip lit)", "n3": "Min adjacent LEDs per twinkle (same as max for fixed length)", "n4": "Max adjacent LEDs per twinkle (same as min for fixed length)", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "radiate": {"n1": "Node spacing (LEDs)", "n2": "Out time (ms)", "n3": "In time (ms)", "min_delay": 10, "max_delay": 10000, "max_colors": 2}, "meteor_rain": {"n1": "Tail length", "n2": "Speed (LEDs per frame)", "n3": "Fade amount (1-255)", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "scanner": {"n1": "Eye width", "n2": "End pause (frames)", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "gradient_scroll": {"n1": "Scroll step rate", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "comet_dual": {"n1": "Tail length", "n2": "Speed", "n3": "Gap", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "sparkle_trail": {"n1": "Spark density", "n2": "Decay", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "wave": {"n1": "Wavelength", "n2": "Amplitude", "n3": "Drift speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "plasma": {"n1": "Scale", "n2": "Speed", "n3": "Contrast", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "segment_chase": {"n1": "Segment size", "n2": "Phase step", "n3": "Segment phase offset", "n4": "Gap per segment", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "bar_graph": {"n1": "Level percent", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "breathing_dual": {"n1": "Phase offset", "n2": "Ease", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "strobe_burst": {"n1": "Burst count", "n2": "Burst gap", "n3": "Cooldown", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "rain_drops": {"n1": "Drop rate", "n2": "Ripple width", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "fireflies": {"n1": "Count", "n2": "Twinkle speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "clock_sweep": {"n1": "Hand width", "n2": "Marker interval", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "marquee": {"n1": "On length", "n2": "Off length", "n3": "Step", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "aurora": {"n1": "Band count", "n2": "Shimmer", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "snowfall": {"n1": "Flake density", "n2": "Fall speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "heartbeat": {"n1": "Pulse 1 ms", "n2": "Pulse 2 ms", "n3": "Pause ms", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "orbit": {"n1": "Orbit count", "n2": "Base speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "palette_morph": {"n1": "Morph ms", "n2": "Warp rate", "n3": "Turbulence", "max_colors": 10, "min_delay": 10, "max_delay": 10000}}
|
||||
{"on": {"min_delay": 10, "max_delay": 10000, "max_colors": 1}, "off": {"min_delay": 10, "max_delay": 10000, "max_colors": 0}, "rainbow": {"n1": "Step Rate", "min_delay": 10, "max_delay": 10000, "max_colors": 0}, "colour_cycle": {"n1": "Step Rate", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "transition": {"min_delay": 10, "max_delay": 10000, "max_colors": 10}, "chase": {"n1": "Colour 1 Length", "n2": "Colour 2 Length", "n3": "Step 1", "n4": "Step 2", "min_delay": 10, "max_delay": 10000, "max_colors": 2, "has_background": true}, "pulse": {"n1": "Attack", "n2": "Hold", "n3": "Decay", "min_delay": 10, "max_delay": 10000, "max_colors": 10, "has_background": true}, "circle": {"n1": "Head Rate", "n2": "Max Length", "n3": "Tail Rate", "n4": "Min Length", "min_delay": 10, "max_delay": 10000, "max_colors": 2, "has_background": true}, "blink": {"min_delay": 10, "max_delay": 10000, "max_colors": 10, "has_background": true}, "flicker": {"n1": "Min brightness", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "flame": {"n1": "Min brightness", "n2": "Breath period (ms)", "n3": "Spark gap min (ms, 0=default 10–30 s, -1=off)", "n4": "Spark gap max (ms)", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "twinkle": {"n1": "Twinkle activity (1–255, higher = more changes)", "n2": "Density (0–255, higher = more of the strip lit)", "n3": "Min adjacent LEDs per twinkle (same as max for fixed length)", "n4": "Max adjacent LEDs per twinkle (same as min for fixed length)", "min_delay": 10, "max_delay": 10000, "max_colors": 10, "has_background": true}, "radiate": {"n1": "Node spacing (LEDs)", "n2": "Out time (ms)", "n3": "In time (ms)", "min_delay": 10, "max_delay": 10000, "max_colors": 2, "has_background": true}, "meteor_rain": {"n1": "Tail length", "n2": "Speed (LEDs per frame)", "n3": "Fade amount (1-255)", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "scanner": {"n1": "Eye width", "n2": "End pause (frames)", "min_delay": 10, "max_delay": 10000, "max_colors": 10, "has_background": true}, "gradient_scroll": {"n1": "Scroll step rate", "min_delay": 10, "max_delay": 10000, "max_colors": 10}, "comet_dual": {"n1": "Tail length", "n2": "Speed", "n3": "Gap", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "sparkle_trail": {"n1": "Spark density", "n2": "Decay", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "wave": {"n1": "Wavelength", "n2": "Amplitude", "n3": "Drift speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "plasma": {"n1": "Scale", "n2": "Speed", "n3": "Contrast", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "segment_chase": {"n1": "Segment size", "n2": "Phase step", "n3": "Segment phase offset", "n4": "Gap per segment", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "bar_graph": {"n1": "Level percent", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "breathing_dual": {"n1": "Phase offset", "n2": "Ease", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "strobe_burst": {"n1": "Burst count", "n2": "Burst gap", "n3": "Cooldown", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "rain_drops": {"n1": "Drop rate", "n2": "Ripple width", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "fireflies": {"n1": "Count", "n2": "Twinkle speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "clock_sweep": {"n1": "Hand width", "n2": "Marker interval", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "marquee": {"n1": "On length", "n2": "Off length", "n3": "Step", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "aurora": {"n1": "Band count", "n2": "Shimmer", "max_colors": 10, "min_delay": 10, "max_delay": 10000}, "snowfall": {"n1": "Flake density", "n2": "Fall speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "heartbeat": {"n1": "Pulse 1 ms", "n2": "Pulse 2 ms", "n3": "Pause ms", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "orbit": {"n1": "Orbit count", "n2": "Base speed", "max_colors": 10, "min_delay": 10, "max_delay": 10000, "has_background": true}, "palette_morph": {"n1": "Morph ms", "n2": "Warp rate", "n3": "Turbulence", "max_colors": 10, "min_delay": 10, "max_delay": 10000}}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"1": {"name": "default", "names": ["led-188b0e1560a8", "led-f0f5bdfb9d30"], "presets": [["4", "2", "7"], ["3", "14", "5"], ["8", "9", "1"], ["6", "38", "42"], ["39", "40", "41"], ["43", "44", "45"], ["46", "47", "48"], ["49", "50", "51"], ["52", "53", "54"], ["55", "56", "57"], ["58", "59", "60"], ["61", "62"]], "presets_flat": ["4", "2", "7", "3", "14", "5", "8", "9", "1", "6", "38", "42", "39", "40", "41", "43", "44", "45", "46", "47", "48", "49", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62"], "default_preset": "41", "brightness": 23}, "2": {"name": "default", "names": ["1", "2", "3", "4", "5", "6", "7", "8", "0", "a"], "presets": [["16", "17", "18"], ["19", "20", "21"], ["22", "23", "24"], ["25", "26", "27"], ["28", "29", "30"]], "presets_flat": ["16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30"]}, "3": {"name": "default", "names": ["1"], "presets": [], "default_preset": null}, "4": {"name": "default", "names": ["1"], "presets": [], "default_preset": null}, "5": {"name": "dj", "names": ["dj"], "presets": [["31", "32", "33"]], "default_preset": "31", "presets_flat": ["31", "32", "33"]}, "6": {"name": "default", "names": ["1"], "presets": [], "default_preset": null, "presets_flat": []}, "7": {"name": "dj", "names": ["dj"], "presets": [["34", "35", "36"]], "default_preset": "34", "presets_flat": ["34", "35", "36"]}, "8": {"name": "test", "names": ["led-188b0e1560a8"], "presets": [["1", "2", "3"], ["4", "5"]], "default_preset": "1", "presets_flat": ["1", "2", "3", "4", "5"], "brightness": 167}}
|
||||
Submodule led-driver updated: a79c6f4dd3...fbebe9f4f9
@@ -367,6 +367,7 @@ async def create_driver_pattern(request):
|
||||
Body JSON:
|
||||
name, code (required),
|
||||
min_delay, max_delay, max_colors (optional numbers),
|
||||
has_background (optional bool),
|
||||
n1..n8 (optional string labels),
|
||||
overwrite (optional, default true).
|
||||
"""
|
||||
@@ -409,6 +410,9 @@ async def create_driver_pattern(request):
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
if "has_background" in data:
|
||||
meta["has_background"] = bool(data.get("has_background"))
|
||||
|
||||
for i in range(1, 9):
|
||||
nk = "n%d" % i
|
||||
if nk not in data:
|
||||
|
||||
@@ -255,6 +255,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
};
|
||||
|
||||
const patternSupportsBackgroundColor = () => {
|
||||
if (!presetPatternInput || !presetPatternInput.value) {
|
||||
return false;
|
||||
}
|
||||
const pattern = String(presetPatternInput.value).trim();
|
||||
const meta =
|
||||
(cachedPatterns && cachedPatterns[pattern]) ||
|
||||
(cachedPatterns && cachedPatterns[pattern.toLowerCase()]) ||
|
||||
null;
|
||||
return !!(meta && typeof meta === 'object' && meta.has_background === true);
|
||||
};
|
||||
|
||||
const renderPresetColors = (colors, paletteRefs) => {
|
||||
if (!presetColorsContainer) return;
|
||||
|
||||
@@ -296,14 +308,21 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
const swatchContainer = document.createElement('div');
|
||||
swatchContainer.style.cssText = 'display: flex; flex-wrap: wrap; gap: 0.5rem;';
|
||||
swatchContainer.style.cssText = 'display: flex; flex-wrap: nowrap; gap: 0.5rem; align-items: flex-start; overflow-x: auto;';
|
||||
swatchContainer.classList.add('color-swatches-container');
|
||||
|
||||
const showBackgroundLabel = patternSupportsBackgroundColor() && currentPresetColors.length > 1;
|
||||
currentPresetColors.forEach((color, index) => {
|
||||
const isBackgroundColor = showBackgroundLabel && index === currentPresetColors.length - 1;
|
||||
const swatchWrapper = document.createElement('div');
|
||||
swatchWrapper.style.cssText = 'position: relative; display: inline-block;';
|
||||
if (isBackgroundColor) {
|
||||
// Keep the background color swatch at the far right.
|
||||
swatchWrapper.style.marginLeft = 'auto';
|
||||
}
|
||||
swatchWrapper.draggable = true;
|
||||
swatchWrapper.dataset.colorIndex = index;
|
||||
swatchWrapper.dataset.backgroundColor = isBackgroundColor ? '1' : '0';
|
||||
const refAtIndex = currentPresetPaletteRefs[index];
|
||||
swatchWrapper.dataset.paletteRef = Number.isInteger(refAtIndex) ? String(refAtIndex) : '';
|
||||
swatchWrapper.classList.add('draggable-color-swatch');
|
||||
@@ -424,6 +443,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
swatchWrapper.appendChild(swatch);
|
||||
swatchWrapper.appendChild(colorPicker);
|
||||
swatchWrapper.appendChild(removeBtn);
|
||||
if (isBackgroundColor) {
|
||||
const bgLabel = document.createElement('div');
|
||||
bgLabel.textContent = 'Background';
|
||||
bgLabel.style.cssText = `
|
||||
margin-top: 0.25rem;
|
||||
text-align: center;
|
||||
font-size: 0.72rem;
|
||||
color: #cfcfcf;
|
||||
letter-spacing: 0.02em;
|
||||
`;
|
||||
swatchWrapper.appendChild(bgLabel);
|
||||
}
|
||||
swatchContainer.appendChild(swatchWrapper);
|
||||
});
|
||||
|
||||
@@ -445,6 +476,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
e.preventDefault();
|
||||
const dragging = swatchContainer.querySelector('.dragging-color');
|
||||
if (!dragging) return;
|
||||
const backgroundEl = swatchContainer.querySelector('.draggable-color-swatch[data-background-color="1"]');
|
||||
if (backgroundEl) {
|
||||
swatchContainer.appendChild(backgroundEl);
|
||||
}
|
||||
|
||||
// Get new order of colors from DOM
|
||||
const colorElements = [...swatchContainer.querySelectorAll('.draggable-color-swatch')];
|
||||
|
||||
@@ -550,14 +550,14 @@ body.preset-ui-run .edit-mode-only {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Zone preset selecting area: 3 columns, vertical scroll only */
|
||||
/* Zone preset selecting area: 8 columns on desktop, vertical scroll only */
|
||||
#presets-list-zone {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(8, minmax(0, 1fr));
|
||||
grid-auto-rows: 5rem;
|
||||
column-gap: 0.3rem;
|
||||
row-gap: 0.3rem;
|
||||
@@ -1261,8 +1261,8 @@ body.preset-ui-run .edit-mode-only {
|
||||
.color-swatches-container {
|
||||
min-height: 80px;
|
||||
}
|
||||
/* Presets list: 3 columns and vertical scroll (defined above); mobile same */
|
||||
@media (max-width: 1000px) {
|
||||
/* Presets list: 3 columns on phone-sized screens */
|
||||
@media (max-width: 600px) {
|
||||
#presets-list-zone {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 7rem);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
Browser automation tests using Selenium.
|
||||
Tests run against the device in an actual browser. Target host defaults to
|
||||
``192.168.4.1``; override with ``LED_CONTROLLER_DEVICE_IP`` (IP or hostname,
|
||||
``127.0.0.1:5000``; override with ``LED_CONTROLLER_DEVICE_IP`` (IP or hostname,
|
||||
or a full ``http://`` / ``https://`` base URL).
|
||||
|
||||
Fixed delays between UI steps use ``LED_CONTROLLER_BROWSER_SLEEP_SCALE``
|
||||
@@ -49,7 +49,7 @@ from selenium.common.exceptions import (
|
||||
ElementNotInteractableException,
|
||||
)
|
||||
|
||||
_DEFAULT_DEVICE_HOST = "192.168.4.1"
|
||||
_DEFAULT_DEVICE_HOST = "127.0.0.1:5000"
|
||||
|
||||
|
||||
def _device_base_url() -> str:
|
||||
|
||||
Reference in New Issue
Block a user