Как сделать правило, работающее по времени

Разве что тут:

Ссылка на список - из документации

на этой странице есть какой-то огрызок кода с такой строкой и всё

// Control's type
    "type": "value",

Что это, зачем и почему непонятно.

Кроме того тут ещё есть вот это. Тоже непонятно. Вообще никаких пояснений

"precision": 0.1,

Если мне нужно 1 цифру после запятой, то надо написать сюда 0,1? Это так переводится?
Я вообще-то думал один знак после запятой “precision”: 1,, два знака “precision”: 2, и так далее

Даже на базовые вещи нет никакой справки

В общем теперь виртуальный термостат вроде работает. По крайней мере его свойства заполняются значениями, но основная его функция - включать и выключать ТЭН не работает.
Пока что для простоты условия со расписанием убрал из кода.

На скриншоте видно, что термостат включен, текущая температура 58,8, требуемая температура 70, гистерезис 5.
То есть ТЭН должен был включиться, но он выключен.

var time = new Date(); //Текущее время
var name = "BoilerTermostat/Name";
var onoff = "BoilerTermostat/ON_OFF"
var scheduleMode = "BoilerTermostat/ScheduleMode"
var setpoint = "BoilerTermostat/Setpoint"
var hysteresis = "BoilerTermostat/Hysteresis"
var BoilerTemperature = "wb-mai6_51/IN 1 P Temperature";
var BoilerRelay = "wb-mio-gpio_70:4/K1"; 




defineVirtualDevice("BoilerTermostat", {
    title: "Термостат бойлера",
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Бойлер",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "text",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 50,
          min: 20,
          max: 70,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      RelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Состояние реле",
        type: "text",
        value: false,
      },
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
      Time: {
        title: "Время",
        type: "value",
        value: "",
      }
  }
});



defineRule("BoilerTemperature", { 
  whenChanged: BoilerTemperature, 
  then: function (newValue, devName, cellName) {
    dev['BoilerTermostat/Temperature'] = newValue;
    SendTelegramMsg( 'BoilerTermostat/Temperature = ' + newValue );
  }
});


defineRule("BoilerRelay", { 
  whenChanged: BoilerRelay, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/RelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/RelayState'] = "выключено"; }
    SendTelegramMsg( 'BoilerTermostat/RelayState = ' + newValue );
  }

});



defineRule("cron minute timer", { // задание, которое выполняется каждую минуту
  when: cron("00 * * * * *"),
  then: function () {
    var currentHour = time.getHours();
    dev['BoilerTermostat/Time'] = currentHour;
    SendTelegramMsg( 'currentHour = ' + currentHour );
    }
});




defineRule("BoilerThermostatController", {
  whenChanged: [onoff, scheduleMode, setpoint, BoilerTemperature, hysteresis], 
  then: function (newValue, devName, cellName) {

 
    SendTelegramMsg('Температура ' + dev[BoilerTemperature] + ', Настройка ' + dev[setpoint] + ', Гистерезис ' + dev[hysteresis] + ', Положение реле ' + dev[BoilerRelay] + ', Термостат ' + dev[onoff] + ', Расписание ' + dev[scheduleMode]);
    log.info('Температура ' + dev[BoilerTemperature] + ', Настройка ' + dev[setpoint] + ', Гистерезис ' + dev[hysteresis] + ', Положение реле ' + dev[BoilerRelay] + ', Термостат ' + dev[onoff] + ', Расписание ' + dev[scheduleMode]);
    log.info(" time =", currentHour)

	if (dev[onoff]) {  // если термостат включён

      // тут ТЭН нельзя включать вручную
      // то есть в виджете НЕ  
      // выключатель реле "wb-mio-gpio_70:4/K1" должен быть спрятан 
      // или виден, но заблокирован



      	if ( dev[BoilerTemperature] < dev[setpoint] - dev[hysteresis] ) { // если температура датчика меньше уставки - гистерезис
        	dev[BoilerRelay] = true;
          SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Начался нагрев.');
      	}

      	if ( dev[BoilerTemperature] = dev[setpoint] ) { //если температура датчика достигла уставки выключить нагрев
        	dev[BoilerRelay] = false;
          SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен.');
      	}

        if ( dev[BoilerTemperature] > dev[setpoint] ) { //если температура датчика больше уставки выключить нагрев
          dev[BoilerRelay] = false;
          SendTelegramMsg( 'ВНИМАНИЕ ПЕРЕГРЕВ. ' + dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Превышение температуры. Необходимо проверить' + dev[name] + ' .');
        }



  }

  else { dev[BoilerRelay] = false; } // если термостат выключён, то нагрев не работает

  }

});



Не понятно. Что вы ожидаете, что происходит? Точнее - какого поведения от какой части кода ожидаете?
Я - откровенно плохой программист, поэтому для отладки пользуюсь, обычно выводом в лог, отслеживая как зхначения данных, их типы так и ветвления.

Сделал вывод в лог всех изменяемых значений правила BoilerThermostatController и там какие-то красные ошибки.
Что это?
При этом вроде необходимые данные прилетают в лог.

Насколько я понял значение currentHour не видно в правиле BoilerThermostatController.
Но оно мне там будет нужно для логики включения ТЭНа по расписанию.
Как его туда передать?

проблема действительно в этой переменной currentHour, которая не определена внутри правила BoilerThermostatController

При изменении кода вот таким образом всё стало работать.
Но тут получается, что время не вычисляется раз в минуту.
Оно вычисляется только в момент срабатывания правила BoilerThermostatController, то есть
whenChanged: [onoff, scheduleMode, setpoint, BoilerTemperature, hysteresis]

Может и не сильно страшно, потому как если подумать, то пока идёт нагрев постоянно изменяется температура.
Я посмотрел по логам примерно пару раз в минуту повышается на 0,1 градус и соответственно запускается правило.
Потом температура достигает предела и ТЭН отключается. В этот промежуток времени мне вообще не важно какой сейчас час.

Хотя хотелось бы, чтобы значение времени было внутри функции всегда свежим.

var time = new Date(); //Текущее время
var name = "BoilerTermostat/Name";
var onoff = "BoilerTermostat/ON_OFF"
var scheduleMode = "BoilerTermostat/ScheduleMode"
var setpoint = "BoilerTermostat/Setpoint"
var hysteresis = "BoilerTermostat/Hysteresis"
var BoilerTemperature = "wb-mai6_51/IN 1 P Temperature";
var BoilerRelay = "wb-mio-gpio_70:4/K1"; 



defineVirtualDevice("BoilerTermostat", {
    title: "Термостат бойлера",
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Бойлер",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "text",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 50,
          min: 20,
          max: 70,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      RelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Состояние реле",
        type: "text",
        value: false,
      },
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
      Time: {
        title: "Время",
        type: "value",
        value: "",
      }
  }
});



defineRule("BoilerTemperature", { 
  whenChanged: BoilerTemperature, 
  then: function (newValue, devName, cellName) {
    dev['BoilerTermostat/Temperature'] = newValue;
    log.info( 'BoilerTermostat/Temperature = ' + newValue );
  }
});


defineRule("BoilerRelay", { 
  whenChanged: BoilerRelay, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/RelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/RelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/RelayState = ' + newValue );
  }

});




defineRule("cron minute timer", { // задание, которое выполняется каждую минуту
  when: cron("00 * * * * *"),
  then: function () {
    var currentHour = time.getHours();
    dev['BoilerTermostat/Time'] = currentHour;
    log.info('Время ' + currentHour );
    }
});




defineRule("BoilerThermostatController", {
  whenChanged: [onoff, scheduleMode, setpoint, BoilerTemperature, hysteresis], 
  then: function (newValue, devName, cellName) {

  	if (dev[onoff]) { // если термостат включён

      if (dev[scheduleMode]) { // если термостат работает по расписанию
          // тут ТЭН нельзя включать вручную
          // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
          // должен быть спрятан или виден, но заблокирован для нажатия

        if (time.getHours() < 7 || time.getHours() >= 23) { // с 23 ночи до 7 утра

          heatingRules();
          SendTelegramMsg('Температура ' + dev[BoilerTemperature] + ', Настройка ' + dev[setpoint] + ', Гистерезис ' + dev[hysteresis] + ', Положение реле ' + dev[BoilerRelay] + ', Термостат ' + dev[onoff] + ', Расписание ' + dev[scheduleMode] + ', Время ' + time.getHours());
          log.info('Температура ' + dev[BoilerTemperature] + ', Настройка ' + dev[setpoint] + ', Гистерезис ' + dev[hysteresis] + ', Положение реле ' + dev[BoilerRelay] + ', Термостат ' + dev[onoff] + ', Расписание ' + dev[scheduleMode] + ', Время ' + time.getHours());

        }

        else  { // с 7 утра до 23 ночи
          dev[BoilerRelay] = false;
          SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен по времени.');
        }
      }

      else { // scheduleMode == false, то есть термостат работает вручную по кнопке
          // тут ТЭН надо включать вручную
          // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
          // должен быть виден и активен для нажатия

      }
    }

    else { // если термостат выключён, то нагрев не работает
      dev[BoilerRelay] = false; 
      SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Термостат выключён.');
      log.info( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Термостат выключён.');     
    } 

  }

});



function heatingRules() { 

  if ( dev[BoilerTemperature] <= dev[setpoint] - dev[hysteresis] ) { // если температура датчика меньше уставки - гистерезис
    dev[BoilerRelay] = true;
    SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Начался нагрев.');
    log.info( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Начался нагрев.');
  }
  
  else if ( dev[BoilerTemperature] = dev[setpoint] ) { //если температура датчика достигла уставки выключить нагрев
    dev[BoilerRelay] = false;
    SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен по настройке термостата.');
    log.info( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен по настройке термостата.');
  }
  
  else if ( dev[BoilerTemperature] > dev[setpoint] ) { //если температура датчика больше уставки выключить нагрев
    dev[BoilerRelay] = false;
    SendTelegramMsg( 'ВНИМАНИЕ ПЕРЕГРЕВ. ' + dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Превышение температуры. Необходимо проверить' + dev[name] + ' .');
    log.info( 'ВНИМАНИЕ ПЕРЕГРЕВ. ' + dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Превышение температуры. Необходимо проверить' + dev[name] + ' .');
  }
  
  else {  // тут ничего не делаем (либо идёт нагрев либо остывание)
  
  }
  
};

В итоге термостат работает не так как я ожидал. Вот основные правила управления ТЭНом.

dev[BoilerTemperature] <= dev[setpoint] - dev[hysteresis]
Включаем ТЭН, если температура датчика меньше настройки термостата минус гистерезис.
В моём примере нагрев начался при температуре датчика 58 < 70-11

dev[BoilerTemperature] = dev[setpoint]
Выключаем ТЭН, когда температура достигла настройки термостата.
А в моих логах видно, что ТЭН отключился, когда температура достигла (настройки термостата минус гистерезис), хотя у меня такого в коде нет.

Почему так случилось?

dev[BoilerTemperature] > dev[setpoint]

Ну и значение температуры виртуального термостата почему-то отличается от температуры датчика почти на целый градус, хотя изменения прилетают каждый 0,1 градус.

А переменная time где определяется?
Я вижу присвоение ей значения только при старте, все.

Тут ведь проверяется слева наприаво, получается что условие можно записать как (false - value) или (true - value). В общем вычисление следует выполнять до сравнения, закоючив в скобки.

Подправил.
И ещё придумал как завернуть весь код в cron, чтобы раз в минуту проверялась температура.
Теперь появилась новая проблема - скрипт отправляет мне в телеграм 15 сообщений в минуту.
Совсем отключать уведомления я не хочу, но хочется чтобы они были не такими тупыми.
Что-то типа если минуту назад это сообщение уже было, то не надо слать его повторно.

var name = "BoilerTermostat/Name";
var onoff = "BoilerTermostat/ON_OFF";
var scheduleMode = "BoilerTermostat/ScheduleMode";
var setpoint = "BoilerTermostat/Setpoint";
var hysteresis = "BoilerTermostat/Hysteresis";
var BoilerTemperature = "wb-mai6_51/IN 1 P Temperature";
var WBBoilerRelay = "wb-mio-gpio_70:4/K1"; 
var CirculationTimeout = 10000; // 10 секунд
var CirculationTopics = ["wb-mr6c_120/Input 4 Double Press Counter",    "wb-mr6c_126/Input 5 Double Press Counter"];
var CirculationNames =  ["на первом этаже", "на втором этаже"];



defineVirtualDevice("BoilerTermostat", {
    title: "Термостат бойлера",
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Бойлер",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "value",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 50,
          min: 20,
          max: 70,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      WBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Состояние реле WB",
        type: "text",
        value: false,
      },
      ABBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Состояние реле ABB",
        type: "text",
        value: false,
      },
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
  }
});



defineRule("BoilerTemperature", { 
  whenChanged: BoilerTemperature, 
  then: function (newValue, devName, cellName) {
    dev['BoilerTermostat/Temperature'] = newValue;
    log.info( 'BoilerTermostat/Temperature = ' + newValue );
  }
});


defineRule("WBBoilerRelay", { 
  whenChanged: WBBoilerRelay, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/WBRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/WBRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/WBRelayState = ' + newValue );
  }

});




defineRule("cron minute timer", { // задание, которое выполняется каждую минуту
  when: cron("00 * * * * *"),
  then: function () {

    BoilerController();

    }
});




function BoilerController() {

  defineRule( {
    whenChanged: [onoff, scheduleMode, setpoint, BoilerTemperature, hysteresis], 
    then: function (newValue, devName, cellName) {

      time = new Date(); //Текущее время

    	if (dev[onoff]) { // если термостат включён
            SendTelegramMsg( 'Термостат включён');
            log.info( 'Термостат включён');

        if (dev[scheduleMode]) { // если термостат работает по расписанию
            // тут ТЭН нельзя включать вручную
            // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
            // должен быть спрятан или виден, но заблокирован для нажатия
            SendTelegramMsg('scheduleMode ON - нагрев по расписанию ночью');
            log.info('scheduleMode ON - нагрев по расписанию ночью');

          if (time.getHours() < 7 || time.getHours() >= 23) { // с 23 ночи до 7 утра
            SendTelegramMsg('Началось ночное время нагрева по расписанию');
            log.info('Началось ночное время нагрева по расписанию');
            heatingRules();
          }

          else  { // с 7 утра до 23 ночи
            dev[WBBoilerRelay] = false;
            SendTelegramMsg( 'Время ' + time.getHours() + '. heatingRules stopped в Южном. Ночь закончилась.');
            log.info( 'Время ' + time.getHours() + '. heatingRules stopped в Южном. Ночь закончилась.');
          }
        }

        else { // scheduleMode == false, то есть термостат работает вручную по кнопке
            // тут ТЭН надо включать вручную
            // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
            // должен быть виден и активен для нажатия
            SendTelegramMsg( 'scheduleMode OFF - ручной режим');
            log.info( 'scheduleMode OFF - ручной режим');
            heatingRules();
        }
      }

      else { // если термостат выключён, то нагрев не работает
        dev[WBBoilerRelay] = false; 
        SendTelegramMsg( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Термостат выключён.');
        log.info( dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + '. Нагрев остановлен. Термостат выключён.');     
      } 

    }

  });

}


function heatingRules() { 

  SendTelegramMsg('Время ' + time.getHours() + '. heatingRules started в Южном');
  log.info('Время ' + time.getHours() + '. heatingRules started в Южном');


  if ( dev[BoilerTemperature] <= (dev[setpoint] - dev[hysteresis]) ) { // если температура датчика меньше уставки - гистерезис
    dev[WBBoilerRelay] = true;
    SendTelegramMsg(dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + ' <= ' + dev[setpoint] + '-' + dev[hysteresis] + 
      '. Положение реле WB ' + dev[WBBoilerRelay] + '. Начался нагрев.');
    log.info(dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + ' <= ' + dev[setpoint] + '-' + dev[hysteresis] + 
      '. Положение реле WB ' + dev[WBBoilerRelay] + '. Начался нагрев.');
  }

  else if ( dev[BoilerTemperature] > (dev[setpoint] - dev[hysteresis]) || dev[BoilerTemperature] < dev[setpoint] ) { 
  // тут ничего не делаем (либо идёт нагрев либо остывание) - просто пишем об этом сообщение
    SendTelegramMsg('Тут ничего не делаем. Температура достигла ' + dev[BoilerTemperature] + ' . else if ' + dev[BoilerTemperature] + ' > ' + (dev[setpoint] + '-' + dev[hysteresis]) );
    log.info('Тут ничего не делаем. Температура достигла ' + dev[BoilerTemperature] + ' . else if ' + dev[BoilerTemperature] + ' > ' + (dev[setpoint] + '-' + dev[hysteresis]) );

  }
  
  else if ( dev[BoilerTemperature] = dev[setpoint] ) { // если температура датчика достигла уставки выключить нагрев
    dev[WBBoilerRelay] = false;
    SendTelegramMsg(dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + ' = ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
    log.info(dev[name] + ' в Южном. Температура ' + dev[BoilerTemperature] + ' = ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
  }
  
  else if ( dev[BoilerTemperature] > dev[setpoint] ) { // если температура датчика больше уставки выключить нагрев
    dev[WBBoilerRelay] = false;
    SendTelegramMsg('ВНИМАНИЕ ПЕРЕГРЕВ. ' + dev[name] + 'а в Южном. Температура ' + dev[BoilerTemperature] + ' > ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
    log.info('ВНИМАНИЕ ПЕРЕГРЕВ. ' + dev[name] + 'а в Южном. Температура ' + dev[BoilerTemperature] + ' > ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
  }
    
  else {  // на всякий случай во всех других ситуациях вырубаем ТЭН
    dev[WBBoilerRelay] = true;
    SendTelegramMsg('Тут ничего не делаем. Температура ' + dev[BoilerTemperature] + ' . Последний else.');
    log.info('Тут ничего не делаем. Температура ' + dev[BoilerTemperature] + ' . Последний else.');
  }
  
}




Изменил скрипт вот так, чтобы было меньше дублированных оповещений.
Теперь вообще не работает.

/*   бойлер   */
var BoilerName = "BoilerTermostat/Name";
var BoilerONOFF = "BoilerTermostat/ON_OFF";
var BoilerScheduleMode = "BoilerTermostat/ScheduleMode";
var BoilerTemperature = "wb-mai6_51/IN 1 P Temperature";
var BoilerSetpoint = "BoilerTermostat/Setpoint";
var BoilerHysteresis = "BoilerTermostat/Hysteresis";
var BoilerPressure = 4;  // пока не поставил датчик давления на бойлер
var BoilerMinPressure = 2;
var BoilerMaxPressure = 5;
var BoilerMainRelayState = "wb-mio-gpio_70:2/IN9"; // если подаётся ток с клеммы S термореле бойлера на реле WB
var BoilerWBRelayState = "wb-mio-gpio_70:4/K1"; 
var BoilerABBRelayState = "wb-mio-gpio_70:2/IN11";
var BoilerVirtualPumpRelayState = true; // виртуальный насос чисто потому что в функции heatingRules нужен такой аргумент для унификации с котлом

var BoilerCirculationTimeout = 10000; // 10 секунд
var BoilerCirculationRelay = "wb-mr6cu_76/K4";
var BoilerCirculationButtons = ["wb-mr6c_120/Input 4 Double Press Counter",    "wb-mr6c_126/Input 5 Double Press Counter"];
var BoilerCirculationNames =  ["на первом этаже", "на втором этаже"];


/*   котёл   */
var KotelName = "KotelTermostat/Name";
var KotelONOFF = "KotelTermostat/ON_OFF";
var KotelScheduleMode = "KotelTermostat/ScheduleMode";
var KotelTemperature = "wb-mai6_51/IN 1 N Temperature";
var KotelSetpoint = "KotelTermostat/Setpoint";
var KotelHysteresis = "KotelTermostat/Hysteresis";
var KotelPressure = "wb-mai6_51/IN 5 P Value";
var KotelMinPressure = 1;
var KotelMaxPressure = 2;
var KotelMainRelayState = "wb-mio-gpio_70:2/IN1";   // если подаётся ток с предельного термореле котла на реле WB
var KotelWBRelayState = "wb-mio-gpio_70:4/K2";
var KotelABBRelayState = "wb-mio-gpio_70:2/IN12";
var KotelPumpRelayState = "wb-mr6cu_76/K2";



defineVirtualDevice("KotelTermostat", { 
    title: "Термостат котла",  
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Котёл",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "value",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 70,
          min: 40,
          max: 90,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      Pressure: {
        title: "Давление",
        type: "value",
        value: "",
        units: "bar",
        precision: 2,
      },
      MainRelayState: { // состояние термореле в котле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Термореле в котле",
        type: "text",
        value: false,
      },  
      WBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле WB",
        type: "text",
        value: false,
      },
      ABBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле ABB",
        type: "text",
        value: false,
      },
      PumpRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-циркуляция (ВКЛ)
        title: "Циркуляционный насос",
        type: "text",
        value: false,
      },
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
  }
});




defineVirtualDevice("BoilerTermostat", {
    title: "Термостат бойлера",
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Бойлер",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "value",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 50,
          min: 20,
          max: 70,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      MainRelayState: { // состояние термореле в бойлере 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Термореле в бойлере",
        type: "text",
        value: false,
      },  
      WBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле WB",
        type: "text",
        value: false,
      },
      ABBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле ABB",
        type: "text",
        value: false,
      },
   
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
  }
});


/*    ПРАВИЛА УПРАВЛЕНИЯ КОТЛОМ   */


// запись давления в ячейку виртуального устройства
defineRule("KotelPressureRule", { 
  whenChanged: KotelPressure, 
  then: function (newValue, devName, cellName) {
    dev['KotelTermostat/Pressure'] = newValue;
    log.info( 'KotelTermostat/Pressure = ' + newValue );
  }
});


// запись температуры в ячейку виртуального устройства
defineRule("KotelTemperatureRule", { 
  whenChanged: KotelTemperature, 
  then: function (newValue, devName, cellName) {
    dev['KotelTermostat/Temperature'] = newValue;
    log.info( 'KotelTermostat/Temperature = ' + newValue );
  }
});

// запись состояния главного термореле бойлера в ячейку виртуального устройства
defineRule("KotelMainRelayStateRule", { 
  whenChanged: KotelMainRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/MainRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/MainRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/MainRelayState = ' + newValue );
  }
});

// запись состояния реле WB в ячейку виртуального устройства
defineRule("KotelWBRelayStateRule", { 
  whenChanged: KotelWBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/WBRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/WBRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/WBRelayState = ' + newValue );
  }
});

// запись состояния реле ABB в ячейку виртуального устройства
defineRule("KotelABBRelayStateRule", { 
  whenChanged: KotelABBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/ABBRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/ABBRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/ABBRelayState = ' + newValue );
  }
});

// запись состояния реле насоса в ячейку виртуального устройства
defineRule("KotelPumpRelayStateRule", { 
  whenChanged: KotelPumpRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/PumpRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/PumpRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/PumpRelayState = ' + newValue );
  }
});


/*    КОНЕЦ ПРАВИЛ УПРАВЛЕНИЯ КОТЛОМ   */




/*    ПРАВИЛА УПРАВЛЕНИЯ БОЙЛЕРОМ   */

// запись температуры в ячейку виртуального устройства
defineRule("BoilerTemperatureRule", { 
  whenChanged: BoilerTemperature, 
  then: function (newValue, devName, cellName) {
    dev['BoilerTermostat/Temperature'] = newValue;
    log.info( 'BoilerTermostat/Temperature = ' + newValue );
  }
});

// запись состояния главного термореле бойлера в ячейку виртуального устройства
defineRule("BoilerMainRelayStateRule", { 
  whenChanged: BoilerMainRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/MainRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/MainRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/MainRelayState = ' + newValue );
  }
});

// запись состояния реле WB в ячейку виртуального устройства
defineRule("BoilerWBRelayStateRule", { 
  whenChanged: BoilerWBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/WBRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/WBRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/WBRelayState = ' + newValue );
  }
});

// запись состояния реле ABB в ячейку виртуального устройства
defineRule("BoilerABBRelayStateRule", { 
  whenChanged: BoilerABBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/ABBRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/ABBRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/ABBRelayState = ' + newValue );
  }
});


// запустить насос циркуляции ГВС при двойном нажатии выключателя в ванной или в сортире
defineRule("BoilerCirculationControlRule", {
  whenChanged: BoilerCirculationButtons,
  then: function (newValue, devName, cellName) {
    var index = BoilerCirculationButtons.indexOf(devName + '/' + cellName);
    if (newValue) {
      dev['BoilerCirculationRelay']=true;
      setTimeout(function (){
        dev['BoilerCirculationRelay']=false;
      }, BoilerCirculationTimeout);
      SendTelegramMsg('В Южном кто-то пошёл мыться ' + BoilerCirculationNames[index]);
    }
  }
});

/*    КОНЕЦ ПРАВИЛ УПРАВЛЕНИЯ БОЙЛЕРОМ   */



/*

Режим работы котла
1) Проверка давления. 
Нормальный режим 1,8...2,2 бар. 
Если стало 1,5 или  2,5, то высылаем смс в телегу. 
Если стало 1 или 3, то останавливаем котёл. 
2) Проверка температуры
3) Проверка времени
Котёл включается с 23-00 по 07-00 при температуре датчика менее 70 градусов и отключается при 90.

Режим работы бойлера 
Насос бойлера работает всё время при условии:
1) Температура в ТА больше температуры бойлера
2) Температура включения насоса 50, выключение при 60.

Режим работы полов 
1) Проверка давления. 
Нормальный режим 1,8...2,2 бар. 
Если стало 1,5 или  2,5, то высылаем смс в телегу. 
Если стало 1 или 3, то выключаем  насос. 
2) Насос включается в 5 утра и выключается в 7 вечера.

*/




function heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState) { 

  SendTelegramMsg('Время ' + time.getHours() + '. heatingRules started в Южном');
  log.info('Время ' + time.getHours() + '. heatingRules started в Южном');


      if (pressure <= MinPressure) {
          dev[WBRelayState] = false;
          dev[PumpRelayState] = false;
          log.info('В Южном критически низкое давление в ' + name + ' ' + pressure + ' бар. ' + name + ' отключен.');
          SendTelegramMsg('В Южном критически низкое давление в ' + name + ' ' + pressure + ' бар. ' + name + ' отключен.');

      }

      else if (pressure >= MaxPressure) {
          dev[WBRelayState] = false;
          dev[PumpRelayState] = false;
          log.info('В Южном очень высокое давление в ' + name + ' ' + pressure + ' бар. ' + name + ' отключен.');
          SendTelegramMsg('В Южном очень высокое давление в ' + name + ' ' + pressure + ' бар. ' + name + ' отключен.');
      }

/* ===================   тут работаем   =============================  */
      else if (pressure > MinPressure && pressure < MaxPressure) {  // pressure > MinPressure   И   pressure < MaxPressure 

          if ( dev[temperature] <= (dev[setpoint] - dev[hysteresis]) ) { // если температура датчика меньше, чем (уставка минус гистерезис)
              // доп проверка ЕСЛИ реле на момент проверки было выключено, то включаем его и шлём уведомление
              // а ЕСЛИ реле на момент проверки было уже включено, то ничего не делаем
              if ( dev[WBRelayState] == false ) {
                  dev[WBRelayState] = true;
                  dev[PumpRelayState] = true;
                  log.info(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' <= ' + dev[setpoint] + '-' + dev[hysteresis] + 
                    '. Положение реле WB ' + dev[WBRelayState] + '. Начался нагрев.');
                  SendTelegramMsg(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' <= ' + dev[setpoint] + '-' + dev[hysteresis] + 
                    '. Положение реле WB ' + dev[WBRelayState] + '. Начался нагрев.');
              }
          }

          else if ( dev[temperature] > (dev[setpoint] - dev[hysteresis]) && dev[temperature] < dev[setpoint] ) {
          // тут ничего не делаем (либо идёт нагрев либо остывание)
            dev[PumpRelayState] = true;
          }
          
          else if ( dev[temperature] = dev[setpoint] ) { // если температура датчика достигла уставки выключить нагрев
              // доп проверка ЕСЛИ реле на момент проверки было включено, то выключаем его и шлём уведомление
              // а ЕСЛИ реле на момент проверки было уже выключено, то ничего не делаем
              if ( dev[WBRelayState] == true ) {
                  dev[WBRelayState] = false;
                  dev[PumpRelayState] = true;
                  SendTelegramMsg(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' = ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
                  log.info(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' = ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
              }
          }
          
          else if ( dev[temperature] > dev[setpoint] ) { // если температура датчика больше уставки выключить нагрев
                  dev[WBRelayState] = false;
                  dev[PumpRelayState] = true;
                  SendTelegramMsg(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' > ' + dev[setpoint] + ' (настройка термостата).');
                  log.info(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' > ' + dev[setpoint] + ' (настройка термостата).');
          }
            
          else {  // на всякий случай во всех других ситуациях вырубаем ТЭН
              if ( dev[WBRelayState] == true ) {
                  dev[WBRelayState] = false;
                  SendTelegramMsg('Последний else по температуре сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
                  log.info('Последний else по температуре сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
              }
          }

      }
/* ===================   конец работы   =============================  */

      else {  // на всякий случай во всех других ситуациях вырубаем ТЭН, например WBRelayState undefined

          if ( dev[WBRelayState] == true ) {
              dev[WBRelayState] = false;
              SendTelegramMsg('Последний else по давлению сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
              log.info('Последний else по давлению сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
          }

      }
  
}




function TermostatController(name, onoff, scheduleMode, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState) {

  defineRule( {
    whenChanged: [onoff, scheduleMode, setpoint, hysteresis, temperature, pressure], 
    then: function (newValue, devName, cellName) {

      time = new Date(); //Текущее время


      if (dev[onoff]) { // если термостат включён
            log.info( 'Термостат включён');

        if (dev[scheduleMode]) { // если термостат работает по расписанию
            // тут ТЭН нельзя включать вручную
            // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
            // должен быть спрятан или виден, но заблокирован для нажатия
            log.info('scheduleMode ON - нагрев по расписанию ночью');

          if (time.getHours() < 7 || time.getHours() == 23) { // время меньше  7 часов  ИЛИ  равно 23 часам, то есть с 23 ночи до 7 утра
            log.info('Началось ночное время нагрева по расписанию');
            heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState);
          }

          else  { // с 7 утра до 23 ночи
              if ( dev[WBRelayState] == true ) {
                  dev[WBRelayState] = false;
                  SendTelegramMsg( 'Время ' + time.getHours() + '. heatingRules stopped в Южном. Ночь закончилась.');
                  log.info( 'Время ' + time.getHours() + '. heatingRules stopped в Южном. Ночь закончилась.');
              }
          }
        }

        else { // scheduleMode == false, то есть термостат работает вручную по кнопке
            // тут ТЭН надо включать вручную
            // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
            // должен быть виден и активен для нажатия
            log.info( 'scheduleMode OFF - ручной режим');
            heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState);
        }
      }

      else { // если термостат выключён, то нагрев не работает

        if ( dev[WBRelayState] == true ) {
            dev[WBRelayState] = false; 
            SendTelegramMsg( dev[name] + ' в Южном. Температура '  + name + ' ' + dev[temperature] + '. Нагрев остановлен. Термостат выключён.');
            log.info( dev[name] + ' в Южном. Температура '  + name + ' ' + dev[temperature] + '. Нагрев остановлен. Термостат выключён.');    
        }

      } 

    }

  });

}






defineRule("cron minute timer", { // задание, которое выполняется каждую минуту
  when: cron("00 * * * * *"),
  then: function () {

    TermostatController(BoilerName, BoilerONOFF, BoilerScheduleMode, BoilerTemperature, BoilerSetpoint, BoilerHysteresis, BoilerPressure, BoilerMinPressure, BoilerMaxPressure, BoilerMainRelayState, BoilerWBRelayState, BoilerABBRelayState, BoilerVirtualPumpRelayState);

    TermostatController(KotelName, KotelONOFF, KotelScheduleMode, KotelTemperature, KotelSetpoint, KotelHysteresis, KotelPressure, KotelMinPressure, KotelMaxPressure, KotelMainRelayState, KotelWBRelayState, KotelABBRelayState, KotelPumpRelayState);



    }
});

выдаёт вот такие ошибки

Oct 02 10:08:55 wirenboard-AM363YRR wb-mqtt-db[26775]: WARNING: [conventions] converting empty value to boolean "false"
Oct 02 10:08:55 wirenboard-AM363YRR wb-mqtt-db[26775]: WARNING: [conventions] converting empty value to boolean "false"
Oct 02 10:08:55 wirenboard-AM363YRR wb-mqtt-db[26775]: WARNING: [conventions] converting empty value to boolean "false"
Oct 02 10:08:55 wirenboard-AM363YRR wb-mqtt-db[26775]: WARNING: [conventions] converting empty value to boolean "false"
Oct 02 10:08:55 wirenboard-AM363YRR wb-mqtt-db[26775]: WARNING: [conventions] converting empty value to boolean "false"
Oct 02 10:08:55 wirenboard-AM363YRR wb-mqtt-db[26775]: WARNING: [conventions] converting empty value to boolean "false"
Oct 02 10:08:56 wirenboard-AM363YRR wb-rules[16332]: INFO: reloading file: /etc/wb-rules/heating.js
Oct 02 10:09:00 wirenboard-AM363YRR wb-rules[16332]: ERROR: [rule error] ECMAScript error: Error: invalid whenChanged spec
                                                             transformWhenChangedItem /usr/share/wb-rules-system/scripts/lib.js:209 preventsyield
                                                             map  native strict preventsyield
                                                             anon /usr/share/wb-rules-system/scripts/lib.js:230 preventsyield
                                                             forEach  native strict preventsyield
                                                             anon /usr/share/wb-rules-system/scripts/lib.js:243
                                                             TermostatController /etc/wb-rules/heating.js:480
                                                             anon /etc/wb-rules/heating.js:493 preventsyield
                                                             call  native strict preventsyield
                                                             anon /usr/share/wb-rules-system/scripts/lib.js:240 preventsyield

Так прямо в тексте сообщения и написано.

defineRule( {

Я посмотрел на строку с указанным номером…

Где это написано?
В моём сообщении такого нигде нет

Тут:

И что с ним делать?
Я ошибок там не вижу.

Вот тут /t/problema-s-pravilom/10807/20 на похожую ошибку в логе был совет сделать правило анонимным. У меня уже анонимное, ошибка та же самая.

Причём движок не мешает сохранять это правило. При сохранении ошибок не выводится. Ошибки появляются только в процессе работы.

Ну посмотрите что именно передается в whenChanged.
Все ли значения являются (нет) именами топиков? Вот то что прямо сразу видно: pressure - это value.

Я заменил в шапке скрипта значение BoilerPressure

var BoilerPressure = "wb-mai6_51/IN 5 P Value"; 
var BoilerMinPressure = 0.8;
var BoilerMaxPressure = 5;

Ошибки прекратились

Но правило не работает.
Термостат включён.
Работа по расписанию включена (с 23 до 7 утра).
Время 23-27.
Давление = 1, что больше 0.8 и меньше 5.
На термостате выставлено 66 градусов, текущая температура 50. Гистерезис 5.
При этом реле wb-mio-gpio_70:4/K1 должно включиться, а оно не включается.

Я добавил точки оповещений и выяснил, что правило не работает, потому что в аргумент pressure функции heatingRules передаётся текст wb-mai6_51/IN 5 P Value и поэтому ни одно из числовых условий не срабатывает.

Почему так происходит?

heatingRules вызывается из 447, 464 строки

            heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState);

В обоих случаях в пятый аргумент передается значение pressure которое нициируется строкой 493, 495 значениями value 4 и string “wb-mai6_51/IN 5 P Value”. значение строковой переменной определяется в 29 строке.

Нашёл ошибку - надо было вместо pressure подставлять dev[pressure].
Тестирую дальше.
Правило начало работать.

вот обновлённый код скрипта

var HeatingStart = 23;
var HeatingStop = 7;
/*   бойлер   */
var BoilerName = "BoilerTermostat/Name";
var BoilerONOFF = "BoilerTermostat/ON_OFF";
var BoilerScheduleMode = "BoilerTermostat/ScheduleMode";
var BoilerTemperature = "wb-mai6_51/IN 1 P Temperature";
var BoilerSetpoint = "BoilerTermostat/Setpoint";
var BoilerHysteresis = "BoilerTermostat/Hysteresis";
var BoilerPressure = "wb-mai6_51/IN 5 P Voltage";  // пока не поставил датчик давления на бойлер ТУТ ДАВЛЕНИЕ КОТЛА
/*
0.5 bar = 0.667 В
0.8 bar = 0.767 В
1 bar = 0.833 В
2 bar = 1.167 В
2.8 bar = 1.433 В
3 bar = 1.500 В
4 bar = 1.833 В
6 bar = 2.500 В
*/
var BoilerMinPressure = 0.667;  //  0.667 В = 0.5 bar // потом заменить на 0.833 В = 1 bar
var BoilerMaxPressure = 2.5;    //  2.500 В = 6 bar
var BoilerMainRelayState = "wb-mio-gpio_70:2/IN9"; // если подаётся ток с клеммы S термореле бойлера на реле WB
var BoilerWBRelayState = "wb-mio-gpio_70:4/K1"; 
var BoilerABBRelayState = "wb-mio-gpio_70:2/IN11";
var BoilerVirtualPumpRelayState = true; // виртуальный насос чисто потому что в функции heatingRules нужен такой аргумент для унификации с котлом

var BoilerCirculationTimeout = 10000; // 10 секунд
var BoilerCirculationRelay = "wb-mr6cu_76/K4";
var BoilerCirculationButtons = ["wb-mr6c_120/Input 4 Double Press Counter",    "wb-mr6c_126/Input 5 Double Press Counter"];
var BoilerCirculationNames =  ["на первом этаже", "на втором этаже"];


/*   котёл   */
var KotelName = "KotelTermostat/Name";
var KotelONOFF = "KotelTermostat/ON_OFF";
var KotelScheduleMode = "KotelTermostat/ScheduleMode";
var KotelTemperature = "wb-mai6_51/IN 1 N Temperature";
var KotelSetpoint = "KotelTermostat/Setpoint";
var KotelHysteresis = "KotelTermostat/Hysteresis";
var KotelPressure = "wb-mai6_51/IN 5 P Voltage";
var KotelMinPressure = 0.667;  //  0.667 В = 0.5 bar
var KotelMaxPressure = 1.433;  //  1.433 В = 2.8 bar
var KotelMainRelayState = "wb-mio-gpio_70:2/IN1";   // если подаётся ток с предельного термореле котла на реле WB
var KotelWBRelayState = "wb-mio-gpio_70:4/K2";
var KotelABBRelayState = "wb-mio-gpio_70:2/IN12";
var KotelPumpRelayState = "wb-mr6cu_76/K2";



defineVirtualDevice("KotelTermostat", { 
    title: "Термостат котла",  
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Котёл",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "value",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 70,
          min: 40,
          max: 90,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      Pressure: {
        title: "Давление",
        type: "value",
        value: "",
        units: "bar",
        precision: 2,
      },
      MainRelayState: { // состояние термореле в котле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Термореле в котле",
        type: "text",
        value: false,
      },  
      WBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле WB",
        type: "text",
        value: false,
      },
      ABBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле ABB",
        type: "text",
        value: false,
      },
      PumpRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-циркуляция (ВКЛ)
        title: "Циркуляционный насос",
        type: "text",
        value: false,
      },
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
  }
});




defineVirtualDevice("BoilerTermostat", {
    title: "Термостат бойлера",
    cells: {
      Name: {
        title: "Название",
        type: "text",
        value: "Бойлер",
      }, 
      ON_OFF: { // активация термостата 0-выкл 1-включен
        title: "Выключатель",
        type: "switch",
        value: false,
      }, 
      ScheduleMode: { // режим 0-ручной 1-по расписанию с 23 вечера до 7 утра
        title: "Работа по расписанию",
        type: "switch",
        value: false,
      },
      Temperature: {
        title: "Температура",
        type: "value",
        value: "",
        units: "deg C",
        precision: 2,
      },
      Setpoint: { // уставка
        title: "Уставка",
        type: "range",
        value: 50,
          min: 20,
          max: 70,
        readonly: false,
      },
      Hysteresis: {
        title: "Гистерезис",
        type: "range",
        value: 10,
          min: 5,
          max: 20,
        readonly: false,     
      },
      Pressure: {
        title: "Давление",
        type: "value",
        value: "",
        units: "bar",
        precision: 2,
      },
      MainRelayState: { // состояние термореле в бойлере 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Термореле в бойлере",
        type: "text",
        value: false,
      },  
      WBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле WB",
        type: "text",
        value: false,
      },
      ABBRelayState: { // состояние реле 0-ожидание (ВЫКЛ) 1-нагрев (ВКЛ)
        title: "Реле ABB",
        type: "text",
        value: false,
      },
   
      Lock: { // блокировка в визуализации 0-снята 1-заблокирована
        title: "Блокировка изменений",
        type: "switch",
        value: false,
      },
  }
});


/*    ПРАВИЛА УПРАВЛЕНИЯ КОТЛОМ   */


// запись давления в ячейку виртуального устройства
defineRule("KotelPressureRule", { 
  whenChanged: KotelPressure, 
  then: function (newValue, devName, cellName) {
    dev['KotelTermostat/Pressure'] = newValue;
    log.info( 'KotelTermostat/Pressure = ' + newValue );
  }
});


// запись температуры в ячейку виртуального устройства
defineRule("KotelTemperatureRule", { 
  whenChanged: KotelTemperature, 
  then: function (newValue, devName, cellName) {
    dev['KotelTermostat/Temperature'] = newValue;
    log.info( 'KotelTermostat/Temperature = ' + newValue );
  }
});

// запись состояния главного термореле бойлера в ячейку виртуального устройства
defineRule("KotelMainRelayStateRule", { 
  whenChanged: KotelMainRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/MainRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/MainRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/MainRelayState = ' + newValue );
  }
});

// запись состояния реле WB в ячейку виртуального устройства
defineRule("KotelWBRelayStateRule", { 
  whenChanged: KotelWBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/WBRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/WBRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/WBRelayState = ' + newValue );
  }
});

// запись состояния реле ABB в ячейку виртуального устройства
defineRule("KotelABBRelayStateRule", { 
  whenChanged: KotelABBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/ABBRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/ABBRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/ABBRelayState = ' + newValue );
  }
});

// запись состояния реле насоса в ячейку виртуального устройства
defineRule("KotelPumpRelayStateRule", { 
  whenChanged: KotelPumpRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['KotelTermostat/PumpRelayState'] = "включено"; }
    if (newValue == false) { dev['KotelTermostat/PumpRelayState'] = "выключено"; }
    log.info( 'KotelTermostat/PumpRelayState = ' + newValue );
  }
});


/*    КОНЕЦ ПРАВИЛ УПРАВЛЕНИЯ КОТЛОМ   */




/*    ПРАВИЛА УПРАВЛЕНИЯ БОЙЛЕРОМ   */


// запись давления в ячейку виртуального устройства
defineRule("BoilerPressureRule", { 
  whenChanged: BoilerPressure, 
  then: function (newValue, devName, cellName) {
    dev['BoilerTermostat/Pressure'] = newValue;
    log.info( 'BoilerTermostat/Pressure = ' + newValue );
  }
});


// запись температуры в ячейку виртуального устройства
defineRule("BoilerTemperatureRule", { 
  whenChanged: BoilerTemperature, 
  then: function (newValue, devName, cellName) {
    dev['BoilerTermostat/Temperature'] = newValue;
    log.info( 'BoilerTermostat/Temperature = ' + newValue );
  }
});

// запись состояния главного термореле бойлера в ячейку виртуального устройства
defineRule("BoilerMainRelayStateRule", { 
  whenChanged: BoilerMainRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/MainRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/MainRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/MainRelayState = ' + newValue );
  }
});

// запись состояния реле WB в ячейку виртуального устройства
defineRule("BoilerWBRelayStateRule", { 
  whenChanged: BoilerWBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/WBRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/WBRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/WBRelayState = ' + newValue );
  }
});

// запись состояния реле ABB в ячейку виртуального устройства
defineRule("BoilerABBRelayStateRule", { 
  whenChanged: BoilerABBRelayState, 
  then: function (newValue, devName, cellName) {
    if (newValue == true) { dev['BoilerTermostat/ABBRelayState'] = "включено"; }
    if (newValue == false) { dev['BoilerTermostat/ABBRelayState'] = "выключено"; }
    log.info( 'BoilerTermostat/ABBRelayState = ' + newValue );
  }
});


// запустить насос циркуляции ГВС при двойном нажатии выключателя в ванной или в сортире
defineRule("BoilerCirculationControlRule", {
  whenChanged: BoilerCirculationButtons,
  then: function (newValue, devName, cellName) {
    var index = BoilerCirculationButtons.indexOf(devName + '/' + cellName);
    if (newValue) {
      dev['BoilerCirculationRelay']=true;
      setTimeout(function (){
        dev['BoilerCirculationRelay']=false;
      }, BoilerCirculationTimeout);
      SendTelegramMsg('В Южном кто-то пошёл мыться ' + BoilerCirculationNames[index]);
    }
  }
});

/*    КОНЕЦ ПРАВИЛ УПРАВЛЕНИЯ БОЙЛЕРОМ   */



/*

Режим работы котла
1) Проверка давления. 
Нормальный режим 1,8...2,2 бар. 
Если стало 1,5 или  2,5, то высылаем смс в телегу. 
Если стало 1 или 3, то останавливаем котёл. 
2) Проверка температуры
3) Проверка времени
Котёл включается с 23-00 по 07-00 при температуре датчика менее 70 градусов и отключается при 90.

Режим работы бойлера 
Насос бойлера работает всё время при условии:
1) Температура в ТА больше температуры бойлера
2) Температура включения насоса 50, выключение при 60.

Режим работы полов 
1) Проверка давления. 
Нормальный режим 1,8...2,2 бар. 
Если стало 1,5 или  2,5, то высылаем смс в телегу. 
Если стало 1 или 3, то выключаем  насос. 
2) Насос включается в 5 утра и выключается в 7 вечера.

*/




function heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState) { 

  SendTelegramMsg('Время ' + time.getHours() + '. heatingRules started в Южном.');
  log.info('Время ' + time.getHours() + '. heatingRules started в Южном.');


      if (dev[pressure] <= MinPressure) {
          dev[WBRelayState] = false;
          dev[PumpRelayState] = false;
          log.info('В Южном критически низкое давление в ' + name + ' ' + dev[pressure] + ' бар. ' + name + ' отключен.');
          SendTelegramMsg('В Южном критически низкое давление в ' + name + ' ' + dev[pressure] + ' бар. ' + name + ' отключен.');

      }

      else if (dev[pressure] >= MaxPressure) {
          dev[WBRelayState] = false;
          dev[PumpRelayState] = false;
          log.info('В Южном очень высокое давление в ' + name + ' ' + dev[pressure] + ' бар. ' + name + ' отключен.');
          SendTelegramMsg('В Южном очень высокое давление в ' + name + ' ' + dev[pressure] + ' бар. ' + name + ' отключен.');
      }

/* ===================   тут работаем   =============================  */
      else if (dev[pressure] > MinPressure && dev[pressure] < MaxPressure) {  // pressure > MinPressure   И   pressure < MaxPressure 
          log.info('pressure > MinPressure AND pressure < MaxPressure.');
          SendTelegramMsg('pressure > MinPressure AND pressure < MaxPressure.');


          if ( dev[temperature] <= (dev[setpoint] - dev[hysteresis]) ) { // если температура датчика меньше, чем (уставка минус гистерезис)
              // доп проверка ЕСЛИ реле на момент проверки было выключено, то включаем его и шлём уведомление
              // а ЕСЛИ реле на момент проверки было уже включено, то ничего не делаем
              log.info('dev[temperature] <= (dev[setpoint] - dev[hysteresis])');
              SendTelegramMsg('dev[temperature] <= (dev[setpoint] - dev[hysteresis])');

              if ( dev[WBRelayState] == false ) {
                  dev[WBRelayState] = true;
                  dev[PumpRelayState] = true;
                  log.info(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' <= ' + dev[setpoint] + '-' + dev[hysteresis] + 
                    '. Положение реле WB ' + dev[WBRelayState] + '. Начался нагрев.');
                  SendTelegramMsg(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' <= ' + dev[setpoint] + '-' + dev[hysteresis] + 
                    '. Положение реле WB ' + dev[WBRelayState] + '. Начался нагрев.');
              }
          }

          else if ( dev[temperature] > (dev[setpoint] - dev[hysteresis]) && dev[temperature] < dev[setpoint] ) {
            log.info('dev[temperature] > (dev[setpoint] - dev[hysteresis]) && dev[temperature] < dev[setpoint]');
            SendTelegramMsg('dev[temperature] > (dev[setpoint] - dev[hysteresis]) && dev[temperature] < dev[setpoint]');
          // тут ничего не делаем (либо идёт нагрев либо остывание)
            dev[PumpRelayState] = true;
          }
          
          else if ( dev[temperature] = dev[setpoint] ) { // если температура датчика достигла уставки выключить нагрев
              // доп проверка ЕСЛИ реле на момент проверки было включено, то выключаем его и шлём уведомление
              // а ЕСЛИ реле на момент проверки было уже выключено, то ничего не делаем
              log.info('dev[temperature] = dev[setpoint]');
              SendTelegramMsg('dev[temperature] = dev[setpoint]');

              if ( dev[WBRelayState] == true ) {
                  dev[WBRelayState] = false;
                  dev[PumpRelayState] = true;
                  SendTelegramMsg(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' = ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
                  log.info(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' = ' + dev[setpoint] + ' (настройка термостата). Нагрев остановлен.');
              }
          }
          
          else if ( dev[temperature] > dev[setpoint] ) { // если температура датчика больше уставки выключить нагрев
                  dev[WBRelayState] = false;
                  dev[PumpRelayState] = true;
                  SendTelegramMsg(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' > ' + dev[setpoint] + ' (настройка термостата).');
                  log.info(dev[name] + ' в Южном. Температура ' + name + ' ' + dev[temperature] + ' > ' + dev[setpoint] + ' (настройка термостата).');
          }
            
          else {  // на всякий случай во всех других ситуациях вырубаем ТЭН
                  SendTelegramMsg('Последний else по температуре сработал. Надо проверить почему. WBRelayState = false.' );
                  log.info('Последний else по температуре сработал. Надо проверить почему. WBRelayState = false.');

              if ( dev[WBRelayState] == true ) {
                  dev[WBRelayState] = false;
                  SendTelegramMsg('Последний else по температуре сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
                  log.info('Последний else по температуре сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
              }
          }

      }
/* ===================   конец работы   =============================  */

      else {  // на всякий случай во всех других ситуациях вырубаем ТЭН, например WBRelayState undefined
              SendTelegramMsg('Последний else по давлению сработал. Надо проверить почему. WBRelayState = false. pressure = ' + dev[pressure] + ' MinPressure = ' + MinPressure + ' MaxPressure = ' + MaxPressure);
              log.info('Последний else по давлению сработал. Надо проверить почему. WBRelayState = false. pressure = ' + dev[pressure] + ' MinPressure = ' + MinPressure + ' MaxPressure = ' + MaxPressure);

          if ( dev[WBRelayState] == true ) {
              dev[WBRelayState] = false;
              SendTelegramMsg('Последний else по давлению сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
              log.info('Последний else по давлению сработал. Надо проверить почему. Температура ' + name + ' ' + dev[temperature] + ' . Нагрев остановлен.');
          }

      }
  
}

function TermostatController(name, onoff, scheduleMode, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState, HeatingStart, HeatingStop) {

  defineRule( {
    whenChanged: [onoff, scheduleMode, setpoint, hysteresis, temperature, pressure], 
    then: function (newValue, devName, cellName) {

      time = new Date(); //Текущее время


      if (dev[onoff]) { // если термостат включён
            log.info( 'Термостат включён');

        if (dev[scheduleMode]) { // если термостат работает по расписанию
            // тут ТЭН нельзя включать вручную
            // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
            // должен быть спрятан или виден, но заблокирован для нажатия
            log.info('scheduleMode ON - нагрев по расписанию ночью');

          if (time.getHours() < HeatingStop || time.getHours() == HeatingStart ) { // время меньше  7 часов  ИЛИ  равно 23 часам, то есть с 23 ночи до 7 утра
            log.info('Началось ночное время нагрева по расписанию');
            heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState);
          }

          else  { // с 7 утра до 23 ночи
              if ( dev[WBRelayState] == true ) {
                  dev[WBRelayState] = false;
                  SendTelegramMsg( 'Время ' + time.getHours() + '. heatingRules stopped в Южном. Ночь закончилась.');
                  log.info( 'Время ' + time.getHours() + '. heatingRules stopped в Южном. Ночь закончилась.');
              }
          }
        }

        else { // scheduleMode == false, то есть термостат работает вручную по кнопке
            // тут ТЭН надо включать вручную
            // то есть в виджете выключатель реле "wb-mio-gpio_70:4/K1"
            // должен быть виден и активен для нажатия
            log.info( 'scheduleMode OFF - ручной режим');
            heatingRules(name, temperature, setpoint, hysteresis, pressure, MinPressure, MaxPressure, MainRelayState, WBRelayState, ABBRelayState, PumpRelayState);
        }
      }

      else { // если термостат выключён, то нагрев не работает

        if ( dev[WBRelayState] == true ) {
            dev[WBRelayState] = false; 
            SendTelegramMsg( dev[name] + ' в Южном. Температура '  + name + ' ' + dev[temperature] + '. Нагрев остановлен. Термостат выключён.');
            log.info( dev[name] + ' в Южном. Температура '  + name + ' ' + dev[temperature] + '. Нагрев остановлен. Термостат выключён.');    
        }

      } 

    }

  });

}






defineRule("cron minute timer", { // задание, которое выполняется каждую минуту
  when: cron("00 * * * * *"),
  then: function () {

    TermostatController(BoilerName, BoilerONOFF, BoilerScheduleMode, BoilerTemperature, BoilerSetpoint, BoilerHysteresis, BoilerPressure, BoilerMinPressure, BoilerMaxPressure, BoilerMainRelayState, BoilerWBRelayState, BoilerABBRelayState, BoilerVirtualPumpRelayState, HeatingStart, HeatingStop);

    TermostatController(KotelName, KotelONOFF, KotelScheduleMode, KotelTemperature, KotelSetpoint, KotelHysteresis, KotelPressure, KotelMinPressure, KotelMaxPressure, KotelMainRelayState, KotelWBRelayState, KotelABBRelayState, KotelPumpRelayState, HeatingStart, HeatingStop);



    }
});

Но периодически вижу ошибку в логе

Oct 07 11:35:45 wirenboard-AM363YRR wb-rules[2208]: ERROR: control wb-mai6_51/IN 1 P Temperature SetValue() error: This control is not writable

Плюс по сообщениям в логе заметил, что если на термостате выставить температуру больше, чем на датчике, то вместо правила в строке 435 dev[temperature] > dev[setpoint] срабатывает правило из строки 421 dev[temperature] = dev[setpoint].

Потом заменил знак = на == в строке 421 dev[temperature] == dev[setpoint] и думал, что на этом конец.

Но теперь появилась вообще непонятная ошибка, хотя скрипт работает.

Oct 07 11:54:58 wirenboard-AM363YRR wb-rules[2208]: ERROR: [rule error] ECMAScript error: TypeError: call target not an object
                                                            duk_js_executor.c:2761
                                                            setDevValue /usr/share/wb-rules-system/scripts/lib.js:120 preventsyield
                                                            heatingRules /etc/wb-rules/heating.js:437
                                                            anon /etc/wb-rules/heating.js:490 preventsyield
                                                            call  native strict preventsyield
                                                            anon /usr/share/wb-rules-system/scripts/lib.js:238 preventsyield

Что это за ошибка?

  1. Вместо таких конструкций для булевый значений: if ( dev[WBRelayState] == true ) {
    Используйте такие: if ( dev[WBRelayState] ) {
    Получится короче и задумываиься над количеством знаков равенства но надо.
  2. Если нужно сравнить значения, попробуйте три знака равенства: dev[temperature] === dev[setpoint]

Эта тема была автоматически закрыта через 7 дней после последнего ответа. В ней больше нельзя отвечать.