Add Daylight Cycle value source type
New value source that outputs brightness (0-1) based on the daylight color LUT, computing BT.601 luminance from the simulated sky color. Supports real-time wall-clock mode or configurable simulation speed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,7 @@ import {
|
||||
import {
|
||||
showValueSourceModal, closeValueSourceModal, saveValueSource,
|
||||
editValueSource, cloneValueSource, deleteValueSource, onValueSourceTypeChange,
|
||||
onDaylightVSRealTimeChange,
|
||||
addSchedulePoint,
|
||||
testValueSource, closeTestValueSourceModal,
|
||||
} from './features/value-sources.js';
|
||||
@@ -409,6 +410,7 @@ Object.assign(window, {
|
||||
cloneValueSource,
|
||||
deleteValueSource,
|
||||
onValueSourceTypeChange,
|
||||
onDaylightVSRealTimeChange,
|
||||
addSchedulePoint,
|
||||
testValueSource,
|
||||
closeTestValueSourceModal,
|
||||
|
||||
@@ -30,6 +30,7 @@ const _colorStripTypeIcons = {
|
||||
const _valueSourceTypeIcons = {
|
||||
static: _svg(P.layoutDashboard), animated: _svg(P.refreshCw), audio: _svg(P.music),
|
||||
adaptive_time: _svg(P.clock), adaptive_scene: _svg(P.cloudSun),
|
||||
daylight: _svg(P.sun),
|
||||
};
|
||||
const _audioSourceTypeIcons = { mono: _svg(P.mic), multichannel: _svg(P.volume2) };
|
||||
const _deviceTypeIcons = {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { Modal } from '../core/modal.js';
|
||||
import {
|
||||
getValueSourceIcon, getAudioSourceIcon, getPictureSourceIcon,
|
||||
ICON_CLONE, ICON_EDIT, ICON_TEST,
|
||||
ICON_LED_PREVIEW, ICON_ACTIVITY, ICON_TIMER, ICON_MOVE_VERTICAL,
|
||||
ICON_LED_PREVIEW, ICON_ACTIVITY, ICON_TIMER, ICON_MOVE_VERTICAL, ICON_CLOCK,
|
||||
ICON_MUSIC, ICON_TRENDING_UP, ICON_MAP_PIN, ICON_MONITOR, ICON_REFRESH,
|
||||
} from '../core/icons.js';
|
||||
import { wrapCard } from '../core/card-colors.js';
|
||||
@@ -64,6 +64,9 @@ class ValueSourceModal extends Modal {
|
||||
sceneSensitivity: document.getElementById('value-source-scene-sensitivity').value,
|
||||
sceneSmoothing: document.getElementById('value-source-scene-smoothing').value,
|
||||
schedule: JSON.stringify(_getScheduleFromUI()),
|
||||
daylightSpeed: document.getElementById('value-source-daylight-speed').value,
|
||||
daylightRealTime: document.getElementById('value-source-daylight-real-time').checked,
|
||||
daylightLatitude: document.getElementById('value-source-daylight-latitude').value,
|
||||
tags: JSON.stringify(_vsTagsInput ? _vsTagsInput.getValue() : []),
|
||||
};
|
||||
}
|
||||
@@ -97,7 +100,7 @@ function _autoGenerateVSName() {
|
||||
|
||||
/* ── Icon-grid type selector ──────────────────────────────────── */
|
||||
|
||||
const VS_TYPE_KEYS = ['static', 'animated', 'audio', 'adaptive_time', 'adaptive_scene'];
|
||||
const VS_TYPE_KEYS = ['static', 'animated', 'audio', 'adaptive_time', 'adaptive_scene', 'daylight'];
|
||||
|
||||
function _buildVSTypeItems() {
|
||||
return VS_TYPE_KEYS.map(key => ({
|
||||
@@ -213,6 +216,13 @@ export async function showValueSourceModal(editData) {
|
||||
_setSlider('value-source-scene-smoothing', editData.smoothing ?? 0.3);
|
||||
_setSlider('value-source-adaptive-min-value', editData.min_value ?? 0);
|
||||
_setSlider('value-source-adaptive-max-value', editData.max_value ?? 1);
|
||||
} else if (editData.source_type === 'daylight') {
|
||||
_setSlider('value-source-daylight-speed', editData.speed ?? 1.0);
|
||||
document.getElementById('value-source-daylight-real-time').checked = !!editData.use_real_time;
|
||||
_setSlider('value-source-daylight-latitude', editData.latitude ?? 50);
|
||||
_syncDaylightVSSpeedVisibility();
|
||||
_setSlider('value-source-adaptive-min-value', editData.min_value ?? 0);
|
||||
_setSlider('value-source-adaptive-max-value', editData.max_value ?? 1);
|
||||
}
|
||||
} else {
|
||||
document.getElementById('value-source-name').value = '';
|
||||
@@ -240,6 +250,11 @@ export async function showValueSourceModal(editData) {
|
||||
_setSlider('value-source-scene-smoothing', 0.3);
|
||||
_setSlider('value-source-adaptive-min-value', 0);
|
||||
_setSlider('value-source-adaptive-max-value', 1);
|
||||
// Daylight defaults
|
||||
_setSlider('value-source-daylight-speed', 1.0);
|
||||
document.getElementById('value-source-daylight-real-time').checked = false;
|
||||
_setSlider('value-source-daylight-latitude', 50);
|
||||
_syncDaylightVSSpeedVisibility();
|
||||
_autoGenerateVSName();
|
||||
}
|
||||
|
||||
@@ -271,8 +286,9 @@ export function onValueSourceTypeChange() {
|
||||
if (type === 'audio') _ensureAudioModeIconSelect();
|
||||
document.getElementById('value-source-adaptive-time-section').style.display = type === 'adaptive_time' ? '' : 'none';
|
||||
document.getElementById('value-source-adaptive-scene-section').style.display = type === 'adaptive_scene' ? '' : 'none';
|
||||
document.getElementById('value-source-daylight-section').style.display = type === 'daylight' ? '' : 'none';
|
||||
document.getElementById('value-source-adaptive-range-section').style.display =
|
||||
(type === 'adaptive_time' || type === 'adaptive_scene') ? '' : 'none';
|
||||
(type === 'adaptive_time' || type === 'adaptive_scene' || type === 'daylight') ? '' : 'none';
|
||||
|
||||
// Populate audio dropdown when switching to audio type
|
||||
if (type === 'audio') {
|
||||
@@ -290,6 +306,17 @@ export function onValueSourceTypeChange() {
|
||||
_autoGenerateVSName();
|
||||
}
|
||||
|
||||
// ── Daylight helpers ──────────────────────────────────────────
|
||||
|
||||
export function onDaylightVSRealTimeChange() {
|
||||
_syncDaylightVSSpeedVisibility();
|
||||
}
|
||||
|
||||
function _syncDaylightVSSpeedVisibility() {
|
||||
const rt = document.getElementById('value-source-daylight-real-time').checked;
|
||||
document.getElementById('value-source-daylight-speed-group').style.display = rt ? 'none' : '';
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────
|
||||
|
||||
export async function saveValueSource() {
|
||||
@@ -338,6 +365,12 @@ export async function saveValueSource() {
|
||||
payload.smoothing = parseFloat(document.getElementById('value-source-scene-smoothing').value);
|
||||
payload.min_value = parseFloat(document.getElementById('value-source-adaptive-min-value').value);
|
||||
payload.max_value = parseFloat(document.getElementById('value-source-adaptive-max-value').value);
|
||||
} else if (sourceType === 'daylight') {
|
||||
payload.speed = parseFloat(document.getElementById('value-source-daylight-speed').value);
|
||||
payload.use_real_time = document.getElementById('value-source-daylight-real-time').checked;
|
||||
payload.latitude = parseFloat(document.getElementById('value-source-daylight-latitude').value);
|
||||
payload.min_value = parseFloat(document.getElementById('value-source-adaptive-min-value').value);
|
||||
payload.max_value = parseFloat(document.getElementById('value-source-adaptive-max-value').value);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -632,6 +665,13 @@ export function createValueSourceCard(src) {
|
||||
<span class="stream-card-prop">${ICON_MAP_PIN} ${pts} ${t('value_source.schedule.points')}</span>
|
||||
<span class="stream-card-prop">${ICON_MOVE_VERTICAL} ${src.min_value ?? 0}–${src.max_value ?? 1}</span>
|
||||
`;
|
||||
} else if (src.source_type === 'daylight') {
|
||||
if (src.use_real_time) {
|
||||
propsHtml = `<span class="stream-card-prop">${ICON_CLOCK} ${t('value_source.daylight.real_time')}</span>`;
|
||||
} else {
|
||||
propsHtml = `<span class="stream-card-prop">${ICON_TIMER} ${t('value_source.daylight.speed_label')} ${src.speed ?? 1.0}x</span>`;
|
||||
}
|
||||
propsHtml += `<span class="stream-card-prop">${ICON_MOVE_VERTICAL} ${src.min_value ?? 0}\u2013${src.max_value ?? 1}</span>`;
|
||||
} else if (src.source_type === 'adaptive_scene') {
|
||||
const ps = _cachedStreams.find(s => s.id === src.picture_source_id);
|
||||
const psName = ps ? ps.name : (src.picture_source_id || '-');
|
||||
|
||||
@@ -1101,6 +1101,17 @@
|
||||
"value_source.type.adaptive_time.desc": "Adjusts by time of day",
|
||||
"value_source.type.adaptive_scene": "Adaptive (Scene)",
|
||||
"value_source.type.adaptive_scene.desc": "Adjusts by scene content",
|
||||
"value_source.type.daylight": "Daylight Cycle",
|
||||
"value_source.type.daylight.desc": "Brightness follows day/night cycle",
|
||||
"value_source.daylight.speed": "Speed:",
|
||||
"value_source.daylight.speed.hint": "Cycle speed multiplier. 1.0 = full day/night cycle in ~4 minutes. Higher values cycle faster.",
|
||||
"value_source.daylight.use_real_time": "Use Real Time:",
|
||||
"value_source.daylight.use_real_time.hint": "When enabled, brightness follows the actual time of day. Speed is ignored.",
|
||||
"value_source.daylight.enable_real_time": "Follow wall clock",
|
||||
"value_source.daylight.latitude": "Latitude:",
|
||||
"value_source.daylight.latitude.hint": "Your geographic latitude (-90 to 90). Affects sunrise/sunset timing in real-time mode.",
|
||||
"value_source.daylight.real_time": "Real-time",
|
||||
"value_source.daylight.speed_label": "Speed",
|
||||
"value_source.value": "Value:",
|
||||
"value_source.value.hint": "Constant output value (0.0 = off, 1.0 = full brightness)",
|
||||
"value_source.waveform": "Waveform:",
|
||||
|
||||
@@ -1101,6 +1101,17 @@
|
||||
"value_source.type.adaptive_time.desc": "Подстройка по времени суток",
|
||||
"value_source.type.adaptive_scene": "Адаптивный (Сцена)",
|
||||
"value_source.type.adaptive_scene.desc": "Подстройка по содержимому сцены",
|
||||
"value_source.type.daylight": "Дневной цикл",
|
||||
"value_source.type.daylight.desc": "Яркость следует за циклом дня/ночи",
|
||||
"value_source.daylight.speed": "Скорость:",
|
||||
"value_source.daylight.speed.hint": "Множитель скорости цикла. 1.0 = полный цикл день/ночь за ~4 минуты.",
|
||||
"value_source.daylight.use_real_time": "Реальное время:",
|
||||
"value_source.daylight.use_real_time.hint": "Яркость следует за реальным временем суток. Скорость игнорируется.",
|
||||
"value_source.daylight.enable_real_time": "Следовать за часами",
|
||||
"value_source.daylight.latitude": "Широта:",
|
||||
"value_source.daylight.latitude.hint": "Географическая широта (-90 до 90). Влияет на время восхода/заката в режиме реального времени.",
|
||||
"value_source.daylight.real_time": "Реальное время",
|
||||
"value_source.daylight.speed_label": "Скорость",
|
||||
"value_source.value": "Значение:",
|
||||
"value_source.value.hint": "Постоянное выходное значение (0.0 = выкл, 1.0 = полная яркость)",
|
||||
"value_source.waveform": "Форма волны:",
|
||||
|
||||
@@ -1101,6 +1101,17 @@
|
||||
"value_source.type.adaptive_time.desc": "按时间自动调节",
|
||||
"value_source.type.adaptive_scene": "自适应(场景)",
|
||||
"value_source.type.adaptive_scene.desc": "按场景内容调节",
|
||||
"value_source.type.daylight": "日光周期",
|
||||
"value_source.type.daylight.desc": "亮度跟随日夜周期",
|
||||
"value_source.daylight.speed": "速度:",
|
||||
"value_source.daylight.speed.hint": "周期速度倍率。1.0 = 完整日夜周期约4分钟。",
|
||||
"value_source.daylight.use_real_time": "使用实时:",
|
||||
"value_source.daylight.use_real_time.hint": "启用后,亮度跟随实际时间。速度设置将被忽略。",
|
||||
"value_source.daylight.enable_real_time": "跟随系统时钟",
|
||||
"value_source.daylight.latitude": "纬度:",
|
||||
"value_source.daylight.latitude.hint": "地理纬度(-90到90)。影响实时模式下的日出/日落时间。",
|
||||
"value_source.daylight.real_time": "实时",
|
||||
"value_source.daylight.speed_label": "速度",
|
||||
"value_source.value": "值:",
|
||||
"value_source.value.hint": "固定输出值(0.0 = 关闭,1.0 = 最大亮度)",
|
||||
"value_source.waveform": "波形:",
|
||||
|
||||
Reference in New Issue
Block a user