Add mock LED device type for testing without hardware
Virtual device with configurable LED count, RGB/RGBW mode, and simulated send latency. Includes full provider/client implementation, API schema support, and frontend add/settings modal integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -74,6 +74,10 @@ export function isSerialDevice(type) {
|
||||
return type === 'adalight' || type === 'ambiled';
|
||||
}
|
||||
|
||||
export function isMockDevice(type) {
|
||||
return type === 'mock';
|
||||
}
|
||||
|
||||
export function handle401Error() {
|
||||
if (!apiKey) return; // Already handled or no session
|
||||
localStorage.removeItem('wled_api_key');
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
_discoveryScanRunning, set_discoveryScanRunning,
|
||||
_discoveryCache, set_discoveryCache,
|
||||
} from '../core/state.js';
|
||||
import { API_BASE, fetchWithAuth, isSerialDevice, escapeHtml } from '../core/api.js';
|
||||
import { API_BASE, fetchWithAuth, isSerialDevice, isMockDevice, escapeHtml } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
@@ -23,6 +23,8 @@ class AddDeviceModal extends Modal {
|
||||
serialPort: document.getElementById('device-serial-port').value,
|
||||
ledCount: document.getElementById('device-led-count').value,
|
||||
baudRate: document.getElementById('device-baud-rate').value,
|
||||
ledType: document.getElementById('device-led-type')?.value || 'rgb',
|
||||
sendLatency: document.getElementById('device-send-latency')?.value || '0',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -37,16 +39,29 @@ export function onDeviceTypeChanged() {
|
||||
const serialSelect = document.getElementById('device-serial-port');
|
||||
const ledCountGroup = document.getElementById('device-led-count-group');
|
||||
const discoverySection = document.getElementById('discovery-section');
|
||||
|
||||
const baudRateGroup = document.getElementById('device-baud-rate-group');
|
||||
const ledTypeGroup = document.getElementById('device-led-type-group');
|
||||
const sendLatencyGroup = document.getElementById('device-send-latency-group');
|
||||
|
||||
if (isSerialDevice(deviceType)) {
|
||||
if (isMockDevice(deviceType)) {
|
||||
urlGroup.style.display = 'none';
|
||||
urlInput.removeAttribute('required');
|
||||
serialGroup.style.display = 'none';
|
||||
serialSelect.removeAttribute('required');
|
||||
ledCountGroup.style.display = '';
|
||||
baudRateGroup.style.display = 'none';
|
||||
if (ledTypeGroup) ledTypeGroup.style.display = '';
|
||||
if (sendLatencyGroup) sendLatencyGroup.style.display = '';
|
||||
if (discoverySection) discoverySection.style.display = 'none';
|
||||
} else if (isSerialDevice(deviceType)) {
|
||||
urlGroup.style.display = 'none';
|
||||
urlInput.removeAttribute('required');
|
||||
serialGroup.style.display = '';
|
||||
serialSelect.setAttribute('required', '');
|
||||
ledCountGroup.style.display = '';
|
||||
baudRateGroup.style.display = '';
|
||||
if (ledTypeGroup) ledTypeGroup.style.display = 'none';
|
||||
if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
|
||||
// Hide discovery list — serial port dropdown replaces it
|
||||
if (discoverySection) discoverySection.style.display = 'none';
|
||||
// Populate from cache or show placeholder (lazy-load on focus)
|
||||
@@ -68,6 +83,8 @@ export function onDeviceTypeChanged() {
|
||||
serialSelect.removeAttribute('required');
|
||||
ledCountGroup.style.display = 'none';
|
||||
baudRateGroup.style.display = 'none';
|
||||
if (ledTypeGroup) ledTypeGroup.style.display = 'none';
|
||||
if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
|
||||
// Show cached results or trigger scan for WLED
|
||||
if (deviceType in _discoveryCache) {
|
||||
_renderDiscoveryList();
|
||||
@@ -287,12 +304,19 @@ export async function handleAddDevice(event) {
|
||||
|
||||
const name = document.getElementById('device-name').value.trim();
|
||||
const deviceType = document.getElementById('device-type')?.value || 'wled';
|
||||
const url = isSerialDevice(deviceType)
|
||||
? document.getElementById('device-serial-port').value
|
||||
: document.getElementById('device-url').value.trim();
|
||||
const error = document.getElementById('add-device-error');
|
||||
|
||||
if (!name || !url) {
|
||||
let url;
|
||||
if (isMockDevice(deviceType)) {
|
||||
const ledCount = document.getElementById('device-led-count')?.value || '60';
|
||||
url = `mock://${ledCount}`;
|
||||
} else if (isSerialDevice(deviceType)) {
|
||||
url = document.getElementById('device-serial-port').value;
|
||||
} else {
|
||||
url = document.getElementById('device-url').value.trim();
|
||||
}
|
||||
|
||||
if (!name || (!isMockDevice(deviceType) && !url)) {
|
||||
error.textContent = 'Please fill in all fields';
|
||||
error.style.display = 'block';
|
||||
return;
|
||||
@@ -308,6 +332,12 @@ export async function handleAddDevice(event) {
|
||||
if (isSerialDevice(deviceType) && baudRateSelect && baudRateSelect.value) {
|
||||
body.baud_rate = parseInt(baudRateSelect.value, 10);
|
||||
}
|
||||
if (isMockDevice(deviceType)) {
|
||||
const sendLatency = document.getElementById('device-send-latency')?.value;
|
||||
if (sendLatency) body.send_latency_ms = parseInt(sendLatency, 10);
|
||||
const ledType = document.getElementById('device-led-type')?.value;
|
||||
body.rgbw = ledType === 'rgbw';
|
||||
}
|
||||
const lastTemplateId = localStorage.getItem('lastCaptureTemplateId');
|
||||
if (lastTemplateId) {
|
||||
body.capture_template_id = lastTemplateId;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import {
|
||||
_deviceBrightnessCache, updateDeviceBrightness,
|
||||
} from '../core/state.js';
|
||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, isSerialDevice } from '../core/api.js';
|
||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, isSerialDevice, isMockDevice } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { showToast, showConfirm } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
@@ -23,10 +23,16 @@ class DeviceSettingsModal extends Modal {
|
||||
state_check_interval: this.$('settings-health-interval').value,
|
||||
auto_shutdown: this.$('settings-auto-shutdown').checked,
|
||||
led_count: this.$('settings-led-count').value,
|
||||
led_type: document.getElementById('settings-led-type')?.value || 'rgb',
|
||||
send_latency: document.getElementById('settings-send-latency')?.value || '0',
|
||||
};
|
||||
}
|
||||
|
||||
_getUrl() {
|
||||
if (isMockDevice(this.deviceType)) {
|
||||
const ledCount = this.$('settings-led-count')?.value || '60';
|
||||
return `mock://${ledCount}`;
|
||||
}
|
||||
if (isSerialDevice(this.deviceType)) {
|
||||
return this.$('settings-serial-port').value;
|
||||
}
|
||||
@@ -166,9 +172,14 @@ export async function showSettings(deviceId) {
|
||||
document.getElementById('settings-device-name').value = device.name;
|
||||
document.getElementById('settings-health-interval').value = 30;
|
||||
|
||||
const isMock = isMockDevice(device.device_type);
|
||||
const urlGroup = document.getElementById('settings-url-group');
|
||||
const serialGroup = document.getElementById('settings-serial-port-group');
|
||||
if (isAdalight) {
|
||||
if (isMock) {
|
||||
urlGroup.style.display = 'none';
|
||||
document.getElementById('settings-device-url').removeAttribute('required');
|
||||
serialGroup.style.display = 'none';
|
||||
} else if (isAdalight) {
|
||||
urlGroup.style.display = 'none';
|
||||
document.getElementById('settings-device-url').removeAttribute('required');
|
||||
serialGroup.style.display = '';
|
||||
@@ -202,6 +213,23 @@ export async function showSettings(deviceId) {
|
||||
baudRateGroup.style.display = 'none';
|
||||
}
|
||||
|
||||
// Mock-specific fields
|
||||
const ledTypeGroup = document.getElementById('settings-led-type-group');
|
||||
const sendLatencyGroup = document.getElementById('settings-send-latency-group');
|
||||
if (isMock) {
|
||||
if (ledTypeGroup) {
|
||||
ledTypeGroup.style.display = '';
|
||||
document.getElementById('settings-led-type').value = device.rgbw ? 'rgbw' : 'rgb';
|
||||
}
|
||||
if (sendLatencyGroup) {
|
||||
sendLatencyGroup.style.display = '';
|
||||
document.getElementById('settings-send-latency').value = device.send_latency_ms || 0;
|
||||
}
|
||||
} else {
|
||||
if (ledTypeGroup) ledTypeGroup.style.display = 'none';
|
||||
if (sendLatencyGroup) sendLatencyGroup.style.display = 'none';
|
||||
}
|
||||
|
||||
document.getElementById('settings-auto-shutdown').checked = !!device.auto_shutdown;
|
||||
settingsModal.snapshot();
|
||||
settingsModal.open();
|
||||
@@ -241,6 +269,12 @@ export async function saveDeviceSettings() {
|
||||
const baudVal = document.getElementById('settings-baud-rate').value;
|
||||
if (baudVal) body.baud_rate = parseInt(baudVal, 10);
|
||||
}
|
||||
if (isMockDevice(settingsModal.deviceType)) {
|
||||
const sendLatency = document.getElementById('settings-send-latency')?.value;
|
||||
if (sendLatency !== undefined) body.send_latency_ms = parseInt(sendLatency, 10);
|
||||
const ledType = document.getElementById('settings-led-type')?.value;
|
||||
body.rgbw = ledType === 'rgbw';
|
||||
}
|
||||
const deviceResponse = await fetchWithAuth(`/devices/${deviceId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(body)
|
||||
|
||||
@@ -122,6 +122,10 @@
|
||||
"device.led_count_manual.hint": "Number of LEDs on the strip (must match your Arduino sketch)",
|
||||
"device.baud_rate": "Baud Rate:",
|
||||
"device.baud_rate.hint": "Serial communication speed. Higher = more FPS but requires matching Arduino sketch.",
|
||||
"device.led_type": "LED Type:",
|
||||
"device.led_type.hint": "RGB (3 channels) or RGBW (4 channels with dedicated white)",
|
||||
"device.send_latency": "Send Latency (ms):",
|
||||
"device.send_latency.hint": "Simulated network/serial delay per frame in milliseconds",
|
||||
"device.url.hint": "IP address or hostname of the device (e.g. http://192.168.1.100)",
|
||||
"device.name": "Device Name:",
|
||||
"device.name.placeholder": "Living Room TV",
|
||||
|
||||
@@ -122,6 +122,10 @@
|
||||
"device.led_count_manual.hint": "Количество светодиодов на ленте (должно совпадать с вашим скетчем Arduino)",
|
||||
"device.baud_rate": "Скорость порта:",
|
||||
"device.baud_rate.hint": "Скорость серийного соединения. Выше = больше FPS, но требует соответствия скетчу Arduino.",
|
||||
"device.led_type": "Тип LED:",
|
||||
"device.led_type.hint": "RGB (3 канала) или RGBW (4 канала с выделенным белым)",
|
||||
"device.send_latency": "Задержка отправки (мс):",
|
||||
"device.send_latency.hint": "Имитация сетевой/серийной задержки на кадр в миллисекундах",
|
||||
"device.url.hint": "IP адрес или имя хоста устройства (напр. http://192.168.1.100)",
|
||||
"device.name": "Имя Устройства:",
|
||||
"device.name.placeholder": "ТВ в Гостиной",
|
||||
|
||||
Reference in New Issue
Block a user