diff --git a/cmd/server/web/js/app.ts b/cmd/server/web/js/app.ts index 0831602..5b886cb 100644 --- a/cmd/server/web/js/app.ts +++ b/cmd/server/web/js/app.ts @@ -1,26 +1,28 @@ -// app.js (оркестратор) +// app.ts - Оркестратор приложения + import { fetchHealth, fetchHistory } from "./data.js"; import { - state, - addSignals, - resetBufferTracking, - updatePeakHolder, - updateSignalPeakHolder, - setPeakHolderListener + state, + addSignals, + resetBufferTracking, + updatePeakHolder, + updateSignalPeakHolder, + setPeakHolderListener } from "./state.js"; import { - initDOM, - setServerTime, - setStatus, - setMeta, - setBars, - setSignal, - setAudioIndicator, - DOM, - updateAllChannels + initDOM, + setServerTime, + setStatus, + setMeta, + setBars, + setSignal, + setAudioIndicator, + DOM, + setChannelValue, + channelElements } from "./ui.js"; import { initAccordion } from "./accordion.js"; import { drawChart } from "./chart.js"; @@ -37,29 +39,28 @@ let lastSignalLevel: number = 0; // ================= INIT ================= window.addEventListener("DOMContentLoaded", () => { - initDOM(); - initAccordion(); - initResize(); - initCamera(); + initDOM(); + initAccordion(); + initResize(); + initCamera(); - // Инициализация звука при первом клике - document.addEventListener('click', () => { - audioEngine.updateLevel(0); - }, { once: true }); + document.addEventListener('click', () => { + audioEngine.updateLevel(0); + }, { once: true }); - setPeakHolderListener(() => { - if (!connectionLost) { - setBars(state.peakValue); - setSignal(state.peakSignalLevel); + setPeakHolderListener(() => { + if (!connectionLost) { + setBars(state.peakValue); + setSignal(state.peakSignalLevel); - const signalLevel = state.peakSignalLevel; - if (signalLevel !== lastSignalLevel) { - lastSignalLevel = signalLevel; - audioEngine.updateLevel(signalLevel); - setAudioIndicator(signalLevel > 0, signalLevel); - } - } - }); + const signalLevel = state.peakSignalLevel; + if (signalLevel !== lastSignalLevel) { + lastSignalLevel = signalLevel; + audioEngine.updateLevel(signalLevel); + setAudioIndicator(signalLevel > 0, signalLevel); + } + } + }); }); document.addEventListener('DOMContentLoaded', () => { @@ -76,197 +77,214 @@ document.addEventListener('DOMContentLoaded', () => { }); document.addEventListener("keydown", (e: KeyboardEvent) => { - const leftPanel = document.getElementById("leftPanel"); - const rightPanel = document.getElementById("rightPanel"); + const leftPanel = document.getElementById("leftPanel"); + const rightPanel = document.getElementById("rightPanel"); - switch(e.key.toLowerCase()) { - case 'c': - const camBtn = document.getElementById('camToggle'); - if (camBtn) camBtn.click(); - break; - case 'f': - if (leftPanel) { - leftPanel.style.width = "100%"; - if (rightPanel) rightPanel.style.display = "none"; - } - break; - case 'escape': - if (rightPanel) rightPanel.style.display = "flex"; - if (leftPanel && rightPanel) { - const total = window.innerWidth; - const leftWidth = total * 0.6; - leftPanel.style.flex = `0 0 ${leftWidth}px`; - rightPanel.style.flex = `0 0 ${total - leftWidth}px`; - } - break; - case 'm': - if (lastSignalLevel > 0) { - audioEngine.updateLevel(0); - setAudioIndicator(false); - console.log('[Audio] Звук отключён (M)'); - window._mutedLevel = lastSignalLevel; - lastSignalLevel = 0; - } else { - const level = window._mutedLevel || state.peakSignalLevel; - if (level > 0) { - audioEngine.updateLevel(level); - lastSignalLevel = level; - setAudioIndicator(true, level); - console.log('[Audio] Звук включён (M)'); - } - } - break; - } + switch (e.key.toLowerCase()) { + case 'c': + const camBtn = document.getElementById('camToggle'); + if (camBtn) camBtn.click(); + break; + case 'f': + if (leftPanel) { + leftPanel.style.width = "100%"; + if (rightPanel) rightPanel.style.display = "none"; + } + break; + case 'escape': + if (rightPanel) rightPanel.style.display = "flex"; + if (leftPanel && rightPanel) { + const total = window.innerWidth; + const leftWidth = total * 0.6; + leftPanel.style.flex = `0 0 ${leftWidth}px`; + rightPanel.style.flex = `0 0 ${total - leftWidth}px`; + } + break; + case 'm': + if (lastSignalLevel > 0) { + audioEngine.updateLevel(0); + setAudioIndicator(false); + window._mutedLevel = lastSignalLevel; + lastSignalLevel = 0; + } else { + const level = window._mutedLevel || state.peakSignalLevel; + if (level > 0) { + audioEngine.updateLevel(level); + lastSignalLevel = level; + setAudioIndicator(true, level); + } + } + break; + } }); // ================= UPDATE ================= async function update(): Promise { - try { - const [health, history] = await Promise.all([ - fetchHealth(), - fetchHistory() - ]); + try { + const [health, history] = await Promise.all([ + fetchHealth(), + fetchHistory() + ]); - consecutiveErrors = 0; + consecutiveErrors = 0; - if (health.server_time) { - setServerTime(health.server_time); - } - - if (connectionLost) { - console.log("[app] Соединение восстановлено"); - } - - const isOnline = health.pipe_alive && health.has_data; - - if (isOnline) { - setStatus("На связи"); - connectionLost = false; - } else { - setStatus("Нет связи", true); - if (!connectionLost) resetBufferTracking(); - connectionLost = true; - if (lastSignalLevel > 0) { - lastSignalLevel = 0; - audioEngine.updateLevel(0); - setAudioIndicator(false); - } - } - - const arr = Array.isArray(history.bytes) ? history.bytes : []; - const raw = arr[arr.length - 1] ?? 0; - - // Обновляем историю сигналов в state - if (arr.length > 0 && isOnline) { - const bufferSize = health.stats?.filled ?? 0; - addSignals(arr, bufferSize); - } - - // ===== ПАРСИНГ ДАННЫХ ===== - // Биты 0-5: значение (0-63) - // Биты 6-7: уровень сигнала (0-3) - const value = raw & 0x3F; // 0-63 - const level = (raw >> 6) & 0x3; // 0-3 - const percent = (value / 63) * 100; // 0-100% - - // ===== ОБНОВЛЯЕМ КАНАЛЫ ===== - // Передаем сырые данные для отображения в каналах - updateAllChannels(arr); - - if (isOnline) { - // ===== Обновляем Peak Holder (проценты) ==== - const displayValue = updatePeakHolder(percent); - setBars(displayValue); - - // Обновляем уровень сигнала с Peak Holder - const displaySignalLevel = updateSignalPeakHolder(level); - setSignal(displaySignalLevel); - - // Обновляем звук - const currentSignalLevel = state.peakSignalLevel; - if (currentSignalLevel !== lastSignalLevel) { - lastSignalLevel = currentSignalLevel; - audioEngine.updateLevel(currentSignalLevel); - setAudioIndicator(currentSignalLevel > 0, currentSignalLevel); + if (health.server_time) { + setServerTime(health.server_time); } - } else { + + const isOnline = health.pipe_alive && health.has_data; + + if (isOnline) { + setStatus("На связи"); + connectionLost = false; + } else { + setStatus("Нет связи", true); + if (!connectionLost) resetBufferTracking(); + connectionLost = true; + if (lastSignalLevel > 0) { + lastSignalLevel = 0; + audioEngine.updateLevel(0); + setAudioIndicator(false); + } + } + + const arr = Array.isArray(history.bytes) ? history.bytes : []; + const raw = arr[arr.length - 1] ?? 0; + + if (arr.length > 0 && isOnline) { + const bufferSize = health.stats?.filled ?? 0; + addSignals(arr, bufferSize); + } + + // ===== НАХОДИМ МАКСИМУМ ИЗ БУФЕРА ===== + let maxSignal = 0; + let maxLevel = 0; + let lastSignal = 0; + + if (isOnline && arr.length > 0) { + for (const byte of arr) { + const signal = byte & 0x3F; + const amp = (byte >> 6) & 0x3; + + if (signal > maxSignal) { + maxSignal = signal; + } + if (amp > maxLevel) { + maxLevel = amp; + } + } + + lastSignal = raw & 0x3F; + } + + const percent = (maxSignal / 63) * 100; + + // ===== ОБНОВЛЯЕМ КАНАЛЫ ===== + if (isOnline && arr.length > 0) { + setChannelValue(0, maxSignal, maxLevel); + + for (let i = 1; i < 8; i++) { + setChannelValue(i, 0, 0); + if (channelElements[i]) { + channelElements[i].item.classList.remove('channel-active'); + } + } + + if (channelElements[0]) { + channelElements[0].item.classList.add('channel-active'); + } + + if (DOM.lastByte) { + DOM.lastByte.textContent = lastSignal.toString(); + } + } else { + for (let i = 0; i < 8; i++) { + setChannelValue(i, 0, 0); + if (channelElements[i]) { + channelElements[i].item.classList.remove('channel-active'); + } + } + if (DOM.lastByte) { + DOM.lastByte.textContent = '--'; + } + } + + if (isOnline) { + updatePeakHolder(percent); + updateSignalPeakHolder(maxLevel); + } else { + setBars(0, true); + setSignal(0, true); + if (lastSignalLevel > 0) { + lastSignalLevel = 0; + audioEngine.updateLevel(0); + setAudioIndicator(false); + } + } + + const uptime = Math.round(health.uptime_sec || 0); + const filled = health.stats?.filled ?? 0; + const Bps = Math.round(health.stats?.bytes_per_sec ?? 0); + + setMeta(`Работа: ${uptime}s | Буфер: ${filled} | Приём: ${Bps} B/s`); + + if (DOM.history) { + DOM.history.innerHTML = arr + .map((b, i) => `[${i}] ${(b & 0xFF).toString(2).padStart(8, "0")} = ${b & 0xFF}`) + .join("
"); + } + + const now = performance.now(); + if (now - lastChart > 100 && DOM.canvas) { + drawChart(DOM.canvas, state.signalHistory); + lastChart = now; + } + + } catch (e) { + consecutiveErrors++; + + setStatus("СЕРВЕР НЕДОСТУПЕН", true); + setServerTime("--:--:--"); setBars(0, true); setSignal(0, true); + resetBufferTracking(); + if (lastSignalLevel > 0) { - lastSignalLevel = 0; - audioEngine.updateLevel(0); - setAudioIndicator(false); + lastSignalLevel = 0; + audioEngine.updateLevel(0); + setAudioIndicator(false); + } + + if (DOM.lastByte) DOM.lastByte.textContent = "--"; + + if (consecutiveErrors >= MAX_ERRORS && pollTimer) { + clearInterval(pollTimer); } } - - if (DOM.lastByte) { - DOM.lastByte.textContent = isOnline ? value.toString() : "--"; - } - - const uptime = Math.round(health.uptime_sec || 0); - const filled = health.stats?.filled ?? 0; - const Bps = Math.round(health.stats?.bytes_per_sec ?? 0); - - setMeta(`Работа: ${uptime}s | Буфер: ${filled} | Приём: ${Bps} B/s`); - - if (DOM.history) { - DOM.history.innerHTML = arr - .map((b, i) => `[${i}] ${(b & 0xFF).toString(2).padStart(8, "0")} = ${b & 0xFF}`) - .join("
"); - } - - const now = performance.now(); - if (now - lastChart > 100 && DOM.canvas) { - drawChart(DOM.canvas, state.signalHistory); - lastChart = now; - } - - } catch (e) { - consecutiveErrors++; - if (consecutiveErrors === 1) console.warn("[app] Сервер недоступен, ждём..."); - - setStatus("СЕРВЕР НЕДОСТУПЕН", true); - setServerTime("--:--:--"); - setBars(0, true); - setSignal(0, true); - resetBufferTracking(); - - if (lastSignalLevel > 0) { - lastSignalLevel = 0; - audioEngine.updateLevel(0); - setAudioIndicator(false); - } - - if (DOM.lastByte) DOM.lastByte.textContent = "--"; - - if (consecutiveErrors >= MAX_ERRORS && pollTimer) { - clearInterval(pollTimer); - } - } } function startPolling(): void { - if (pollTimer) clearInterval(pollTimer); - if (consecutiveErrors > 0) update(); - pollTimer = window.setInterval(update, 500); + if (pollTimer) clearInterval(pollTimer); + if (consecutiveErrors > 0) update(); + pollTimer = window.setInterval(update, 500); } window.addEventListener("focus", () => { - if (consecutiveErrors >= MAX_ERRORS) { - consecutiveErrors = 0; - startPolling(); - } + if (consecutiveErrors >= MAX_ERRORS) { + consecutiveErrors = 0; + startPolling(); + } }); window.addEventListener("beforeunload", () => { - audioEngine.dispose(); + audioEngine.dispose(); }); declare global { - interface Window { - _mutedLevel?: number; - } + interface Window { + _mutedLevel?: number; + } } startPolling(); -update(); +update(); \ No newline at end of file diff --git a/cmd/server/web/js/state.ts b/cmd/server/web/js/state.ts index 429c229..1d119fa 100644 --- a/cmd/server/web/js/state.ts +++ b/cmd/server/web/js/state.ts @@ -1,23 +1,24 @@ -// state.ts - Peak Holder алгоритм с таймером +// state.ts - Peak Holder с сбросом до текущего максимума interface AppState { maxPoints: number; signalHistory: number[]; lastBufferSize: number; - // Peak Holder + // Peak Holder для амплитуды peakValue: number; // Текущее удерживаемое пиковое значение амплитуды - currentValue: number; // Текущее реальное значение амплитуды + currentValue: number; // Текущий максимум из буфера peakTimer: number | null; // Таймер для сброса пика амплитуды peakHoldTime: number; // Время удержания пика в миллисекундах // Peak Holder для уровня сигнала peakSignalLevel: number; - currentSignalLevel: number; + currentSignalLevel: number; // Текущий максимум уровня из буфера signalPeakTimer: number | null; signalPeakHoldTime: number; hasPeak: boolean; + lastMaxSignal: number; } type PeakHolderListener = () => void; @@ -30,7 +31,7 @@ export const state: AppState = { peakValue: 0, currentValue: 0, peakTimer: null, - peakHoldTime: 3000, // 3 секунды + peakHoldTime: 3000, peakSignalLevel: 0, currentSignalLevel: 0, @@ -38,6 +39,7 @@ export const state: AppState = { signalPeakHoldTime: 3000, hasPeak: false, + lastMaxSignal: 0, }; let peakHolderListener: PeakHolderListener | null = null; @@ -46,20 +48,95 @@ let peakHolderListener: PeakHolderListener | null = null; // ПУБЛИЧНЫЕ ФУНКЦИИ // ============================================ +export function setPeakHolderListener(listener: PeakHolderListener | null): void { + peakHolderListener = listener; +} + +function notifyPeakHolderChange(): void { + if (peakHolderListener) { + peakHolderListener(); + } +} + /** - * Установка времени удержания пика сигнала - * @param ms - время в миллисекундах + * Обновление пикового значения амплитуды + * @param currentMax - текущий максимум из буфера (в процентах 0-100) + * @returns пиковое значение */ -// noinspection JSUnusedGlobalSymbols +export function updatePeakHolder(currentMax: number): number { + state.currentValue = currentMax; + state.lastMaxSignal = currentMax; + + if (currentMax >= state.peakValue) { + state.peakValue = currentMax; + state.hasPeak = true; + scheduleAmplitudePeakRelease(); + notifyPeakHolderChange(); + return state.peakValue; + } + + return state.peakValue; +} + +/** + * Обновление пикового уровня сигнала + * @param currentMaxLevel - текущий максимум уровня из буфера (0-3) + * @returns пиковый уровень + */ +export function updateSignalPeakHolder(currentMaxLevel: number): number { + state.currentSignalLevel = currentMaxLevel; + + if (currentMaxLevel >= state.peakSignalLevel) { + state.peakSignalLevel = currentMaxLevel; + state.hasPeak = true; + scheduleSignalPeakRelease(); + notifyPeakHolderChange(); + return state.peakSignalLevel; + } + + return state.peakSignalLevel; +} + +// ============================================ +// ВНУТРЕННИЕ ФУНКЦИИ +// ============================================ + +function scheduleAmplitudePeakRelease(): void { + if (state.peakTimer !== null) { + clearTimeout(state.peakTimer); + state.peakTimer = null; + } + + state.peakTimer = window.setTimeout(() => { + state.peakValue = state.currentValue; + state.peakTimer = null; + state.hasPeak = false; + notifyPeakHolderChange(); + }, state.peakHoldTime); +} + +function scheduleSignalPeakRelease(): void { + if (state.signalPeakTimer !== null) { + clearTimeout(state.signalPeakTimer); + state.signalPeakTimer = null; + } + + state.signalPeakTimer = window.setTimeout(() => { + state.peakSignalLevel = state.currentSignalLevel; + state.signalPeakTimer = null; + state.hasPeak = false; + notifyPeakHolderChange(); + }, state.signalPeakHoldTime); +} + +// ============================================ +// ОСТАЛЬНЫЕ ФУНКЦИИ +// ============================================ + export function setSignalPeakHoldTime(ms: number): void { state.signalPeakHoldTime = ms; } -/** - * Получение информации о пике сигнала - * @returns { peak: number; current: number; timerActive: boolean } - */ -// noinspection JSUnusedGlobalSymbols export function getSignalPeakInfo(): { peak: number; current: number; timerActive: boolean } { return { peak: state.peakSignalLevel, @@ -68,11 +145,6 @@ export function getSignalPeakInfo(): { peak: number; current: number; timerActiv }; } -/** - * Добавление нового значения сигнала в историю - * @param value - значение сигнала - */ -// noinspection JSUnusedGlobalSymbols export function addSignal(value: number): void { state.signalHistory.push(value); if (state.signalHistory.length > state.maxPoints) { @@ -80,65 +152,6 @@ export function addSignal(value: number): void { } } -/** - * Установка слушателя для обновления пика - * @param listener - функция-слушатель - */ -// noinspection JSUnusedGlobalSymbols -export function setPeakHolderListener(listener: PeakHolderListener | null): void { - peakHolderListener = listener; -} - -/** - * Обновление пикового значения амплитуды - * @param target - текущее значение - * @returns пиковое значение - */ -// noinspection JSUnusedGlobalSymbols -export function updatePeakHolder(target: number): number { - // Обновляем текущее значение - state.currentValue = target; - - // Если новое значение больше текущего пика - if (target > state.peakValue) { - // Устанавливаем новый пик - state.peakValue = target; - state.hasPeak = true; - scheduleAmplitudePeakRelease(); - } else if (target < state.peakValue && state.peakTimer === null) { - // Пик выше текущего значения, но таймер пропал — перезапускаем удержание - scheduleAmplitudePeakRelease(); - } // Возвращаем текущий пик - return state.peakValue; -} - -/** - * Обновление пикового уровня сигнала - * @param level - текущий уровень - * @returns пиковый уровень - */ -// noinspection JSUnusedGlobalSymbols -export function updateSignalPeakHolder(level: number): number { - // Обновляем текущее значение - state.currentSignalLevel = level; - - // Если новый уровень больше пика — устанавливаем новый пик - if (level > state.peakSignalLevel) { - state.peakSignalLevel = level; - state.hasPeak = true; - scheduleSignalPeakRelease(); - } else if (level < state.peakSignalLevel && state.signalPeakTimer === null) { - scheduleSignalPeakRelease(); - } - - // Возвращаем текущий пик - return state.peakSignalLevel; -} - -/** - * Принудительный сброс пика сигнала - */ -// noinspection JSUnusedGlobalSymbols export function forceResetSignalPeak(): void { if (state.signalPeakTimer !== null) { clearTimeout(state.signalPeakTimer); @@ -147,12 +160,10 @@ export function forceResetSignalPeak(): void { state.peakSignalLevel = 0; state.currentSignalLevel = 0; state.hasPeak = false; + state.lastMaxSignal = 0; + notifyPeakHolderChange(); } -/** - * Принудительный сброс всех пиков - */ -// noinspection JSUnusedGlobalSymbols export function forceResetPeak(): void { if (state.peakTimer !== null) { clearTimeout(state.peakTimer); @@ -161,15 +172,11 @@ export function forceResetPeak(): void { state.peakValue = 0; state.currentValue = 0; state.hasPeak = false; + state.lastMaxSignal = 0; forceResetSignalPeak(); + notifyPeakHolderChange(); } -/** - * Добавление массива значений сигнала - * @param values - массив значений - * @param bufferSize - размер буфера - */ -// noinspection JSUnusedGlobalSymbols export function addSignals(values: number[], bufferSize: number): void { if (!Array.isArray(values) || values.length === 0) return; @@ -188,55 +195,7 @@ export function addSignals(values: number[], bufferSize: number): void { } } -/** - * Сброс отслеживания буфера - */ -// noinspection JSUnusedGlobalSymbols export function resetBufferTracking(): void { state.lastBufferSize = 0; forceResetPeak(); -} - -// ============================================ -// ВНУТРЕННИЕ ФУНКЦИИ -// ============================================ - -function notifyPeakHolderChange(): void { - if (peakHolderListener) { - peakHolderListener(); - } -} - -function scheduleAmplitudePeakRelease(): void { - // Сбрасываем старый таймер - if (state.peakTimer !== null) { - clearTimeout(state.peakTimer); - state.peakTimer = null; - } - - // Запускаем новый таймер на 3 секунды - state.peakTimer = window.setTimeout(() => { - state.peakValue = state.currentValue; - state.peakTimer = null; - state.hasPeak = false; - console.log(`[Peak] Сброс пика до: ${state.peakValue}`); - notifyPeakHolderChange(); - }, state.peakHoldTime); -} - -function scheduleSignalPeakRelease(): void { - // Сбрасываем старый таймер - if (state.signalPeakTimer !== null) { - clearTimeout(state.signalPeakTimer); - state.signalPeakTimer = null; - } - - // Запускаем новый таймер на 3 секунды - state.signalPeakTimer = window.setTimeout(() => { - // Сбрасываем пик к текущему уровню - state.peakSignalLevel = state.currentSignalLevel; - state.signalPeakTimer = null; - state.hasPeak = false; - notifyPeakHolderChange(); - }, state.signalPeakHoldTime); } \ No newline at end of file diff --git a/cmd/server/web/js/ui.ts b/cmd/server/web/js/ui.ts index b103410..58bc8d6 100644 --- a/cmd/server/web/js/ui.ts +++ b/cmd/server/web/js/ui.ts @@ -1,5 +1,4 @@ -// ui.ts - ИСПРАВЛЕННАЯ ВЕРСИЯ -// Канал 1 - реальные данные, каналы 2-8 - неактивны +// ui.ts - Управление UI элементами export interface DOMElements { serverTime: HTMLElement | null; @@ -95,7 +94,6 @@ export function setBars(p: number, noConnection: boolean = false): void { el.classList.remove("no-connection"); const percent = Math.max(0, Math.min(100, p)); el.style.width = `${percent}%`; - // Принудительно устанавливаем рыжий цвет el.style.background = '#e6852d'; el.style.backgroundColor = '#e6852d'; el.style.backgroundImage = 'none'; @@ -182,7 +180,7 @@ export const channels: ChannelState[] = Array(8).fill(null).map(() => ({ expanded: true })); -let channelElements: any[] = []; +export let channelElements: any[] = []; export function initChannels() { const container = document.getElementById('channelsContainer'); @@ -196,7 +194,6 @@ export function initChannels() { item.className = 'channel-item'; item.dataset.channel = String(i); - // Только канал 1 активен по умолчанию if (i === 0) { item.classList.add('channel-active'); } @@ -259,15 +256,16 @@ export function toggleChannel(index: number) { } } +export function getChannelElement(index: number) { + return channelElements[index] || null; +} + export function setChannelValue(index: number, value: number, strength: number) { if (index < 0 || index > 7) return; const ch = channels[index]; const el = channelElements[index]; - if (!el) { - console.warn(`[UI] Channel ${index} element not found`); - return; - } + if (!el) return; ch.value = value; ch.strength = strength; @@ -275,7 +273,6 @@ export function setChannelValue(index: number, value: number, strength: number) const maxValue = 63; const percent = (value / maxValue) * 100; - // Принудительно устанавливаем стили для прогресс-бара if (el.bar) { el.bar.style.width = `${Math.min(100, percent)}%`; el.bar.style.background = '#e6852d'; @@ -305,13 +302,7 @@ export function setChannelValue(index: number, value: number, strength: number) } } -/** - * ОБНОВЛЕНИЕ ВСЕХ КАНАЛОВ - * Канал 1 - реальные данные из эмулятора - * Каналы 2-8 - неактивны (0) - */ export function updateAllChannels(rawData?: number[]) { - // Сначала сбрасываем все каналы в 0 for (let i = 0; i < 8; i++) { setChannelValue(i, 0, 0); if (channelElements[i]) { @@ -319,52 +310,28 @@ export function updateAllChannels(rawData?: number[]) { } } - // Если есть данные - обновляем КАНАЛ 1 if (rawData && rawData.length > 0) { - // Берем последний байт (самый свежий) const lastByte = rawData[rawData.length - 1]; + const value = lastByte & 0x3F; + const strength = (lastByte >> 6) & 0x3; - // ПРАВИЛЬНЫЙ ПАРСИНГ: - // Биты 0-5: значение (количество сигналов) 0-63 - // Биты 6-7: уровень сигнала (амплитуда/сила) 0-3 - const value = lastByte & 0x3F; // маска 0b00111111 → 0-63 - const strength = (lastByte >> 6) & 0x3; // маска 0b00000011 → 0-3 - - // Обновляем КАНАЛ 1 с реальными данными setChannelValue(0, value, strength); if (channelElements[0]) { channelElements[0].item.classList.add('channel-active'); } - // Обновляем last-byte if (DOM.lastByte) { DOM.lastByte.textContent = value.toString(); } - - // Обновляем основной прогресс-бар (для синхронизации) - const percent = (value / 63) * 100; - if (DOM.barFill) { - DOM.barFill.style.width = `${percent}%`; - DOM.barFill.style.background = '#e6852d'; - DOM.barFill.style.backgroundColor = '#e6852d'; - DOM.barFill.style.backgroundImage = 'none'; - } } else { - // Если данных нет - канал 1 тоже неактивен if (DOM.lastByte) { DOM.lastByte.textContent = '--'; } - if (DOM.barFill) { - DOM.barFill.style.width = '0%'; - } } } -// Инициализация при загрузке document.addEventListener('DOMContentLoaded', () => { initChannels(); - - // Принудительно обновляем каналы при загрузке updateAllChannels(); const expandAll = document.getElementById('expandAllBtn'); diff --git a/scripts/emulator.py b/scripts/emulator.py index 2404607..e9defbf 100644 --- a/scripts/emulator.py +++ b/scripts/emulator.py @@ -1,8 +1,11 @@ """ -# Создание сокета mkfifo /tmp/gpio_pipe -# Необходим файл с данными, например array.txt. Формат записи данных [0xE0, 0x01, 0x4C, 0x88] -# Запуск python3 emulator.py array.txt /tmp/gpio_pipe +Эмулятор чтения из файла и отправки в pipe. + +Создание сокета: mkfifo /tmp/gpio_pipe +Формат файла: [0xE0, 0x01, 0x4C, 0x88] +Запуск: python3 emulator.py array.txt /tmp/gpio_pipe """ + import sys import time import ast @@ -49,6 +52,7 @@ def status_clear(): # ================= DATA ================= def load_array(path): + """Загружает данные из файла.""" while True: try: with open(path, "r") as f: @@ -73,6 +77,7 @@ def load_array(path): # ================= PIPE ================= def open_pipe(path): + """Открывает pipe для записи.""" while not stop_flag: try: fd = os.open(path, os.O_WRONLY | os.O_NONBLOCK)