Экземпляры мигалок на Timer

Здравствуйте, уважаемые коллеги!
Есть необходимость создавать N блинкеров со своими периодами.

heater_pwm ("heater_2", 2000, 1000, "wb-gpio/A2_OUT");
heater_pwm ("heater_1", 2000, 1000, "wb-gpio/A1_OUT");

function heater_pwm (name, duty_ms, on_pulse_ms, relay){
	startTimer (function heater_on() { // таймер цикла 
		dev[relay] = 1;
		startTimer(function() {        // таймер "положительной" полуволны цикла
			dev[relay] = 0;
		}, on_pulse_ms);
		startTimer(heater_on, duty_ms); // самовызов функции таймера в бесконечном цикле
	}, duty_ms);  
}

Такая реализация работает, но не долго (секунд 30), потом зависает всё.
Судя по всему забивает память?
По описанию работы таймера я считал, что он должен удаляться из памяти по окончании действия своего экземпляра. Видимо у меня они не удаляются?
Помогите пожалуйста найти ошибку.

Кроме того если менять на ходу параметр вызова функции heater_pwm, то начинает работать хаотически не удаляя предыдущие установки этой функции.

Стал исследовать скрипт, приведенный в примере https://wirenboard.com/wiki/index.php/Движок_правил_wb-rules

 function makeMotionDetector(name, timeout_ms, detector_control, relay_control) {
  var motion_timer_id = null;
  defineRule(name, {
      whenChanged: "wb-gpio/" + detector_control,
      then: function(newValue, devName, cellName) {
          if (!newValue) {
              dev["wb-gpio"][relay_control] = 1;

              if (motion_timer_id) {                               //
                  clearTimeout(motion_timer_id);                   //
                  log(motion_timer_id);                            // этот код не работает
              }

              motion_timer_id = setTimeout(function() {
                  dev["wb-gpio"][relay_control] = 0;
                  motion_timer_id = null;
              }, timeout_ms);
          }
      }
  });
}

makeMotionDetector("motion_detector_1", 20000, "EXT1_DR1", "EXT2_R3A1");
makeMotionDetector("motion_detector_2", 10000, "EXT1_DR2", "EXT2_R3A2");
makeMotionDetector("motion_detector_3", 10000, "EXT1_DR3", "EXT2_R3A3");

Не вызывается ни при каком условии clearTimeout.

В таком виде заработало:

var cycle = 120* 1000 //ms
startTicker("blink_", cycle); // основной цикл переключений
PWM("PWM_1", "timer_1_1", "timer_2_1", 0, 60, "wb-gpio/A1_OUT"); // 0--------60________120--------180_____
PWM("PWM_2", "timer_2_1", "timer_2_2", 20, 60, "wb-gpio/A2_OUT");// 0___20--------80_________140------200_
PWM("PWM_3", "timer_3_1", "timer_3_1", 60, 60, "wb-gpio/A3_OUT");// 0________60--------120________180-----

function PWM(name, timer1Name, timer2Name, delay_sec, on_sec, relay) { // 
	defineRule(name, {
		when: function () { return timers.blink_.firing; }, // начало отсчета основного цикла
		then: function () { 
				timer1Name = startTimer(function() {        // установить смещение для начала отдельного ШИМ (рассинхронизация, чтобы не было бросков тока)
					dev[relay] = 1;
					timer2Name = startTimer(function() {    // установить длительность "положительной" полуволны
						dev[relay] = 0;
					}, on_sec * 1000);                      // длительность "положительной" полуволны
				}, delay_sec * 1000);						// смещение для начала отдельного ШИМ
			}
	});
}

Остался вопрос:
в теле функции есть два таймера. Для каждого необходимо задавать уникальные имена.
В этом примере эти уникальные имена задаются как аргументы функции.
Можно ли в теле функции сгенерировать уникальные имена (timer1Name, timer2Name), используя в качестве основы только аргумент “name”?

Все-таки даже в таком виде не работает!
Если поставить период в 1 сек., то через 30 секунд или около того, движок зависает.
При этом, если включить отладку, то в консоли с каждым запуском таймера выскакивает

starting timer: function anon() {/* source code */}

Как же победить это?

Flagman, добрый вечер! Первое, что бросается в глаза, это то, как вы вызываете startTimer. Обычно он вызывается как startTimer(name, milliseconds)
Вы в качестве имени передаете ему анонимную функцию, которая при этом выполняется, чтобы дать таймеру имя (вы получаете сообщение starting timer: function anon() {/* source code */})
Вместо function anon() {/* source code */} в обычном случае фигурирует время таймера.
startTimer возвращает undefined,поэтому присваивание типа
timer1Name = startTimer(…) не работает: после него переменная timer1Name получает значение undefined.
Попробуйте запускать именованные таймеры, ловить отдельными правилами firing и уже с их помощью менять состояние реле.

Спасибо уважаемому Kilpio за указанную ошибку!
Теперь я снова вернулся к проблеме отслеживания имени экземпляра таймера при его запуске.

var cycle = 1 * 1000 //ms 
startTicker("period", cycle); // основной цикл переключений
funcPWM("PWM_1", "timer_1_1", "wb-gpio/A1_OUT"); // 0--------60________120--------180_____
funcPWM("PWM_2", "timer_2_1", "wb-gpio/A2_OUT");// 0___20--------80_________140------200_
funcPWM("PWM_3", "timer_3_1", "wb-gpio/A3_OUT");// 0________60--------120________180-----


function funcPWM(name, timer1Name, relay) {
	defineRule(name + "CyclePeriod", {
	  when: function () { return timers.period.firing; }, // начало отсчета основного цикла
	  then: function () { 	
		  dev[relay] = 1;
		  startTimer(timer1Name, 0.125 * 1000); // длительность "положительной" полуволны  
	  }
	}); 
	defineRule(name + "switchOn", {
	  when: function() { return timers.timer1Name.firing;},
	  then: function() {    // установить длительность "положительной" полуволны
		dev[relay] = 0; // отключаем "положительную" полуволну после срабатывания таймера длительности включения
	  }
	});
}

log('New rule added');

Может быть знаете синтаксис передачи имени таймера в такую команду? timers.timer1Name.firing;
Если она находится внутри экземпляра функции или мне нужно сделать запись трех разных блоков функций?

timers[timer1Name]
Советую почитать про синтаксис JavaScript, например https://javascript.ru/tutorial/foundation/structure

1 лайк

Flagman, всё можно. Поясню на примере safonic, как это реализуемо.

  1. После вызова startTimer (имя_таймера, длительность) у нас появляется экземпляр объекта timers.имя_таймера. У этого объекта после создания по умолчанию есть
    свойство
    firing’ => “true” или “false”
    и метод
    ‘stop’ => “function anon() {/* source code */}”

  2. Мы можем добавить этому таймеру свойство “name” в тот момент, когда мы запускаем таймер и точно знаем его имя.Но если мы напишем
    timers.timer1Name.name=timer1Name;
    то timer1Name будет восприниматься как имя таймера, а не как переменная.
    Поэтому стоит записать так, как предложил safonic:
    startTimer(timer1Name, 0.125*1000); timers[timer1Name].name=timer1Name;

Тут происходит уже подстановка переменной, свойство name добавляется к нужному таймеру.

Соответственно, имя таймера внутри функции обработчика можно получить так:
dev[relay] = 0; // отключаем "положительную" полуволну после срабатывания таймера log(timers[timer1Name].name);

Это возможно, поскольку объект таймера не разрушается после его срабатывания (до перезагрузки движка). Количество таймеров не увеличивается, поскольку мы взводим одни и те же таймеры, так что проблем с памятью быть не должно.

1 лайк

safonik
Kilpio
Очень ценю вашу помощь!

В окончательном виде код получился такой:

var cycle = 3 * 1000;
heater_pwm ("heater_1", 0, 1, "wb-gpio/A1_OUT");
heater_pwm ("heater_2", 1, 1, "wb-gpio/A2_OUT");
heater_pwm ("heater_3", 2, 1, "wb-gpio/A3_OUT");

startTicker("period", cycle); // основной цикл переключений

function heater_pwm (name, delay_sec, on_sec, relay){
	var count = 0;											// контроль непрерывности работы
	var timer1Name = "timer1_" + name;						// задаем уникальное текстовое значение имени таймера внутри каждого экземпляра функции
	var timer2Name = "timer2_" + name;						// задаем уникальное текстовое значение имени таймера внутри каждого экземпляра функции
	defineRule("pwmCycle_" + name, {						// правило для запуска таймера на окончание "положительной" полуволны
		when: function () { return timers.period.firing; }, // начало отсчета основного цикла
		then: function () { 
			log(count++);
			startTimer(timer1Name, delay_sec * 1000);  		// запустили таймер на отключение "положительной" полуволны	
			timers[timer1Name].name = timer1Name;			// присвоили уникальное имя таймеру, чтбы его окончание отследить
		}
	});
	defineRule("pwmDelay_" + name, {
		when: function () { return timers[timer1Name].firing; }, // отследили срабатывание таймера на задержку начала "положительной" полуволны
		then: function () { 
			dev[relay] = 1;									// включили "положительную" полуволну
			startTimer(timer2Name, on_sec * 1000);  		// запустили таймер на отключение "положительной" полуволны	
			timers[timer2Name].name = timer2Name;			// присвоили уникальное имя таймеру, чтбы его окончание отследить
		}
	});	
		defineRule("pwmOn_" + name, {
		when: function () { return timers[timer2Name].firing; }, // отследили срабатывание таймера на окончание "положительной" полуволны
		then: function () {
			dev[relay] = 0;									// закончили "положительную" полуволну
		}
	});	
}

С такими параметрами можно использовать для “бегущих огней” :slight_smile:
Обнаружил, что если частоту переключения поставить слишком большую (период 0,5 сек), то рано или поздно контроллер зависает.

Может быть посоветуете диагностический инструмент, чтобы отследить какой параметр достигает предельного? Пока подозреваю температуру процессора, так как она в этом режиме переваливает за 62 градуса.

Рад, что мы смогли со всем этим разобраться!

Завешивание контроллера движком правил – неприятная проблема. Первое, что можно подозревать – это утечки памяти. Второе – перегрев. Я попробую воспроизвести проблему с вашим правилом, а вы можете смотреть в консоли за загрузкой процессора и потреблением памяти с помощью top, также интересно, появляется ли какая-то информация в отладочной консоли (USB Debug Console).

htop загрузку памяти и процессора указывает на приемлемые (в пределах 20-30%).

USB Debug Console это то что в WebUI внизу появляется при нажатии Rule debugging?

2018-12-19 12:43:31 19
2018-12-19 12:43:31starting timer: timer1_heater_1
2018-12-19 12:43:31 19
2018-12-19 12:43:31starting timer: timer1_heater_2
2018-12-19 12:43:31 19
2018-12-19 12:43:31starting timer: timer1_heater_3
2018-12-19 12:43:31starting timer: timer2_heater_1
2018-12-19 12:43:32starting timer: timer2_heater_2
2018-12-19 12:43:32starting timer: timer2_heater_3
2018-12-19 12:43:32 20
2018-12-19 12:43:32starting timer: timer1_heater_1
2018-12-19 12:43:32 20
2018-12-19 12:43:32starting timer: timer1_heater_2
2018-12-19 12:43:32 20
2018-12-19 12:43:32starting timer: timer1_heater_3
2018-12-19 12:43:32starting timer: timer2_heater_1
2018-12-19 12:43:32starting timer: timer2_heater_2
2018-12-19 12:43:32starting timer: timer2_heater_3

Я имел в виду то, что вы получите в терминале, подключившись к отладочной консоли USB/UART.
При падении иногда ядро успевает что-то интересное написать туда.

В этой консоли никакой информации нет.

Именно поэтому и хотел спросить про дополнительные инструменты, которые вы используете в работе в консоли, кроме htop.

var cycle = 3 * 1000;
var count = 0;                                                            // контроль непрерывности работы
// основной цикл переключений
setInterval(function () {
log(count++);
heater_pwm("heater_1", 0, 1, "wb-gpio/A1_OUT");
heater_pwm("heater_2", 1, 1, "wb-gpio/A2_OUT");
heater_pwm("heater_3", 2, 1, "wb-gpio/A3_OUT");
}, cycle);
function heater_pwm(name, delay_sec, on_sec, relay) {
setTimeout(function () {
    log("Включение положительной полуволны для " + name);
    dev[relay] = 1;                                                   // включили "положительную" полуволну
    setTimeout(function () {
        log("Отключение положительной полуволны для " + name);
        dev[relay] = 0;                                               // закончили "положительную" полуволну
    }, on_sec * 1000);                                        // запустили таймер на отключение "положительной" полуволны
}, delay_sec * 1000);                                         // запустили таймер на включение "положительной" полуволны                                        
}

Попробуйте так, без использования таймеров - будет тормозить меньше?

Такой вариант значительно изящнее.
Я его также использовал, только не догадался вызов функций осуществлять в периодическом цикле, а все-таки ловил его запуск.
Но все-равно правило “крутится” только определенное количество циклов от 50 до 100, а затем зависает.
К тому же, если во время работы программы поменять параметры, то одни таймеры накладываются на другие (оставшиеся от предыдущих параметров) и начинается хаос.
То есть не чистятся старые таймеры.

Это странно. С какими значениями параметров зависает?

Да. Как при изменении параметров останавливать запланированные setInterval и setTimeout идей нет. Тут похоже поможет только рестарт движка правил при изменении значений в скриптах, или делать на таймерах.
Лучше не менять значения в коде скриптов, а вынести это в виртуальные устройства и менять у них параметры через UI/MQTT, и в правилах при изменении этих значений останавливать старые таймеры и запускать новые с новыми значениями.

Да, не зная номер таймаута или интервала я не смог грохнуть прошлый экземпляр. Только ребут помогал. Решение “влоб”, к ни странно, помогло:
for (var i = 0;i<20;i++)
{
log("сбиваю интервал " + i);
clearInterval(i);
clearInterval(interval_id);
}

Присоединяюсь к вопросу. Есть ли возможность видеть наполнение памяти, например, или еще что-то, что позволит хотя бы таймеры отследить - появляются новые или нет? А то переполнят и завесят через месяц wb…

Таких инструментов, насколько мне известно, нет. Но помогает перезагрузка движка правил по крону )