"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); // } // } // }