363 lines
20 KiB
JavaScript
363 lines
20 KiB
JavaScript
"use strict";
|
||
|
||
let signals = gtl.options.record.signalsModel;
|
||
let options = gtl.options;
|
||
let record = gtl.options.record;
|
||
let point = gtl.options.point;
|
||
|
||
let plot_ausp_j = gtl.plots.add("Спектры по каналам"); // спектры по каналам вибрации
|
||
let plot_FreqResp_ampl_j = gtl.plots.add("Частотный отклик по каналам (амплитуда)"); // амплитудные спектры по каналам вибрации
|
||
let plot_FreqResp_phase_j = gtl.plots.add("Частотный отклик по каналам (фаза)"); // фазовые спектры по каналам вибрации
|
||
|
||
// Цвета
|
||
// #ff0000 - красный
|
||
// #00ff00 - зелёный
|
||
// #0000ff - синий
|
||
// #00ddff - голубой
|
||
// #ff3dcc - фиолетовый
|
||
// #ffff00 - жёлтый
|
||
|
||
|
||
// Алгоритм
|
||
// 1. Получить спектры ускорения и силы в комплексном виде (вибропреобразователь и молоток).
|
||
// 2. Преобразовать спектр силы (молотка) в комплексно-сопряжённый вид.
|
||
// 3. Получить кросс-спектр (перекрёстный) между ускорением и силой.
|
||
// 4. Вычислить деление кросс-спектра на спектр силы.
|
||
|
||
|
||
// Настройки для спектров и АФЧХ
|
||
// let signal_force = gtl.options.customOptions.force; // номер канала силы (молотка)
|
||
let frequency = gtl.options.customOptions.frequency; // граничная частота
|
||
let resolution = gtl.options.customOptions.resolution; // частотное разрешение
|
||
let lines = 1 / resolution; // количество линий
|
||
let average = gtl.options.customOptions.average; // количество усреднений
|
||
let overlap = gtl.options.customOptions.overlap; // наложение
|
||
let view_ = gtl.options.customOptions.view; // отображение амплитуды спектра db / unit (1 / 0)
|
||
let view = gtl.spec.unit; // значение view по умолчанию
|
||
let view_freq_resp = gtl.options.customOptions.view_freq_resp; // отображение амплитуды спектра частотного отклика db / unit (1 / 0)
|
||
let variant_freq_resp = gtl.options.customOptions.variant_freq_resp; // вариант вычисления: 0 - через кросс, 1 - через автоспектр
|
||
let thresh_level = gtl.options.customOptions.thresh_level; // уровень удара для засчитывания импульса
|
||
let time_impulse_ = gtl.options.customOptions.time_impulse; // задание длины импульса из опций
|
||
let ausp_j = []; // массив объектов спектров вибрации
|
||
let phase_delta_j = []; // массив объектов дельты фазовых спектров вибрации и спектра силы
|
||
let counter = 1; // счётчик для цикла по каналам вибрации
|
||
let counter_ranges = 0; // счётчик для цикла по выделенным диапазонам сигналов записи
|
||
let ausp_cross_real = []; // массив объектов действительной части кросс-спектров канала силы [0] и канала вибрации [j]
|
||
let ausp_cross_imag = []; // массив объектов мнимой части кросс-спектров канала силы [0] и канала вибрации [j]
|
||
let freq_resp_real = []; // действительная часть частотного отклика между каналом силы [0] и каналом вибрации [j]
|
||
let freq_resp_imag = []; // мнимая часть частотного отклика между каналом силы [0] и каналом вибрации [j]
|
||
let freq_resp_ampl = []; // амплитуда частотного отклика между каналом силы [0] и каналом вибрации [j]
|
||
let freq_resp_phase = []; // фаза частотного отклика между каналом силы [0] и каналом вибрации [j]
|
||
let freq_resp_ampl_avg = []; // усреднение амплитуды частотного отклика по всем ударам
|
||
let freq_resp_phase_avg = []; // усреднение фазы частотного отклика по всем ударам
|
||
|
||
|
||
// Задание диапазона сигнала для анализа
|
||
// let ranges = gtl.player.stored_ranges;
|
||
|
||
// Спектры каналов ускорения (каналы датчиков вибрации)
|
||
gtl.log.info("Количество сигналов", signals.length);
|
||
|
||
gtl.log.info("Граничная частота спектров", frequency);
|
||
gtl.log.info("Частотное разрешение спектров", resolution);
|
||
|
||
|
||
// Условие отображения спектров в дБ или линейном виде
|
||
if (view_ == 1) {view = gtl.spec.db}
|
||
else if (view_ == 0) {view = gtl.spec.unit}
|
||
|
||
for (let j = 1; j < signals.length; j++) {
|
||
|
||
// Спектр амплитудный для цикла по всем каналам вибрации
|
||
ausp_j.push(
|
||
gtl.create_ausp(
|
||
{
|
||
"src": gtl.analog_inputs[signals[j].portNumber], // сырой сигнал вибрации
|
||
"frequency": frequency,
|
||
"resolution": resolution,
|
||
"average": average,
|
||
"overlap": overlap,
|
||
"window": gtl.spec.rectangular,
|
||
"view": view
|
||
}
|
||
)
|
||
);
|
||
|
||
// Спектр фазовый для цикла по всем каналам вибрации (относительно сигнала силы)
|
||
phase_delta_j.push(
|
||
gtl.create_pfc(
|
||
{
|
||
"src0" : gtl.analog_inputs[signals[0].portNumber], // сигнал силы, относительно него вычисляем дельту фазы
|
||
"src1" : gtl.analog_inputs[signals[j].portNumber], // сигнал вибрации
|
||
"frequency" : frequency,
|
||
"resolution" : resolution,
|
||
"average" : average,
|
||
"overlap" : overlap,
|
||
"window" : gtl.spec.rectangular,
|
||
"view" : gtl.phase.deg,
|
||
"range" : gtl.phase.positive,
|
||
"is_single" : false
|
||
}
|
||
)
|
||
);
|
||
}
|
||
|
||
gtl.log.info("Количество спектров", ausp_j.length);
|
||
|
||
// Спектр силы (канал сигнала молотка)
|
||
// Канал_0
|
||
let ausp_force_0 = gtl.create_ausp(
|
||
{
|
||
"src": gtl.analog_inputs[signals[0].portNumber], // сигнал силы
|
||
"frequency": frequency,
|
||
"resolution": resolution,
|
||
"average": average,
|
||
"overlap": overlap,
|
||
"window": gtl.spec.rectangular,
|
||
"view": view
|
||
}
|
||
);
|
||
|
||
|
||
let src_force = gtl.analog_inputs[0]; // источник сигнала силы
|
||
let time = gtl.options.record.playerTime; // время записи для анализа
|
||
let time_window = 0.02 // временное окно наблюдения
|
||
let time_impulse = 0; // длина импульса для функции нарезки сигнала
|
||
if (time_impulse_ == 0)
|
||
{
|
||
// time_impulse = ausp_force_0.acq_time + 0.05; // временное окно для анализа импульса (не менее времени построения спектра)
|
||
time_impulse = gtl.acq_time + 0.05;
|
||
}
|
||
else {time_impulse = time_impulse_};
|
||
let time_pre = time_window / 2; // время предыстории
|
||
let ranges = []; // массив диапазонов для импульсов
|
||
let tries = 0; // циклы диагностки
|
||
let is_ranges = false; // состояние готовности диапазонов
|
||
|
||
// Скользящая амплитуда для создания "огибающей" импульса
|
||
let __max = gtl.create_moving_max({
|
||
src: src_force,
|
||
name: "max",
|
||
time: time_window
|
||
});
|
||
__max.history = 2; //история сохраненных данных сигнала
|
||
|
||
// Порог для олтслеживание импульсов
|
||
let __thresh = gtl.create_moving_thresh({
|
||
src: __max,
|
||
name: "thresh",
|
||
time: time_window,
|
||
level: thresh_level
|
||
});
|
||
__thresh.history = 2; // история сохраненных данных сигнала
|
||
|
||
|
||
__thresh.triggered.connect(thresh_triggered_event); //подключаем функцию
|
||
// Функция получения диапазонов импульсов
|
||
function thresh_triggered_event(flag, sample) {
|
||
if (is_ranges) return;
|
||
if (flag) {
|
||
let start = (sample / __thresh.rate) - time_pre; //время срабатывания триггера
|
||
gtl.log.info("trigger on time", start);
|
||
|
||
let range = {
|
||
min: start,
|
||
max: start + time_impulse
|
||
};
|
||
ranges.push(range); //добавляем диапазон в массив
|
||
};
|
||
};
|
||
|
||
|
||
// Функция диагностики.
|
||
/*
|
||
Первый запуск функции диагностики происходит после проигрывания всего сигнала и определения массива диапазонов по срабатыванию триггера
|
||
После этого происходит добавление массива пользовательских диапазонов в плеер, отрисовка (при необходимости) сигнала триггера
|
||
Затем устанавливается интервал запуска функции диагностики, равный временному окну для анализа импульса и запускается отсчет циклов (попыток) диагностики
|
||
Как только количество циклов (попыток) сравнивается с количеством установленных пользовательских диапазонов, диагностика останавливается
|
||
*/
|
||
|
||
gtl.diagnostic.interval = time; // интервал запуска функции диагностики
|
||
let plot = gtl.plots.add("Impulses"); // создаем координатную плоскость
|
||
|
||
function diagnose() {
|
||
if (tries >= ranges.length) gtl.diagnostic.stop();
|
||
if (!is_ranges) {
|
||
gtl.player.custom_ranges = ranges; // добавляем пользовательские диапазоны в плеер
|
||
gtl.results = { ranges: ranges }; // добавляем диапазоны в результат
|
||
gtl.log.info('creating ranges', true);
|
||
gtl.log.info('ranges', JSON.stringify(ranges));
|
||
is_ranges = true; // пользовательские диапазоны готовы
|
||
|
||
plot.add({
|
||
color: 0x0000ff, // цвет графика
|
||
name: "max", // наименование графика
|
||
x: 1 / __max.rate, // частотное разрешение
|
||
y: __max.getHistoryArray() // значения амплитуд
|
||
}); // рисуем сигнал для контроля
|
||
|
||
plot.add({
|
||
color: 0xff00ff, // цвет графика
|
||
name: "thresh", // наименование графика
|
||
x: 1 / __thresh.rate, // частотное разрешение
|
||
y: __thresh.getHistoryArray() // значения амплитуд
|
||
}); // рисуем сигнал для контроля
|
||
}
|
||
|
||
gtl.diagnostic.interval = time_impulse; // задаем время запуска функции диагностики, равное длине импульса
|
||
if (tries == 0) { gtl.log.info("Установленный интервал запуска диагностики", time_impulse)
|
||
gtl.log.info("Необходимая длина сигнала для спектров", ausp_force_0.acq_time)
|
||
gtl.log.info("acq_time", gtl.acq_time)
|
||
gtl.log.info("Длина массива спектра", ausp_force_0.data.length);}
|
||
else { gtl.log.info('diagnostic', `Цикл диагностики ${tries} - ${true}`) }
|
||
|
||
/*
|
||
Математика обработки сигнала
|
||
*/
|
||
let n = ausp_force_0.data.length; // переменная для цикла по гармоникам спектра
|
||
// signals.length - переменная для цикла по каналам
|
||
|
||
// Канал[0] (ausp_force_0) - комплексно-сопряжённый вид
|
||
// меняется знак мнимой части на противоположный
|
||
// действительная часть без изменений
|
||
let ausp_force_0_imag_anti = []; // мнимая часть с противоположным знаком
|
||
for (let i = 0; i < n; i += 1)
|
||
{
|
||
ausp_force_0_imag_anti.push(ausp_force_0.imag[i] * (-1));
|
||
}
|
||
|
||
// Графики амплитудных спектров вибрации и спектров дельты фаз между каналом силы и вибрации
|
||
for (let k = 0; k < ranges.length; k++) { // цикл по ударам (сначала по ударам, потом по каналам)
|
||
for (let j = 1; j < ausp_j.length; j++) { // цикл по каналам
|
||
// for (let j = 1; j < 2; j++) { // цикл по каналам
|
||
if (counter > 0) {
|
||
plot_ausp_j.add(
|
||
{
|
||
color: 0x34C924,
|
||
name: `Спектр канала ${j+1} - удар №${k+1}, g`,
|
||
x: ausp_j[j].resolution,
|
||
y: ausp_j[j].data,
|
||
tags: ["Амплитудный спектр вибрации, g"]
|
||
});
|
||
|
||
|
||
// Кросс-спектры
|
||
// Канал[j] & Канал[0]
|
||
// сигнал силы в комплексно-сопряжённом виде
|
||
// сигнал вибрации умножвается на сигнал силы, а НЕ наоборот
|
||
// умножение комплексных чисел по формуле:
|
||
// (a1+b1i)*(a2+b2i)=(a1*a2-b1*b2)*(a1*b2+b1*a2)i
|
||
// (a1*a2-b1*b2) - действительная часть
|
||
// (a1*b2+b1*a2)i - мнимая часть
|
||
ausp_cross_real[j] = []; // массив действительной части кросс-спектра
|
||
ausp_cross_imag[j] = []; // массив мнимой части кросс-спектра
|
||
for (let i = 0; i < n; i += 1)
|
||
{
|
||
ausp_cross_real[j].push(ausp_j[j].real[i] * ausp_force_0.real[i] -
|
||
(ausp_j[j].imag[i] * ausp_force_0_imag_anti[i]));
|
||
ausp_cross_imag[j].push(ausp_j[j].real[i] * ausp_force_0_imag_anti[i] +
|
||
ausp_j[j].imag[i] * ausp_force_0.real[i]);
|
||
}
|
||
|
||
// Вычисление частотного отклика
|
||
// деление комплексных чисел по формуле:
|
||
// (a+bi)/(c+di)=(a*c + b*d)/(c^2 + d^2) + (c*b - a*d)/(c^2 + d^2)i
|
||
// (a*c + b*d)/(c^2 + d^2) - действительная часть
|
||
// (c*b - a*d)/(c^2 + d^2)i - мнимая часть
|
||
freq_resp_real[j] = []; // массив массивов действительной части отклика
|
||
freq_resp_imag[j] = []; // массив массивов мнимой части отклика
|
||
if (variant_freq_resp == 0)
|
||
{
|
||
// Вариант 1: Частотный отклик как деление кросс-спектра вибрации и силы на автоспектр силы
|
||
// (Канал[0] & Канал[j]) / Канал[0]
|
||
for (let i = 0; i < n; i += 1)
|
||
{
|
||
freq_resp_real[j].push((ausp_cross_real[j][i] * ausp_force_0.real[i] +
|
||
ausp_cross_imag[j][i] * ausp_force_0.imag[i]) /
|
||
(Math.pow(ausp_force_0.real[i], 2) + Math.pow(ausp_force_0.imag[i], 2)));
|
||
|
||
freq_resp_imag[j].push((ausp_force_0.real[i] * ausp_cross_imag[j][i] -
|
||
ausp_cross_real[j][i] * ausp_force_0.imag[i]) /
|
||
(Math.pow(ausp_force_0.real[i], 2) + Math.pow(ausp_force_0.imag[i], 2)));
|
||
}
|
||
} else {
|
||
// Вариант 2: Частотный отклик как деление автоспектра вибрации на автоспектр силы
|
||
// Канал[j] / Канал[0]
|
||
for (let i = 0; i < n; i += 1)
|
||
{
|
||
freq_resp_real[j].push((ausp_j[j].real[i] * ausp_force_0.real[i] +
|
||
ausp_j[j].imag[i] * ausp_force_0.imag[i]) /
|
||
(Math.pow(ausp_force_0[i].real[i], 2) + Math.pow(ausp_force_0.imag[i], 2)));
|
||
|
||
freq_resp_imag[j].push((ausp_force_0.real[i] * ausp_j[j].imag[i] -
|
||
ausp_j[j].real[i] * ausp_force_0.imag[i]) /
|
||
(Math.pow(ausp_force_0[i].real[i], 2) + Math.pow(ausp_force_0.imag[i], 2)));
|
||
}
|
||
}
|
||
|
||
// запись массива амплитуды и фазы
|
||
freq_resp_ampl[j] = [];
|
||
freq_resp_phase[j] = [];
|
||
for (let i = 0; i < n; i += 1)
|
||
{
|
||
// Условие отображения спектров частотного отклика в дБ или линейном виде
|
||
if (view_freq_resp == 0) {
|
||
freq_resp_ampl[j].push(Math.sqrt(Math.pow(freq_resp_real[j][i],2) + Math.pow(freq_resp_imag[j][i],2)));
|
||
} else if (view_freq_resp == 1) {
|
||
freq_resp_ampl[j].push(20 * Math.log10((Math.sqrt(Math.pow(freq_resp_real[j][i],2) + Math.pow(freq_resp_imag[j][i],2))) / Math.pow(10,-6)));
|
||
}
|
||
if (freq_resp_real[j][i] > 0 & freq_resp_imag[j][i] > 0)
|
||
{freq_resp_phase[j].push(Math.atan(freq_resp_imag[j][i] / freq_resp_real[j][i]) * 180 / Math.PI)}
|
||
else if (freq_resp_real[j][i] < 0 & freq_resp_imag[j][i] > 0)
|
||
{freq_resp_phase[j].push(180 - Math.atan(freq_resp_imag[j][i] / freq_resp_real[j][i]) * 180 / Math.PI)}
|
||
else if (freq_resp_real[j][i] < 0 & freq_resp_imag[j][i] < 0)
|
||
{freq_resp_phase[j].push(180 + Math.atan(freq_resp_imag[j][i] / freq_resp_real[j][i]) * 180 / Math.PI)}
|
||
else if (freq_resp_real[j][i] > 0 & freq_resp_imag[j][i] < 0)
|
||
{freq_resp_phase[j].push(360 - Math.atan(freq_resp_imag[j][i] / freq_resp_real[j][i]) * 180 / Math.PI)}
|
||
}
|
||
// корректировка угла для предотвращения превышения 360 град
|
||
for (let i = 0; i < freq_resp_phase[j].length; i += 1)
|
||
{
|
||
if (freq_resp_phase[j][i] >= 360)
|
||
{freq_resp_phase[j][i] = freq_resp_phase[j][i] - 360}
|
||
else {freq_resp_phase[j][i] = freq_resp_phase[j][i]}
|
||
}
|
||
|
||
plot_FreqResp_ampl_j.add(
|
||
{
|
||
color: 0x3b30b6,
|
||
name: `Амплитуда частотного отклика канала ${j+1} - удар №${k+1}, g/N`,
|
||
x: 1/lines,
|
||
y: freq_resp_ampl[j],
|
||
tags: ["Амплитуда частотного отклика, g/N"]
|
||
});
|
||
// plot_FreqResp_phase_j.add(
|
||
// {
|
||
// color: 0xb6306f,
|
||
// name: `Фаза частотного отклика канала ${j+1} - удар №${k+1}, °`,
|
||
// x: 1/lines,
|
||
// y: freq_resp_phase[j],
|
||
// tags: ["Фаза частотного отклика, °"]
|
||
// });
|
||
|
||
|
||
};
|
||
};
|
||
|
||
counter += 1;
|
||
|
||
plot_ausp_j.add(
|
||
{
|
||
color: 0xff00f7,
|
||
name: `Спектр силы (канал [0]) - удар №${k+1}, N`,
|
||
x: 1/lines,
|
||
y: ausp_force_0.data,
|
||
tags: [`Спектр силы (канал [0]) - удар №${k+1}, N`]
|
||
});
|
||
};
|
||
|
||
|
||
|
||
tries++;
|
||
|
||
} |