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

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

/*   бойлер   */
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 дней после последнего ответа. В ней больше нельзя отвечать.