Show max FPS hint in target editor, fix gradient sharing for multi-target
- Add dynamic "Hardware max ≈ N fps" recommendation below FPS slider, computed from LED count (WLED: protocol timing) or baud rate (serial). Reuses shared _computeMaxFps from devices.js with named constants. - Fix gradient looking different across targets sharing the same stream: configure() now uses max LED count across all consumers; _fit_to_device uses np.interp linear interpolation instead of truncate/tile. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -309,11 +309,29 @@ export async function fetchDeviceBrightness(deviceId) {
|
||||
}
|
||||
}
|
||||
|
||||
// FPS hint helpers (shared with device-discovery)
|
||||
// LED protocol timing constants
|
||||
const LED_US_PER_BIT = 1.25; // SK6812/WS2812B bit time (μs)
|
||||
const LED_BITS_PER_PIXEL = 32; // RGBW worst case (4 channels × 8 bits)
|
||||
const LED_US_PER_PIXEL = LED_BITS_PER_PIXEL * LED_US_PER_BIT; // 40μs
|
||||
const LED_RESET_US = 80; // reset/latch pulse (μs)
|
||||
const US_PER_SECOND = 1_000_000;
|
||||
|
||||
// Serial protocol constants
|
||||
const SERIAL_BITS_PER_BYTE = 10; // 8N1: 1 start + 8 data + 1 stop
|
||||
const SERIAL_RGB_BYTES_PER_LED = 3;
|
||||
const ADALIGHT_HEADER_BYTES = 6; // 'Ada' + count_hi + count_lo + checksum
|
||||
const AMBILED_HEADER_BYTES = 1;
|
||||
|
||||
// FPS hint helpers (shared with device-discovery, targets)
|
||||
export function _computeMaxFps(baudRate, ledCount, deviceType) {
|
||||
if (!baudRate || !ledCount || ledCount < 1) return null;
|
||||
const overhead = deviceType === 'ambiled' ? 1 : 6;
|
||||
const bitsPerFrame = (ledCount * 3 + overhead) * 10;
|
||||
if (!ledCount || ledCount < 1) return null;
|
||||
if (deviceType === 'wled') {
|
||||
const frameUs = ledCount * LED_US_PER_PIXEL + LED_RESET_US;
|
||||
return Math.floor(US_PER_SECOND / frameUs);
|
||||
}
|
||||
if (!baudRate) return null;
|
||||
const overhead = deviceType === 'ambiled' ? AMBILED_HEADER_BYTES : ADALIGHT_HEADER_BYTES;
|
||||
const bitsPerFrame = (ledCount * SERIAL_RGB_BYTES_PER_LED + overhead) * SERIAL_BITS_PER_BYTE;
|
||||
return Math.floor(baudRate / bitsPerFrame);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.js'
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast, showConfirm } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
import { createDeviceCard, attachDeviceListeners, fetchDeviceBrightness } from './devices.js';
|
||||
import { createDeviceCard, attachDeviceListeners, fetchDeviceBrightness, _computeMaxFps } from './devices.js';
|
||||
import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from './kc-targets.js';
|
||||
import { createColorStripCard } from './color-strips.js';
|
||||
|
||||
@@ -93,6 +93,23 @@ function _autoGenerateTargetName() {
|
||||
document.getElementById('target-editor-name').value = `${deviceName} \u00b7 ${cssName}`;
|
||||
}
|
||||
|
||||
function _updateFpsRecommendation() {
|
||||
const el = document.getElementById('target-editor-fps-rec');
|
||||
const deviceSelect = document.getElementById('target-editor-device');
|
||||
const device = _targetEditorDevices.find(d => d.id === deviceSelect.value);
|
||||
if (!device || !device.led_count) {
|
||||
el.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const fps = _computeMaxFps(device.baud_rate, device.led_count, device.device_type);
|
||||
if (fps !== null) {
|
||||
el.textContent = t('targets.fps.rec', { fps, leds: device.led_count });
|
||||
el.style.display = '';
|
||||
} else {
|
||||
el.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function _updateStandbyVisibility() {
|
||||
const deviceSelect = document.getElementById('target-editor-device');
|
||||
const standbyGroup = document.getElementById('target-editor-standby-group');
|
||||
@@ -167,12 +184,13 @@ export async function showTargetEditor(targetId = null) {
|
||||
// Auto-name generation
|
||||
_targetNameManuallyEdited = !!targetId;
|
||||
document.getElementById('target-editor-name').oninput = () => { _targetNameManuallyEdited = true; };
|
||||
deviceSelect.onchange = () => { _updateStandbyVisibility(); _autoGenerateTargetName(); };
|
||||
deviceSelect.onchange = () => { _updateStandbyVisibility(); _updateFpsRecommendation(); _autoGenerateTargetName(); };
|
||||
cssSelect.onchange = () => _autoGenerateTargetName();
|
||||
if (!targetId) _autoGenerateTargetName();
|
||||
|
||||
// Show/hide standby interval based on selected device capabilities
|
||||
_updateStandbyVisibility();
|
||||
_updateFpsRecommendation();
|
||||
|
||||
targetEditorModal.snapshot();
|
||||
targetEditorModal.open();
|
||||
|
||||
Reference in New Issue
Block a user