freq_responce/scripts/Частотный_отклик v11.js

433 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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_vibr = gtl.plots.add("Спектры по каналам"); // спектры по каналам вибрации
let plot_FreqResp_ampl = gtl.plots.add("Частотный отклик по каналам (амплитуда)"); // амплитудные спектры по каналам вибрации
let plot_FreqResp_phase = 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_vibr; // массив объектов спектров вибрации
let ausp_force; // массив объектов спектров силы по диапазонам [k]
let phase_vibr; // массив объектов дельты фазовых спектров вибрации и спектра силы
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}
let src_force = gtl.analog_inputs[0]; // источник сигнала силы
// let src_force = filter_band_force; // источник фильтрованного сигнала силы
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.log.info("Количество ударов (ranges)", ranges.length);
for (let k = 0; k < ranges.length; k++) { // цикл по диапазонам
// Спектр амплитудный для цикла по всем каналам вибрации
ausp_vibr =
gtl.create_ausp(
{
"src": gtl.analog_inputs[signals[1].portNumber], // сырой сигнал вибрации
// "src": filter_band_vibr, // фильтрованный сигнал вибрации
"frequency": frequency,
"resolution": resolution,
"average": average,
"overlap": overlap,
"window": gtl.spec.rectangular,
"view": view
}
);
// Спектр фазовый для цикла по всем каналам вибрации
phase_vibr =
gtl.create_pfc(
{
"src0" : gtl.analog_inputs[signals[0].portNumber], // сигнал силы, относительно него вычисляем дельту фазы
// "src0" : filter_band_force, // фильтрованный сигнал силы, относительно него вычисляем дельту фазы
"src1" : gtl.analog_inputs[signals[1].portNumber], // сигнал вибрации
// "src1" : filter_band_vibr, // фильтрованный сигнал вибрации
"frequency" : frequency,
"resolution" : resolution,
"average" : average,
"overlap" : overlap,
"window" : gtl.spec.rectangular,
"view" : gtl.phase.deg,
"range" : gtl.phase.positive,
"is_single" : false
}
);
// Спектр силы (канал сигнала молотка)
// Канал_0
ausp_force =
gtl.create_ausp(
{
"src": gtl.analog_inputs[signals[0].portNumber], // сигнал силы
// "src": filter_band_force, // фильтрованный сигнал силы
"frequency": frequency,
"resolution": resolution,
"average": average,
"overlap": overlap,
"window": gtl.spec.rectangular,
"view": view
}
)
}
// gtl.log.info("Количество спектров вибрации", ausp_vibr.length);
// gtl.log.info("Количество спектров силы", ausp_force.length);
// let src_force = gtl.analog_inputs[0]; // источник сигнала силы
// // let src_force = filter_band_force; // источник фильтрованного сигнала силы
// 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.acq_time)
gtl.log.info("acq_time", gtl.acq_time)
gtl.log.info("Длина массива спектра", ausp_force.data.length);}
else { gtl.log.info('diagnostic', `Цикл диагностики ${tries} - ${true}`) }
/*
Математика обработки сигнала
*/
let n = ausp_force.data.length; // переменная для цикла по гармоникам спектра
// signals.length - переменная для цикла по каналам
// Канал[0] (ausp_force) - комплексно-сопряжённый вид
// меняется знак мнимой части на противоположный
// действительная часть без изменений
let ausp_force_imag_anti = []; // мнимая часть с противоположным знаком
for (let i = 0; i < n; i += 1)
{
ausp_force_imag_anti.push(ausp_force.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_vibr.add(
{
color: 0x34C924,
name: `Спектр канала - удар №${k+1}, g`,
x: ausp_vibr.resolution,
y: ausp_vibr.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 = []; // массив действительной части кросс-спектра
ausp_cross_imag = []; // массив мнимой части кросс-спектра
for (let i = 0; i < n; i += 1)
{
ausp_cross_real.push(ausp_vibr.real[i] * ausp_force.real[i] -
(ausp_vibr.imag[i] * ausp_force_imag_anti[i]));
ausp_cross_imag.push(ausp_vibr.real[i] * ausp_force_imag_anti[i] +
ausp_vibr.imag[i] * ausp_force.real[i]);
}
// Вычисление частотного отклика
// деление комплексных чисел по формуле:
// (a+bi)/(c+di)=(a*c-b*d)*(a*d+b*c)i
// (a*c-b*d) - действительная часть
// (a*d+b*c)i - мнимая часть
freq_resp_real = []; // массив массивов действительной части отклика
freq_resp_imag = []; // массив массивов мнимой части отклика
if (variant_freq_resp == 0)
{
// Вариант 1: Частотный отклик как деление кросс-спектра вибрации и силы на автоспектр силы
// (Канал[0] & Канал[j]) / Канал[0]
for (let i = 0; i < n; i += 1)
{
freq_resp_real.push((ausp_cross_real[i] * ausp_force.real[i] +
ausp_cross_imag[i] * ausp_force.imag[i]) /
(Math.pow(ausp_cross_real[i], 2) + Math.pow(ausp_force.imag[i], 2)));
freq_resp_imag.push((ausp_force.real[i] * ausp_cross_imag[i] -
ausp_cross_real[i] * ausp_force.imag[i]) /
(Math.pow(ausp_cross_real[i], 2) + Math.pow(ausp_force.imag[i], 2)));
}
} else {
// Вариант 2: Частотный отклик как деление автоспектра вибрации на автоспектр силы
// Канал[j] / Канал[0]
for (let i = 0; i < n; i += 1)
{
freq_resp_real.push((ausp_vibr.real[i] * ausp_force.real[i] +
ausp_vibr.imag[i] * ausp_force.imag[i]) /
(Math.pow(ausp_vibr.real[i], 2) + Math.pow(ausp_force.imag[i], 2)));
freq_resp_imag.push((ausp_force.real[i] * ausp_vibr.imag[i] -
ausp_vibr.real[i] * ausp_force.imag[i]) /
(Math.pow(ausp_vibr.real[i], 2) + Math.pow(ausp_force_vibr.imag[i], 2)));
}
}
// запись массива амплитуды и фазы
freq_resp_ampl = [];
freq_resp_phase = [];
for (let i = 0; i < n; i += 1)
{
// Условие отображения спектров частотного отклика в дБ или линейном виде
if (view_freq_resp == 0) {
freq_resp_ampl.push(Math.sqrt(Math.pow(freq_resp_real[i],2) + Math.pow(freq_resp_imag[i],2)));
} else if (view_freq_resp == 1) {
freq_resp_ampl.push(20 * Math.log10((Math.sqrt(Math.pow(freq_resp_real[i],2) + Math.pow(freq_resp_imag[i],2))) / Math.pow(10,-6)));
}
if (freq_resp_real[i] > 0 & freq_resp_imag[i] > 0)
{freq_resp_phase.push(Math.atan(freq_resp_imag[i] / freq_resp_real[i]) * 180 / Math.PI)}
else if (freq_resp_real[i] < 0 & freq_resp_imag[i] > 0)
{freq_resp_phase.push(180 - Math.atan(freq_resp_imag[i] / freq_resp_real[i]) * 180 / Math.PI)}
else if (freq_resp_real[i] < 0 & freq_resp_imag[i] < 0)
{freq_resp_phase.push(180 + Math.atan(freq_resp_imag[i] / freq_resp_real[i]) * 180 / Math.PI)}
else if (freq_resp_real[i] > 0 & freq_resp_imag[i] < 0)
{freq_resp_phase.push(360 - Math.atan(freq_resp_imag[i] / freq_resp_real[i]) * 180 / Math.PI)}
}
// корректировка угла для предотвращения превышения 360 град
for (let i = 0; i < freq_resp_phase.length; i += 1)
{
if (freq_resp_phase[i] >= 360)
{freq_resp_phase[i] = freq_resp_phase[i] - 360}
else {freq_resp_phase[i] = freq_resp_phase[i]}
}
plot_FreqResp_ampl.add(
{
color: 0x3b30b6,
name: `Амплитуда частотного отклика канала - удар №${k+1}, g/N`,
x: 1/lines,
y: freq_resp_ampl,
tags: ["Амплитуда частотного отклика, g/N"]
});
// plot_FreqResp_phase.add(
// {
// color: 0xb6306f,
// name: `Фаза частотного отклика канала - удар №${k+1}, °`,
// x: 1/lines,
// y: freq_resp_phase[j],
// tags: ["Фаза частотного отклика, °"]
// });
};
};
counter += 1;
plot_ausp_vibr.add(
{
color: 0xff00f7,
name: `Спектр силы (канал [0]) - удар №${k+1}, N`,
x: 1/lines,
y: ausp_force.data,
tags: [`Спектр силы (канал [0]) - удар №${k+1}, N`]
});
};
tries++;
// Усреднение откликов (амплитудных спектров) по всем ударам для каждого канала отдельно
// for (let j = 0; j < ausp_j.length; j++) { // цикл по каналам
// for (let k = 0; k < ranges.length; k++) { // цикл по ударам
// // freq_resp_ampl_avg[j] = freq_resp_ampl[k]
// // freq_resp_phase_avg[j] = [];
// // gtl.log.info("freq_resp_ampl[j][k].length", freq_resp_ampl[j][k].length);
// }
// }
// }