В процессе работы
This commit is contained in:
@@ -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<void> {
|
||||
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("<br>");
|
||||
}
|
||||
|
||||
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("<br>");
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user