Примеры правил wb-rules: различия между версиями

Материал из Wiren Board
(Отмена правки 38434, сделанной Gavrilov (обсуждение))
Строка 10: Строка 10:




== Совместимость скриптов при обновлении wb-rules ==
Предполагается, что при обновлении с предыдущей на следующую версию wb-rules и при соблюдении гайдлайнов при написании скриптов - все сценарии продолжат работать без каких-либо изменений.
Далее перечислены возможные проблемы в связи с изменением логики обработки скриптов новыми версиями движка.


=== Обновление до версии 2.2 ===
С версии 2.2 стали более строго проверяться типы устанавливаемых значений для контролов:
если для установления признака включено/выключено для контролов типа switch, клики по pushbutton или присваивание значений контролам с типом text применялась недокументировання возможность
использования для этой цели числовые значения (1, 0 и т.д.) в версии движка 2.2 операция присваивания не выполнится и завершится с ошибкой.
Корректный способ — устанавливать булевы значений (true/false) для switch/pushbutton и строковые значения для типа text
При возникновении подобной проблемы в логах можно видеть подобные записи:
<pre>
ERROR: control wb-mr3_30/K1 SetValue() error: can't convert control value '1' (type float64) to datatype 'switch'
ERROR: control system/Reboot SetValue() error: can't convert control value '1' (type float64) to datatype 'pushbutton'
ERROR: control status/someStatus SetValue() error: can't convert control value '-1.47' (type float64) to datatype 'text'
</pre>
Вместо:
<syntaxhighlight lang="ecmascript">
dev["wb-mr3_30"]["K1"] = 1 // включение
dev["status"]["someStatus"] = -1.47 // float
</syntaxhighlight>
Нужно:
<syntaxhighlight lang="ecmascript">
dev["wb-mr3_30"]["K1"] = true // включение
dev["status"]["someStatus"] = (-1.47).toString() // text
</syntaxhighlight>
=== Обновление до версии 1.7 ===
Начиная с версии wb-rules 1.7, локальные переменные и функции, объявленные в файле сценария не видны в других сценариях.
Таким образом, каждый сценарий может определять свои функции и переменные без риска изменить поведение других сценариев.
В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет переменные и функции.
В предыдущих версиях wb-rules обращение к переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению.
В версиях, начиная с 1.7, поведение строго определено и такое же, как будто сценарий единственный в системе.
Сценарий 1 (rules1.js):
<syntaxhighlight lang="ecmascript">
var test1 = 42;
setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", test2);
}, 5000);
</syntaxhighlight>
Сценарий 2 (rules2.js):
<syntaxhighlight lang="ecmascript">
var test1 = 84;
var test2 = "Hello";
setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
}, 5000);
</syntaxhighlight>
Было:
<pre>
2019-01-05 17:29:50 myFuncTwo called
2019-01-05 17:29:50 test1: 84, test2: Hello
2019-01-05 17:29:50 myFuncOne called
2019-01-05 17:29:50 test1: 84
2019-01-05 17:29:50 test2: Hello
</pre>
Стало:
<pre>
2019-01-05 17:28:42 myFuncTwo called
2019-01-05 17:28:42 test1: 84, test2: Hello
2019-01-05 17:28:42 myFuncOne called
2019-01-05 17:28:42 test1: 42
2019-01-05 17:28:42 ECMAScript error: ReferenceError: identifier 'test2' undefined
duk_js_var.c:1232
myFuncOne /etc/wb-rules/rules1.js:6 preventsyield
</pre>
Возможные способы решения проблемы:
'''Способ 1'''
Самый простой способ: перенести всю логику, которая использует доступ к переменной в другм файле в тот же файл, где определена переменная. Т.е. на примере приведённых выше правил нужно перенести всё в один файл rules1.js
<syntaxhighlight lang="ecmascript">
var test1 = 42;
var test2 = "Hello";
setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
}, 5000);
setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", test2);
}, 5000);
</syntaxhighlight>
'''Способ 2'''
Следующий способ заключается в использовании глобального постоянного хранилища (PersistentStorage) ====
'''Внимание:''' при использовании глобальных постоянных хранилищ может произойти совпадение имён, в этом случае возможно труднообнаруживаемое нарушение поведения.
Вышеприведённый пример можно исправить следующим образом:
Сценарий 1 (rules1.js):
<syntaxhighlight lang="ecmascript">
var test1 = 42;
var ps = new PersistentStorage("my-global-storage", {global: true});
ps.test1 = test1;
setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", ps.test2);
}, 5000);
</syntaxhighlight>
Сценарий 2 (rules2.js):
<syntaxhighlight lang="ecmascript">
var test2 = "Hello";
var ps = new PersistentStorage("my-global-storage", {global: true});
ps.test2 = test2;
setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", ps.test1, test2);
}, 5000);
</syntaxhighlight>
'''Способ 3'''
"Грязный" способ, который использует прототип глобального объекта - использовать этот способ не рекомендуется из-за возможного замусоривания глобального пространства пользовательскими данными.
Исправленная версия:
Сценарий 1 (rules1.js):
<syntaxhighlight lang="ecmascript">
global.__proto__.test1 = 42;
setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", global.__proto__.test1);
    log("test2: {}", global.__proto__.test2);
}, 5000);
</syntaxhighlight>
Сценарий 2 (rules2.js):
<syntaxhighlight lang="ecmascript">
global.__proto__.test2 = "Hello";
setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", global.__proto__.test1, global.__proto__.test2);
}, 5000);
</syntaxhighlight>


== Как создавать и редактировать правила == <!--T:3-->
== Как создавать и редактировать правила == <!--T:3-->
Строка 261: Строка 94:




 
== Виртуальное устройство defineVirtualDevice == <!--T:17-->
=== Пишем сложные правила === <!--T:17-->
 
<!--T:18-->
Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил:
#Примеры правил:
#* на этой же странице ниже;
#* в [http://forums.contactless.ru/t/dvizhok-pravil-primery-koda/483 специальной теме на нашем форуме ].
#[https://github.com/contactless/wb-rules Полное описание движка правил].






== Примеры правил == <!--T:19-->
Виртуальные устройства - это появляющиеся в веб-интерфейсе новые элементы управления - например, кнопка-выключатель, которая на самом деле выключает два устройства одновременно. Она не привязана напрямую ни к какому физическому устройству, а действия при её нажатии определяются написанным вами скриптом.
При написании скрипта вы можете создать виртуальное устройство для включения/выключения тех или иных управляющих алгоритмов и установки их параметров.
<!--T:20-->
<syntaxhighlight lang="ecmascript">
defineVirtualDevice("wb-1", {
  title: "Отопление",
  cells: {
    "Температура": {
    type: "range",
    max: 24,
    value: 20,
    },
    "Вкл.": {
    type: "switch",
    value: false,
    }
  }
});


</syntaxhighlight>


=== Слежение за контролом === <!--T:20-->
=== Типы устройств (type): ===


'''switch''' — переключатель. Может принимать значения true или false. По-умолчанию доступен для изменения пользователем.
<!--T:21-->
<!--T:21-->
Это простейшее правило следит за контролом и устанавливает другой контрол в такое же состояние.


'''wo-switch''' — переключатель, аналог switch. Может принимать значения true или false. По-умолчанию не доступен для изменения пользователем.
<!--T:22-->
<!--T:22-->
Например правило может включать сирену и лампу, если датчик движения заметил движение.


'''pushbutton''' — кнопка. Может принимать значения true. По-умолчанию доступна для нажатия.
range — ползунок. Может принимать значения от 0 до max. По-умолчанию доступен для изменения пользователем.
<!--T:23-->
<!--T:23-->
В примере датчик движения подключен к входу "сухой контакт", контрол типа "switch". Сирена подключена к встроеному реле Wiren Board, а лампа - через релейный блок по Modbus.  Когда вход типа "сухой контакт" (выход датчика движения) замкнут, то на лампу и реле подаётся "1", когда выключен - "0".


'''rgb''' — специальный тип для задания цвета. Кодируется 3 числами от 0 до 255, разделенными точкой с запятой. Например „255;0;0“ задает красный цвет. По-умолчанию доступен для изменения пользователем.
<!--T:24-->
<!--T:24-->
Правило срабатывает каждый раз при изменении значения контрола "D1_IN" у устройства "wb-gpio".  В код правила передаётся новое значение этого контрола в виде переменной newValue.


'''alarm''' — индикатор. Может принимать значения true или false.
<!--T:25-->
<!--T:25-->
<syntaxhighlight lang="ecmascript">


'''text''' — текстовое поле. По-умолчанию не доступно для редактирования пользователем.
<!--T:26-->
<!--T:26-->
defineRule("motion_detector", {
  whenChanged: "wb-gpio/D1_IN",
  then: function (newValue, devName, cellName) {
dev["wb-gpio"]["Relay_2"] = newValue;
dev["wb-mrm2_6"]["Relay 1"] = newValue;


  <!--T:27-->
'''value''' — значение с плавающей точкой. По-умолчанию не доступно для редактирования пользователем.
}
<!--T:27-->
});


Так же существуют еще 14 специальных типов. Все они аналогичны value, но имеют соответствующие подписи в интерфейсе.
<!--T:28-->
<!--T:28-->
</syntaxhighlight>


{| class="wikitable"
! Type
! meta/type
! units
!value format
|-
| Temperature
| temperature
| °C
| float
|-
| Relative humidity
| rel_humidity
| %, RH
| float, 0 - 100
|-
| Atmospheric pressure
| atmospheric_pressure
| millibar (100 Pa)
| float
|-
| Precipitation rate (rainfall rate)
| rainfall
| mm per hour
| float
|-
| Wind speed
| wind_speed
| m/s
| float
|-
| Power
| power
| watt
| float
|-
| Power consumption
| power_consumption
| kWh
| float
|-
| Voltage
| voltage
| volts
| float
|-
| Water flow
| water_flow
| m^3 / hour
| float
|-
| Water total consumption
| water_consumption
| m^3
| float
|-
| Resistance
| resistance
| resistance
| float
|-
| Gas concentration
| concentration
| ppm
| float (unsigned)
|-
| Heat power
| heat_power
| Gcal / hour
| float
|-
| Heat energy
| heat_energy
| Gcal
| float
|}
<!--T:29-->
<!--T:29-->
То же самое, но с виртуальным девайсом в качестве источника событий. Пример использования: сценарная кнопка, которая включает/выключает сирену и лампочку.
<!--T:30-->
<syntaxhighlight lang="ecmascript">
defineVirtualDevice("simple_test", {
    title: "Simple switch",
    cells: {
enabled: {
    type: "switch",
    value: false
},
    }
});


=== value: ===
Обязательное поле. При создании устройства в первый раз его значение будет установлено в значение по-умолчанию. В дальнейшем значения сохраняются в специальное хранилище в постоянной памяти и восстанавливаются при загрузке сценария.
<!--T:31-->
<!--T:31-->
defineRule("simple_switch", {
  whenChanged: "simple_test/enabled",
  then: function (newValue, devName, cellName) {
dev["wb-gpio"]["Relay_2"] = newValue;
dev["wb-mrm2_6"]["Relay 1"] = newValue;
  <!--T:32-->
}
});


=== forceDefault: ===
Если необходимо каждый раз при перезагрузке скрипта восстанавливать строго определённое значение (т.е. не восстанавливать предыдущее сохранённое), нужно добавить в описание контрола поле '''forceDefault: true'''
<!--T:33-->
<!--T:33-->
</syntaxhighlight>
=== Детектор движения c таймаутом === <!--T:34-->


=== max: ===
Для параметра типа '''range''' может задавать его максимально допустимое значение.
<!--T:35-->
<!--T:35-->
На вход D2 подключен детектор движения с выходом типа "сухой контакт", который замыкает D2 и GND при обнаружении движения.
При этом, на канале "wb-gpio/D2_IN" появляется статус "1".


<!--T:36-->
=== readonly: ===
Правило включает свет при обнаружении движения и выключает свет, спустя 30 секунд после пропадания сигнала с датчика движения.
Когда задано истинное значение — устройство становится не доступным для редактирования пользователем. Если надо предоставить пользователю возможность редактировать значение, следует добавить в описание '''readonly: false'''


<!--T:37-->
Освещение подключено через встроенное реле, канал wb-gpio/Relay_1.
== Типы правил defineRule  ==
 
<!--T:38-->
<syntaxhighlight lang="ecmascript">


<!--T:39-->
var motion_timer_1_timeout_ms = 30 * 1000;
var motion_timer_1_id = null;


=== whenChanged ===
Правило срабатывает при любых изменениях значений параметров или функций
В примере кнопка подключена ко входу А1 контроллера. При нажатии на кнопку срабатывает реле 1 устройства MRM2-mini, при отжатии реле возвращается в исходное состояние.
<!--T:40-->
<!--T:40-->
defineRule("motion_detector_1", {
  whenChanged: "wb-gpio/D2_IN",
  then: function (newValue, devName, cellName) {
    if (newValue) {
        dev["wb-gpio"]["Relay_1"] = 1;
      <!--T:41-->
if (motion_timer_1_id) {
          clearTimeout(motion_timer_1_id);
      }
     
        motion_timer_1_id = setTimeout(function () {
    dev["wb-gpio"]["Relay_1"] = 0;       
                  motion_timer_1_id = null; 
      }, motion_timer_1_timeout_ms);         
    }
  }
});
<!--T:42-->
</syntaxhighlight>
=== Создание однотипных правил === <!--T:43-->
<!--T:44-->
Если таких детекторов движения нужно несколько, то, чтобы не копировать код, можно обернуть создание правила и переменных в функцию:
<!--T:45-->
<syntaxhighlight lang="ecmascript">
function makeMotionDetector(name, timeout_ms, detector_control, relay_control) {
  var motion_timer_id = null;
  defineRule(name, {
      whenChanged: "wb-gpio/" + detector_control,
      then: function(newValue, devName, cellName) {
          if (!newValue) {
              dev["wb-gpio"][relay_control] = 1;
              if (motion_timer_id) {
                  clearTimeout(motion_timer_id);
              }
              <!--T:46-->
motion_timer_id = setTimeout(function() {
                  dev["wb-gpio"][relay_control] = 0;
                  motion_timer_id = null;
              }, timeout_ms);
          }
      }
  });
}
<!--T:47-->
makeMotionDetector("motion_detector_1", 20000, "EXT1_DR1", "EXT2_R3A1");
makeMotionDetector("motion_detector_2", 10000, "EXT1_DR2", "EXT2_R3A2");
makeMotionDetector("motion_detector_3", 10000, "EXT1_DR3", "EXT2_R3A3");
</syntaxhighlight>
=== Активация правила только в определённое время === <!--T:48-->
<!--T:49-->
Правило как в предыдущем разделе, но выполняется только с 9:30 до 17:10 по UTC.


<!--T:50-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
var motion_timer_1_timeout_ms = 5 * 1000;
defineRule("test_rule", { //имя правила test_rule
var motion_timer_1_id = null;
defineRule("motion_detector_1", {
   whenChanged: "wb-gpio/A1_IN",
   whenChanged: "wb-gpio/A1_IN",
   then: function (newValue, devName, cellName) {
   then: function (newValue, devName, cellName) {
     var date = new Date();
     dev["wb-mrm2-mini_2"]["Relay 1"] = newValue;
 
  }
    <!--T:51-->
// time point marking the beginning of the interval
    // i.e. "today, at HH:MM". All dates are in UTC!
    var date_start = new Date(date);
    date_start.setHours(9);
    date_start.setMinutes(30);
 
    <!--T:52-->
// time point marking the end of the interval
    var date_end = new Date(date);
    date_end.setHours(17);
    date_end.setMinutes(10);
   
    // if time is between 09:30 and 17:10 UTC
    if ((date > date_start) && (date < date_end)) {
      if (newValue) {
          dev["wb-gpio"]["EXT1_R3A1"] = 1;
 
          if (motion_timer_1_id) {
            clearTimeout(motion_timer_1_id);
        }
 
          motion_timer_1_id = setTimeout(function () {
            dev["wb-gpio"]["EXT1_R3A1"] = 0;           
            motion_timer_1_id = null;
          }, motion_timer_1_timeout_ms);            
      }
    }
  }
});
});
</syntaxhighlight>
=== Роллеты === <!--T:53-->
<!--T:54-->
Одно реле включает двигатель, поднимающий шторы, второе реле - включает двигатель, опускающий шторы.
Правило следит за тем, чтобы оба реле не были включены одновременно.
<!--T:55-->
Кроме этого, правило отключает двигатели спустя заданное время после включения.
<!--T:56-->
<syntaxhighlight lang="ecmascript">
(function() { //don't touch this line
 
  var suffix = "1"; // must be different in different JS files
  var relay_up_device = "lc103_4";
  var relay_up_control = "Relay 1";
  <!--T:57-->
var relay_down_device = "lc103_4";
  var relay_down_control = "Relay 2";
  <!--T:58-->
var timeout_s = 15;
 
  // End of settings
 
 
  var relay_up_timer_id = null;
  var relay_down_timer_id = null;
 
  defineRule( "roller_shutter_up_on" + suffix, {
  asSoonAs: function() {
    return dev[relay_up_device][relay_up_control];
  },
    then: function () {
      if (relay_up_timer_id) {
        relay_up_timer_id = clearTimeout(relay_up_timer_id);
      };
      <!--T:59-->
relay_up_timer_id = setTimeout(function() {
        return dev[relay_up_device][relay_up_control] = 0;
      }, timeout_s * 1000);
    }
  });
  <!--T:60-->
defineRule("roller_shutter_down_on" + suffix, {
    asSoonAs: function() {
      return dev[relay_down_device][relay_down_control];
    },
    then: function () {
      if (relay_down_timer_id) {
        relay_down_timer_id = clearTimeout(relay_down_timer_id);
      };
     
      relay_down_timer_id = setTimeout(function() {
        dev[relay_down_device][relay_down_control] = 0;
      }, timeout_s * 1000);
    }
  });
  <!--T:61-->
defineRule("roller_shutter_both_on" + suffix, {
    asSoonAs: function() {
      return dev[relay_up_device][relay_up_control] && dev[relay_down_device][relay_down_control];
    },
    then: function () {
      if (relay_up_timer_id) {
        relay_up_timer_id = clearTimeout(relay_up_timer_id);
      };


      <!--T:62-->
if (relay_down_timer_id) {
        relay_down_timer_id = clearTimeout(relay_down_timer_id);
      };
     
      <!--T:63-->
dev[relay_up_device][relay_up_control] = 0;
      dev[relay_down_device][relay_down_control] = 0;
      log("Both roller shutter relays on, switching them off");
    }
  });
})();
</syntaxhighlight>
</syntaxhighlight>
<!--T:41-->


 
=== asSoonAs ===
 
Правила, задаваемые при помощи asSoonAs, срабатывают в случае, когда значение, возвращаемое функцией, заданной в asSoonAs, становится истинным при том, что при предыдущем просмотре данного правила оно было ложным.
<!--T:64-->
<!--T:43-->
Более старая версия того же сценария демонстрирует использование alias-ов:
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
defineRule({
  asSoonAs: function() {
    return dev["wb-gpio"]["A2_IN"];
  },
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 2"] = true;
  }
});
defineRule({
  asSoonAs: function() {
    return dev["wb-gpio"]["A3_IN"];
  },
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 2"] = false;
  }
});


<!--T:65-->
(function() {
  defineAlias("relay_up_1", "lc103_4/Relay 1");
  defineAlias("relay_down_1", "lc103_4/Relay 2");
  var timeout_s = 15;
  <!--T:66-->
defineRule("roller_shutter_1_up_on", {
  asSoonAs: function() {
    return relay_up_1;
  },
    then: function () {
      setTimeout(function() {
        relay_up_1 = 0;
      }, timeout_s * 1000);
    }
  });
  <!--T:67-->
defineRule("roller_shutter_1_down_on", {
    asSoonAs: function() {
      return relay_down_1;
    },
    then: function () {
      setTimeout(function() {
        relay_down_1 = 0;
      }, timeout_s * 1000);
    }
  });
  <!--T:68-->
defineRule("roller_shutter_1_both_on", {
    asSoonAs: function() {
      return relay_up_1 && relay_down_1;
    },
    then: function () {
      relay_up_1 = 0;
      relay_down_1 = 0;
      log("Both roller shutter relays on, switching them off");
    }
  });
})();
<!--T:69-->
</syntaxhighlight>
</syntaxhighlight>


При нажатии на кнопку, подключенную к входу А2, включаем реле, а при нажатии на кнопку, подключенную к входу А3 — выключаем.




=== Системные правила === <!--T:70-->
=== when ===  
 
<!--T:71-->
Некоторые правила поставляются с системой правил по умолчанию в пакете wb-rules-system.
 
<!--T:72-->
Полный список правил [https://github.com/contactless/wb-rules-system/tree/master/rules в репозитории].
 
<!--T:73-->
Некоторые примеры:
 
 
 
==== Правило для пищалки ==== <!--T:74-->


<!--T:75-->
Правила, задаваемые при помощи when срабатывают при каждом просмотре, при котором функция, заданная в when, возвращает истинное значение. При срабатывании правила выполняется функция, заданная в свойстве when.
[https://github.com/contactless/wb-rules-system/blob/master/rules/buzzer.js Правило] создаёт виртуальное устройство buzzer с ползунками для регулировки громкости и частоты, а также кнопкой включения звука.


<!--T:76-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
defineVirtualDevice("buzzer", {
defineVirtualDevice("test_button2", {
   title: "Buzzer", //
   title: "test_relay2",
 
   cells: {
   <!--T:77-->
     "switch_on": {
cells: {
     type: "pushbutton",
     frequency : {
     value: false,
        type : "range",
        value : 3000,
        max : 7000,
     },
    volume : {
        type : "range",
        value : 10,
        max : 100,
     },
    enabled : {
        type : "switch",
        value : false,
     },
     },
    "switch_off": {
    type: "pushbutton",
    value: false,
    }
   }
   }
});
});


 
defineRule({
<!--T:78-->
  when: function() {
// setup pwm2
    return dev["test_button2"]["switch_on"];
runShellCommand("echo 2 > /sys/class/pwm/pwmchip0/export");
  },
 
   then: function (newValue, devName, cellName) {
 
     dev["wb-mrm2-mini_2"]["Relay 2"] = true;
 
<!--T:79-->
function _buzzer_set_params() {
        var period = parseInt(1.0 / dev.buzzer.frequency * 1E9);
        var duty_cycle = parseInt(dev.buzzer.volume  * 1.0  / 100 * period * 0.5);
 
 
        <!--T:80-->
runShellCommand("echo " + period + " > /sys/class/pwm/pwmchip0/pwm2/period");
        runShellCommand("echo " + duty_cycle + " > /sys/class/pwm/pwmchip0/pwm2/duty_cycle");
};
 
 
<!--T:81-->
defineRule("_system_buzzer_params", {
  whenChanged: [
    "buzzer/frequency",
    "buzzer/volume",
    ],
 
   <!--T:82-->
then: function (newValue, devName, cellName) {
     if ( dev.buzzer.enabled) {
        _buzzer_set_params();
    }
   }
   }
});
});


 
defineRule({
<!--T:83-->
   when: function() {
defineRule("_system_buzzer_onof", {
    return dev["test_button2"]["switch_off"];
   whenChanged: "buzzer/enabled",
  },
   then: function (newValue, devName, cellName) {
   then: function (newValue, devName, cellName) {
     if ( dev.buzzer.enabled) {
     dev["wb-mrm2-mini_2"]["Relay 2"] = false;
        _buzzer_set_params();
  }
        runShellCommand("echo 1  > /sys/class/pwm/pwmchip0/pwm2/enable");
    } else {
        runShellCommand("echo 0  > /sys/class/pwm/pwmchip0/pwm2/enable");
    }
  }
});
});
<!--T:84-->
</syntaxhighlight>
</syntaxhighlight>


При нажатии на кнопку switch_on, включаем реле, а при нажатии на кнопку,switch_off — выключаем.
<!--T:49-->


=== cron-правила ===


==== Правило для статуса питания ==== <!--T:85-->
Отдельный тип правил - cron-правила. Такие правила задаются следующим образом:


<!--T:86-->
<syntaxhighlight lang="ecmascript">
[https://github.com/contactless/wb-rules-system/blob/master/rules/power_status.js Правило] создаёт виртуальное устройство, которое сообщает текущий статус питания. В качестве входных данных используется два канала АЦП: измерение напряжения на аккумуляторе и измерение входного напряжения.
defineRule("crontest_hourly", {
  when: cron("@hourly"),
  then: function () {
    log("@hourly rule fired");
  }
})
</syntaxhighlight>


<!--T:87-->
Вместо @hourly здесь можно задать любое выражение, допустимое в стандартном crontab, например, 0 20 * * * (выполнять правило каждый день в 20:00). Помимо стандартных выражений допускается использование ряда расширений, см. описание формата выражений используемой cron-библиотеки.
Реализована следующая логика:


<!--T:88-->
1. Если входное напряжение меньше напряжение на аккумуляторе, то значит плата питается от аккумулятора. В этом случае, также отображается 0V в качестве входного напряжения.


<!--T:89-->
== Таймеры ==
2. Если входное напряжение  больше напряжения на аккумуляторе, то плата работает от внешнего источника питания. В качестве входонго напряжения отображается измерение с канала Vin.


=== setTimeout(); ===


<!--T:90-->
С помощью данного таймера можно отсрочить выполнение правила на определенное время. В примере после нажатия кнопки включается пищалка и затем выключается спустя 2 секунды.
Для иллюстрации правила используют два разных способа срабатывания: по изменению значения контрола (правило _system_track_vin) и по изменению значения выражения (два других).


<!--T:91-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
 
defineVirtualDevice("test_buzzer", {
<!--T:92-->
title: "Test Buzzer",
defineVirtualDevice("power_status", {
   cells: {
  title: "Power status", //
     enabled: {
 
      type: "pushbutton",
   <!--T:93-->
      value: false
cells: {
     'working on battery' : {
        type : "switch",
        value : false,
        readonly : true
    },
    'Vin' : {
        type : "voltage",
        value : 0
    }
 
 
  <!--T:94-->
}
});
 
 
 
<!--T:95-->
defineRule("_system_track_vin", {
    whenChanged: "wb-adc/Vin",
    then: function() {
        if (dev["wb-adc"]["Vin"] < dev["wb-adc"]["BAT"] ) {
            dev["power_status"]["Vin"] = 0;
        } else {
            dev["power_status"]["Vin"] = dev["wb-adc"]["Vin"] ;
        }
     }
     }
});
<!--T:96-->
defineRule("_system_dc_on", {
  asSoonAs: function () {
    return  dev["wb-adc"]["Vin"] > dev["wb-adc"]["BAT"];
  },
  then: function () {
    dev["power_status"]["working on battery"] = false;
   }
   }
});
});


<!--T:97-->
defineRule({
defineRule("_system_dc_off", {
  whenChanged: "test_buzzer/enabled",
  asSoonAs: function () {
    then: function (newValue, devName, cellName) {
     return  dev["wb-adc"]["Vin"] <= dev["wb-adc"]["BAT"];
     dev["buzzer"]["enabled"] = true;
  },
    setTimeout(function () {
  then: function () {
      dev["buzzer"]["enabled"] = false;
    dev["power_status"]["working on battery"] = true;
    }, 2000);
   }
   }
});
});
<!--T:98-->
</syntaxhighlight>
</syntaxhighlight>
<!--T:59-->


=== setInterval(); ===<!--T:60-->


=== Отправка команд по RS-485 === <!--T:99-->
<!--T:100-->
Для примера отправим команду устройству на порт ''/dev/ttyNSC0'' (соответствует аппаратному порту RS-485-ISO на [[Special:MyLanguage/Wiren Board 4|Wiren Board 4]]).
Для этого будем использовать движок правил и возможность выполнения произвольных shell-команд. Подробнее см. [https://github.com/contactless/wb-rules#%D0%94%D1%80%D1%83%D0%B3%D0%B8%D0%B5-%D0%BF%D1%80%D0%B5%D0%B4%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%91%D0%BD%D0%BD%D1%8B%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8-%D0%B8-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5 в документации].
<!--T:101-->
С помощью движка правил создадим виртуальное устройство с контролом типа switch (переключатель).
<!--T:102-->
При включении переключателя будем отправлять команду (уст. Яркость кан. 00=0xff) для Uniel UCH-M141:
<pre>
FF FF 0A 01 FF 00 00 0A
</pre>
<!--T:103-->
При выключении переключателя будем отправлять команду (уст. Яркость кан. 00=0x00) для Uniel UCH-M141:
<pre>
FF FF 0A 01 00 00 00 0B
</pre>
<!--T:104-->
1. Настройка порта
<!--T:105-->
Для настройки порта /dev/ttyNSC0 на скорость 9600 надо выполнить следующую команду
<!--T:106-->
<pre>
stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8
</pre>
<!--T:107-->
2. Отправка команды
<!--T:108-->
Отправка данных делается следующей шелл-командой:
<!--T:109-->
<pre>
/usr/bin/printf '\xFF\xFF\x0A\x01\xD1\x06\x00\xE2' >/dev/ttyNSC0
</pre>
где "\xFF\xFF\x0A\x01\xD1\x06\x00\xE2" - это запись команды "FF FF 0A 01 D1 06 00 E2".
<!--T:110-->
3. Создадим в движке правил новый файл с правилами <code>/etc/wb-rules/rs485_cmd.js</code>
<!--T:111-->
Файл можно редактировать с помощью vim, nano или mcedit в сеансе ssh на устройстве, либо залить его с помощью SCP.
<!--T:112-->
<pre>
root@wirenboard:~# mcedit  /etc/wb-rules/rs485_cmd.js
</pre>
<!--T:113-->
4. Описываем в файле виртуальный девайс
<!--T:114-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
defineVirtualDevice("rs485_cmd", {
defineVirtualDevice("test_buzzer", {
    title: "Send custom command to RS-485 port",
  title: "Test Buzzer",
    cells: {
  cells: {
enabled: {
    enabled: {
    type: "switch",
      type: "pushbutton",
    value: false
      value: false
},
     }
     }
  }
});
});
</syntaxhighlight>




<!--T:115-->
var test_interval = null;
5. Перезапускаем wb-rules и проверяем работу


<!--T:116-->
defineRule({                                             
<pre>
  whenChanged: "test_buzzer/enabled",
root@wirenboard:~# /etc/init.d/wb-rules restart
  then: function (newValue, devName, cellName) {
root@wirenboard:~# tail -f /var/log/messages
    var n = 0;
</pre>
    if (dev["test_buzzer"]["enabled"]){
 
      test_interval = setInterval(function () {
<!--T:117-->
dev["buzzer"]["enabled"] = !dev["buzzer"]["enabled"];
В логе не должно быть сообщений об ошибке (выход через control-c)
        n = n+1;
 
        if (n >= 10){
 
          clearInterval(test_interval);
<!--T:118-->
        }
В веб-интерфейсе в разделе Devices должно появиться новое устройство "Send custom command to RS-485 port".
      }, 500);  
 
    }
 
  }
<!--T:119-->
});
6. Добавим функцию для конфигурирования порта.
 
 
<!--T:120-->
<syntaxhighlight lang="ecmascript">
function setup_port() {
    runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8");
}
 
<!--T:121-->
</syntaxhighlight>
</syntaxhighlight>
<!--T:61-->


 
=== startTimer(); ===
<!--T:122-->
Запускает однократный таймер. При срабатывании таймера происходит просмотр правил, при этом timers.<name>.firing для этого таймера становится истинным на время этого просмотра.
7. Опишем правила на включение и выключение переключателя
<!--T:63-->
 
<!--T:123-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
defineRule("_rs485_switch_on", {
defineVirtualDevice("test_buzzer", {
   asSoonAs: function () {
   title: "Test Buzzer",
     return dev.rs485_cmd.enabled;
  cells: {
  },
     enabled: {
  then: function() {
      type: "switch",
     runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\xff\\x00\\x00\\x0a' > /dev/ttyNSC0");
      value: false
     }
   }
   }
});
});


<!--T:124-->
defineRule("1",{
defineRule("_rs485_switch_off", {
   asSoonAs: function () {
   asSoonAs: function () {
     return !dev.rs485_cmd.enabled;
     return dev["test_buzzer"]["enabled"] ;
   },
   },
   then: function() {
   then: function () {
     runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\x00\\x00\\x00\\x0b' >/dev/ttyNSC0");
     startTimer("one_second", 1000);
    dev["buzzer"]["enabled"] = true;//включаем пищалку
   }
   }
});
});


<!--T:125-->
defineRule("2",{
  when: function () { return timers.one_second.firing; },
  then: function () {
    dev["buzzer"]["enabled"] = false;//выключаем пищалку
    dev["test_buzzer"]["enabled"] = false;
  }
});
</syntaxhighlight>
</syntaxhighlight>
<!--T:64-->


 
=== startTicker(); ===
<!--T:126-->
Запускает периодический таймер с указанным интервалом. В примере правило 1 запускает таймер с интервалом 1 сек. Вызывая срабатывание правила 2. Метод stop() приводит к остановке таймера.
Обратите внимание на двойное экранирование.
<!--T:66-->
 
 
 
 
<!--T:127-->
7. Собираем всё вместе
 
<!--T:128-->
Полное содержимое файла с правилами:
 
<!--T:129-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
defineVirtualDevice("rs485_cmd", {
defineVirtualDevice("test_buzzer", {
    title: "Send custom command to RS-485 port",
  title: "Test Buzzer",
    cells: {
  cells: {
enabled: {
    enabled: {
    type: "switch",
      type: "switch",
    value: false
      value: false
},
     }
     }
  }
});
});


 
defineRule("1",{
<!--T:130-->
function setup_port() {
    runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8");
}
 
<!--T:131-->
defineRule("_rs485_switch_on", {
   asSoonAs: function () {
   asSoonAs: function () {
     return dev.rs485_cmd.enabled;
     return dev["test_buzzer"]["enabled"] ;
   },
   },
   then: function() {
   then: function () {
     runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\xff\\x00\\x00\\x0a' > /dev/ttyNSC0");
     startTicker("one_second", 1000);
   }
   }
});
});
 
defineRule("2",{
<!--T:132-->
   when: function () { return timers.one_second.firing; },
defineRule("_rs485_switch_off", {
   then: function () {
   asSoonAs: function () {
     dev["buzzer"]["enabled"] = !dev["buzzer"]["enabled"];
    return !dev.rs485_cmd.enabled;
    if (dev["test_buzzer"]["enabled"] == false){
  },
      timers.one_second.stop();
   then: function() {
    }
     runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\x00\\x00\\x00\\x0b' >/dev/ttyNSC0");
   }
   }
});
});
<!--T:133-->
setTimeout(setup_port, 1000); // запланировать выполнение setup_port() через 1 секунду после старта правил.
<!--T:134-->
</syntaxhighlight>
</syntaxhighlight>
<!--T:67-->


 
== Сообщения в лог ==  
 
В зависимости от функции сообщение классифицируется как отладочное (debug), информационное (info), предупреждение (warning) или сообщение об ошибке (error).
=== Пользовательские поля в интерфейсе === <!--T:135-->
<!--T:69-->
 
<!--T:136-->
Чтобы дать пользователю возможность вводить точные значения параметров (уставки) из интерфейса, можно воспользоваться [https://wirenboard.com/wiki/index.php/%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D1%83%D0%B5%D0%BC%D1%8B%D1%85_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%B8%D1%85_%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9_%D0%B2_%D0%B2%D0%B5%D0%B1-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81%D0%B5 инструкцией].
 
<!--T:137-->
Более подробно и с примером - в [https://support.wirenboard.com/t/kak-na-wb5-wb6-sozdat-pole-dlya-vvoda-ustavok-i-peredat-znachenie-v-pravila/2180 теме на портале техподдержки].
 
 
=== Импульсные счетчики ===
 
Импульсный счетчик подключен к WB-MCM8. Выдает 1 импульс на 10 литров воды. При подключении на счетчике были показания 123.120 м³, что равно 123120 литрам воды. У WB-MCM8 при подключении было насчитано 7 импульсов.
 
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
var meterCorrection = 123120 // Корректировочное значение счетчика в литрах
defineVirtualDevice("test_buzzer", {
var counterCorrection = 7 // Корректировочное значение WB-MCM8 в импульсах
  title: "Test Buzzer",
var inpulseValue = 10 // Количество литров на один импульс
  cells: {
 
    enabled: {
defineVirtualDevice("water_meters", { // Создаем виртуальный девайс для отображения в веб интерфейсе.
      type: "pushbutton",
    title: "Счетчики воды",
      value: false
    cells: {
        water_meter_1: {
            type: "value",
            value: 0
        },
     }
     }
  }
});
});


defineRule("water_meter_1", {
defineRule({
    whenChanged: "wb-mcm8_29/Input 1 counter",
  whenChanged: "test_buzzer/enabled",
    then: function(newValue, devName, cellName) {
  then: function (newValue, devName, cellName) {
      if(newValue){
    log.info('info');
      dev["water_meters"]["water_meter_1"] = ((parseInt(newValue) - counterCorrection) * inpulseValue) + meterCorrection; // Умножаем значение счетчика на количество литров/импульс и прибавляем корректировочное значение.
    log.debug('debug');
      }
    log.error('error');
    }
    log.warning('warning');
    log('log!'); //сокращение для log.info();
    debug('deb!'); //сокращение для log.debug();
  }
});
});
</syntaxhighlight>
</syntaxhighlight>
<!--T:70-->
== Выполнение произвольной команды runShellCommand(); == 


=== Сложные правила с расписаниями === <!--T:138-->
<!--T:139-->
Объект - продуктовый магазин. Различные системы магазина управляются по обратной связи от датчиков температуры и с учётом расписания работы магазина.
<!--T:140-->
Для расписаний используются не cron-правила, а обёртка над ними. Обёртка включает и выключает правила, которые, в отличие от cron-правил, выполняются постоянно, будучи включенными.
<!--T:141-->
Например, мы хотим, чтобы освещение было включено с 10 до 17ч. Обёртка (libschedule) будет выполнять правило "включить освещение" раз в минуту с 10 утра до 17 вечера.
<!--T:142-->
Это значит, что даже если контроллер работает с перерывами и пропустил время перехода между расписаниями (10 утра), то контроллер всё равно включит освещение при первой возможности.
<!--T:143-->
lib_schedules.js:
<!--T:144-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
global.__proto__.Schedules = {};
defineVirtualDevice("test_button", {
 
   title: "Test Button",
<!--T:145-->
   cells: {
(function(Schedules) { // замыкание
     enabled: {
 
       type: "pushbutton",
   <!--T:146-->
       value: false
function todayAt(now, hours, minutes) {
    var date = new Date(now);
    // i.e. "today, at HH:MM". All dates are in UTC!
    date.setHours(hours);
    date.setMinutes(minutes);
    return date;
   }
 
 
  <!--T:147-->
function checkScheduleInterval(now, start_time, end_time) {
    var start_date = todayAt(now, start_time[0], start_time[1]);
    var end_date = todayAt(now, end_time[0], end_time[1]);
    log("checkScheduleInterval {} {} {}".format(now, start_date, end_date));
 
     <!--T:148-->
if (end_date >= start_date) {
      if ((now >= start_date) && (now < end_date)) {
        return true;
      }
    } else {
       // end date is less than start date,
      // assuming they belong to a different days (e.g. today and tomorrow)
 
      <!--T:149-->
// option 1: what if it's now the day of "end" date?
      // in this case the following is enough:
      if (now < end_date) {
        return true;
      }
 
      <!--T:150-->
// well, that seems not to be the case. ok,
       // option 2: it's the day of "start" date:
 
      <!--T:151-->
if (now >= start_date) {
        return true;
      }
    }
    return false;
 
  <!--T:152-->
}
 
  <!--T:153-->
function checkSchedule(schedule, now) {
    if (now == undefined) {
      now = new Date();
    }
 
    <!--T:154-->
for (var i = 0; i < schedule.intervals.length; ++i) {
      var item = schedule.intervals[i];
      if (checkScheduleInterval(now, item[0], item[1])) {
        log("found matching schedule interval at {}".format(item));
        return true;
      }
     }
     }
    return false;
   }
   }
 
});
  function updateSingleScheduleDevStatus(schedule) {
    log("updateSingleScheduleDevStatus {}".format(schedule.name));
    dev["_schedules"][schedule.name] = checkSchedule(schedule);
  };


   <!--T:155-->
defineRule({
function addScheduleDevCronTasks(schedule) {
   whenChanged: "test_button/enabled",
     for (var i = 0; i < schedule.intervals.length; ++i) {
  then: function (newValue, devName, cellName) {
      var interval = schedule.intervals[i];
     runShellCommand("mosquitto_pub -t '/devices/test_button/controls/enabled/meta/readonly' -r -m 1");
      for (var j = 0; j < 2; ++j) { // either start or end of the interval
    setTimeout(function () {
        var hours = interval[j][0];
      runShellCommand("mosquitto_pub -t '/devices/test_button/controls/enabled/meta/readonly' -r -m 0");
        var minutes = interval[j][1];
    }, 5000);
        log("cron at " + "0 " + minutes + " " + hours + " * * *");
        defineRule("_schedule_dev_{}_{}_{}".format(schedule.name, i, j), {
          when: cron("0 " + minutes + " " + hours + " * * *"),
          then: function () {
            log("_schedule_dev_ {}_{}_{}".format(schedule.name, i, j));
            updateSingleScheduleDevStatus(schedule);
          }
        });
      }
    }   
   }
   }
});
</syntaxhighlight>


  <!--T:156-->
function addScheduleAutoUpdCronTask(schedule) {
    defineRule("_schedule_auto_upd_{}".format(schedule.name), {
      when: cron("@every " + schedule.autoUpdate),
      then: function() {
        dev._schedules[schedule.name] = dev._schedules[schedule.name];
      }
    });
  }


  <!--T:157-->
var _schedules = {};


  <!--T:158-->
== Пишем сложные правила == <!--T:189-->
Schedules.registerSchedule = function(schedule) {
    _schedules[schedule.name] = schedule;
  };


  <!--T:159-->
<!--T:190-->
Schedules.initSchedules = function() {
Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил:
    var params = {
#Примеры правил:
      title: "Schedule Status",
#* на этой же странице ниже;
      cells: {}
#* в [http://forums.contactless.ru/t/dvizhok-pravil-primery-koda/483 специальной теме на нашем форуме ].
    };
#[https://github.com/contactless/wb-rules Полное описание движка правил].


    <!--T:160-->
== Совместимость скриптов при обновлении wb-rules ==
for (var schedule_name in _schedules) {
      if (_schedules.hasOwnProperty(schedule_name)) {
        var schedule = _schedules[schedule_name];
        params.cells[schedule_name] = {type: "switch", value: false, readonly: true};
      }
    };


    <!--T:161-->
* [[Special:MyLanguage/Движок_правил_wb-rules_1.7|Движок правил wb-rules 1.7]]
defineVirtualDevice("_schedules", params);
* [[Special:MyLanguage/Движок_правил_wb-rules_2.0|Движок правил wb-rules 2.0]]


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


=== Обновление до версии 2.2 ===


    <!--T:162-->
С версии 2.2 стали более строго проверяться типы устанавливаемых значений для контролов:
for (var schedule_name in _schedules) {
      if (_schedules.hasOwnProperty(schedule_name)) {
        var schedule = _schedules[schedule_name];


        <!--T:163-->
если для установления признака включено/выключено для контролов типа switch, клики по pushbutton или присваивание значений контролам с типом text применялась недокументировання возможность
// setup cron tasks which updates the schedule dev status at schedule
использования для этой цели числовые значения (1, 0 и т.д.) в версии движка 2.2 операция присваивания не выполнится и завершится с ошибкой.
        //  interval beginings and ends
Корректный способ — устанавливать булевы значений (true/false) для switch/pushbutton и строковые значения для типа text
        addScheduleDevCronTasks(schedule);


        <!--T:164-->
При возникновении подобной проблемы в логах можно видеть подобные записи:
// if needed, setup periodic task to trigger rules which use this schedule
<pre>
        if (schedule.autoUpdate) {
ERROR: control wb-mr3_30/K1 SetValue() error: can't convert control value '1' (type float64) to datatype 'switch'
          addScheduleAutoUpdCronTask(schedule);
ERROR: control system/Reboot SetValue() error: can't convert control value '1' (type float64) to datatype 'pushbutton'
        }
ERROR: control status/someStatus SetValue() error: can't convert control value '-1.47' (type float64) to datatype 'text'
</pre>


        <!--T:165-->
Вместо:
// set schedule dev status as soon as possible at startup
<syntaxhighlight lang="ecmascript">
        (function(schedule) {
dev["wb-mr3_30"]["K1"] = 1 // включение
          setTimeout(function() {
dev["status"]["someStatus"] = -1.47 // float
            updateSingleScheduleDevStatus(schedule);
          }, 1);
        })(schedule);
 
      <!--T:166-->
};
    };
 
   
  <!--T:167-->
};
 
<!--T:168-->
})(Schedules);
</syntaxhighlight>
</syntaxhighlight>


<!--T:169-->
Нужно:
Пример правил, с использованием Schedules:
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
(function() { // замыкание
dev["wb-mr3_30"]["K1"] = true // включение
dev["status"]["someStatus"] = (-1.47).toString() // text
</syntaxhighlight>


  <!--T:170-->
=== Обновление до версии 1.7 ===
defineAlias("countersTemperature", "wb-msw2_30/Temperature");
  defineAlias("vegetablesTemperature", "wb-msw2_31/Temperature");


  <!--T:171-->
Начиная с версии wb-rules 1.7, локальные переменные и функции, объявленные в файле сценария не видны в других сценариях.
defineAlias("heater1EnableInverted", "wb-mrm2-old_70/Relay 1");
Таким образом, каждый сценарий может определять свои функции и переменные без риска изменить поведение других сценариев.
  defineAlias("frontshopVentInverted", "wb-gpio/EXT1_R3A3");


  <!--T:172-->
В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет переменные и функции.
Schedules.registerSchedule({
В предыдущих версиях wb-rules обращение к переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению.
    "name" : "signboard", // вывеска
В версиях, начиная с 1.7, поведение строго определено и такое же, как будто сценарий единственный в системе.
    "autoUpdate" : "1m",
    "intervals" : [
      [ [12, 30], [20, 30] ],  // в UTC, 15:30 - 23:30 MSK
      [ [3, 30], [5, 20] ],  // в UTC, 6:30 - 8:20 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "ext_working_hours_15m",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [4, 45], [20, 15] ],  // всё ещё UTC, 7:45 - 23:15 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "working_hours",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [5, 0], [19, 0] ], // всё ещё UTC, 8:00 - 22:00 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "working_hours_15m",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [4, 45], [19, 15] ],  // всё ещё UTC, 7:45 - 22:15 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "frontshop_lighting",
    "autoUpdate" : "1m",
    "intervals" : [
      [ [4, 20], [20, 45] ],  // всё ещё UTC, 7:20 -23:45 MSK
    ]
  });
  Schedules.registerSchedule({
    "name" : "heaters_schedule",
    "intervals" : [
      [ [4, 0], [17, 0] ],  // всё ещё UTC, 07:00 - 20:00 MSK дневной режим
    ]
  });
  Schedules.initSchedules();


  <!--T:173-->
Сценарий 1 (rules1.js):
// Вывеска и фасадное освещение
<syntaxhighlight lang="ecmascript">
  defineRule("signboardOnOff", {
var test1 = 42;
    when: function() {
      return dev._schedules.signboard || true;
    },
    then: function (newValue, devName, cellName) {
      log("signboardOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      var on = dev._schedules.signboard; //
     
      dev["wb-mr6c_80/K2"] = !on;
      dev["wb-mr6c_80/K1"] = !on;
      dev["wb-mr6c_80/K3"] = !on;
    }
  });


setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", test2);
}, 5000);
</syntaxhighlight>


  <!--T:174-->
Сценарий 2 (rules2.js):
// Освещение торгового зала
<syntaxhighlight lang="ecmascript">
  defineRule("lightingFrontshopOnOff", {
var test1 = 84;
    when: function() {
var test2 = "Hello";
      return dev._schedules.frontshop_lighting || true;
    },
    then: function (newValue, devName, cellName) {
      log("lightingFrontshopOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-gpio/EXT1_R3A1"] = ! dev._schedules.frontshop_lighting; //инвертированный контактор
    }
  });


setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
}, 5000);
</syntaxhighlight>


  <!--T:175-->
Было:
// Вентиляция подсобного помещения
<pre>
  defineRule("ventBackstoreOnOff", {
2019-01-05 17:29:50 myFuncTwo called
    when: function() {
2019-01-05 17:29:50 test1: 84, test2: Hello
      return dev._schedules.ext_working_hours_15m || true;
2019-01-05 17:29:50 myFuncOne called
    },
2019-01-05 17:29:50 test1: 84
    then: function (newValue, devName, cellName) {
2019-01-05 17:29:50 test2: Hello
      log("ventBackstoreOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
</pre>
      var on = dev._schedules.ext_working_hours_15m;
      dev["wb-mr6c_81/K1"] = ! on; //инвертированный контактор
      dev["wb-mr6c_81/K5"] = ! on; //инвертированный контактор
    }
  });


  <!--T:176-->
Стало:
// Освещение холодильных горок
<pre>
  defineRule("lightingCoolingshelfsOnOff", {
2019-01-05 17:28:42 myFuncTwo called
    when: function() {
2019-01-05 17:28:42 test1: 84, test2: Hello
      return dev._schedules.frontshop_lighting || true;
2019-01-05 17:28:42 myFuncOne called
    },
2019-01-05 17:28:42 test1: 42
    then: function (newValue, devName, cellName) {
2019-01-05 17:28:42 ECMAScript error: ReferenceError: identifier 'test2' undefined
      log("lightingCoolingshelfsOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
duk_js_var.c:1232
      var on = dev._schedules.working_hours_15m;
myFuncOne /etc/wb-rules/rules1.js:6 preventsyield
</pre>


      <!--T:177-->
Возможные способы решения проблемы:
// освещение в горках через нормально-закрытые реле (инвертировано)
      dev["wb-mrm2-old_60/Relay 1"] = !on;
      dev["wb-mrm2-old_61/Relay 1"] = !on;
      dev["wb-mrm2-old_62/Relay 1"] = !on;
      dev["wb-mrm2-old_63/Relay 1"] = !on;
      dev["wb-mrm2-old_64/Relay 1"] = !on;
      dev["wb-mrm2-old_65/Relay 1"] = !on;
      dev["wb-mrm2-old_66/Relay 1"] = !on;
      dev["wb-mrm2-old_67/Relay 1"] = !on;
    }
  });


'''Способ 1'''


  <!--T:178-->
Самый простой способ: перенести всю логику, которая использует доступ к переменной в другм файле в тот же файл, где определена переменная. Т.е. на примере приведённых выше правил нужно перенести всё в один файл rules1.js
//Брендовые холодильники (пиво, лимонады)
<syntaxhighlight lang="ecmascript">
  defineRule("powerBrandFridgesOnOff", {
var test1 = 42;
    when: function() {
var test2 = "Hello";
      return dev._schedules.working_hours || true;
    },
    then: function (newValue, devName, cellName) {
      log("powerBrandFridgesOnOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      var on = dev._schedules.working_hours;
     
      dev["wb-gpio/EXT1_R3A5"] = !on; // инвертировано
    }
  });


setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
}, 5000);


  <!--T:179-->
setInterval(function myFuncOne() {
// ========= Котлы и приточная вентиляция ТЗ  ===========
    log("myFuncOne called");
  // обратная связь по температуре овощной зоны
    log("test1: {}", test1);
    log("test2: {}", test2);
}, 5000);
</syntaxhighlight>


  <!--T:180-->
'''Способ 2'''
// днём работает позиционный регулятор
  defineRule("heatersDayOff", {
    when: function() {
      return (dev._schedules.heaters_schedule) && (vegetablesTemperature > 17.0);
    },
    then: function (newValue, devName, cellName) {
      log("heatersDayOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !false; // инвертировано
    }
  });


  <!--T:181-->
Следующий способ заключается в использовании глобального постоянного хранилища (PersistentStorage) ====
defineRule("heatersDayOn", {
    when: function() {
      return (dev._schedules.heaters_schedule) && (vegetablesTemperature < 16.7);
    },
    then: function (newValue, devName, cellName) {
      log("heatersDayOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !true; // инвертировано
    }
  });


  <!--T:182-->
'''Внимание:''' при использовании глобальных постоянных хранилищ может произойти совпадение имён, в этом случае возможно труднообнаруживаемое нарушение поведения.
// ночью работает позиционный регулятор
  defineRule("heatersNightOff", {
    when: function() {
      return (!dev._schedules.heaters_schedule) && (vegetablesTemperature > 11.6);
    },
    then: function (newValue, devName, cellName) {
      log("heatersNightOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !false; // инвертировано
    }
  });


  <!--T:183-->
Вышеприведённый пример можно исправить следующим образом:
defineRule("heatersNightOn", {
    when: function() {
      return (!dev._schedules.heaters_schedule) && (vegetablesTemperature < 11.3);
    },
    then: function (newValue, devName, cellName) {
      log("heatersNightOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      heater1EnableInverted = !true; // инвертировано
    }
  });


Сценарий 1 (rules1.js):
<syntaxhighlight lang="ecmascript">
var test1 = 42;


  <!--T:184-->
var ps = new PersistentStorage("my-global-storage", {global: true});
// приточная и вытяжная вентиляция принудительно выключены
 
  defineRule("ventFrontshopAlwaysOff", {
    when: cron("@every 1m"),
    then: function() {
  dev["wb-gpio/EXT1_R3A3"] = !false;
  dev["wb-gpio/EXT1_R3A4"] = !false;
    }
  });
 
 
  // ==================  Кассовая зона =================


  <!--T:185-->
ps.test1 = test1;
// в кассовой зоне в рабочее время температура поддерживается кондиционерами (позиционный регулятор)


  <!--T:186-->
setInterval(function myFuncOne() {
defineRule("countersACOn", {
    log("myFuncOne called");
    when: function() {
     log("test1: {}", test1);
      return (dev._schedules.working_hours_15m) && (countersTemperature < 17.7);
     log("test2: {}", ps.test2);
     },
}, 5000);
     then: function (newValue, devName, cellName) {
</syntaxhighlight>
      log("countersACOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_75/Play from ROM7"] = true; // кондиционер кассовой зоны на нагрев
    }
  });


  <!--T:187-->
Сценарий 2 (rules2.js):
// в нерабочее время кондиционер выключен
<syntaxhighlight lang="ecmascript">
  defineRule("countersACOff", {
var test2 = "Hello";
    when: function() {
      return (!dev._schedules.working_hours_15m) || (countersTemperature > 18.0);
    },
    then: function (newValue, devName, cellName) {
      log("countersACOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_75/Play from ROM2"] = true; // кондиционер кассовой зоны выключить
    }
  });


  <!--T:188-->
var ps = new PersistentStorage("my-global-storage", {global: true});
// =============== Овощная зона ==============
  // Охлаждение овощей кондиционером только при температуре воздуха выше 18.5C 


  <!--T:189-->
ps.test2 = test2;
defineRule("acVegOn", {
    when: function() {
      return vegetablesTemperature >= 18.5
    },
    then: function (newValue, devName, cellName) {
      log("acVegOn  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_76/Play from ROM3"] = true; // Охлаждение +18
    }
  });


  <!--T:190-->
setInterval(function myFuncTwo() {
defineRule("acVegOff", {
     log("myFuncTwo called");
    when: function() {
    log("test1: {}, test2: {}", ps.test1, test2);
      return vegetablesTemperature < 17.8
}, 5000);
     },
    then: function (newValue, devName, cellName) {
      log("acVegOff  newValue={}, devName={}, cellName={}", newValue, devName, cellName);
      dev["wb-mir_76/Play from ROM2"] = true; // выключить
    }
  });
})()
</syntaxhighlight>
</syntaxhighlight>




'''Способ 3'''


== Полное описание возможностей движка правил == <!--T:191-->
"Грязный" способ, который использует прототип глобального объекта - использовать этот способ не рекомендуется из-за возможного замусоривания глобального пространства пользовательскими данными.


<!--T:192-->
Исправленная версия:
Самое полное описание движка правил: https://github.com/contactless/wb-rules/blob/master/README.md


Сценарий 1 (rules1.js):
<syntaxhighlight lang="ecmascript">
global.__proto__.test1 = 42;


setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", global.__proto__.test1);
    log("test2: {}", global.__proto__.test2);
}, 5000);
</syntaxhighlight>


== Новые возможности последних версий == <!--T:193-->
Сценарий 2 (rules2.js):
<syntaxhighlight lang="ecmascript">
global.__proto__.test2 = "Hello";


<!--T:194-->
setInterval(function myFuncTwo() {
* [[Special:MyLanguage/Движок_правил_wb-rules_1.7|Движок правил wb-rules 1.7]]
    log("myFuncTwo called");
    log("test1: {}, test2: {}", global.__proto__.test1, global.__proto__.test2);
}, 5000);
</syntaxhighlight>


== В разработке == <!--T:195-->
<!--T:196-->
Описание возможностей будущих версий движка правил можно прочесть здесь:
* [[Special:MyLanguage/Движок_правил_wb-rules_2.0|Движок правил wb-rules 2.0]]


</translate>
</translate>

Версия 13:56, 28 апреля 2020

Редактирование правил в веб-интерфейсе

Для контроллера можно писать правила, например: "Если температура датчика меньше 18°С, включи нагреватель". Правила создаются через веб-интерфейс и пишутся на простом языке, похожем на Javascript.

Самое полное описание движка правил: https://github.com/contactless/wb-rules



Как создавать и редактировать правила

  • Список файлов с правилами находится на странице Scripts веб-интерфейса.
  • Нажмите на название файла, чтобы открыть его для редактирования.
    • Чтобы создать новый файл, нажмите на пункт New..., вверху введите название скрипта (используйте для названия только латинские буквы и цифры, в качестве расширения укажите .js), в основное поле введите текст скрипта, затем нажмите кнопку Save вверху.
  • Правило начинает работать автоматически после нажатия кнопки Save, если в нём нет ошибок (смотрите ниже).

Примечания:

  1. Файлы с правилами хранятся на контроллере в виде обычных текстовых файлов в папке /etc/wb-rules/, поэтому их можно редактировать и загружать напрямую с компьютера.
  2. Правила исполняются сервисом wb-rules, документацию по нему смотрите странице сервиса в Github.


Пишем первое правило

Правило для управления обогревателем, записанное через веб-интерфейс

Правила бывают двух типов - непосредственно правила (начинаются со слов defineRule) и виртуальные устройства (начинаются со слов defineVirtualDevice). Виртуальные устройства - это появляющиеся в веб-интерфейсе новые элементы управления - например, кнопка-выключатель, которая на самом деле выключает два устройства одновременно. Она не привязана напрямую ни к какому физическому устройству, а действия при её нажатии определяются написанным вами скриптом.

Любое количество разных правил можно хранить в одном файле. Обычно в одном файле хранятся правила, отвечающие за близкие функции.


Первое правило

Для начала разберём простое правило - при превышении температуры выключи обогреватель. Температуру получаем с датчика 1-Wire, обогреватель подключён к Реле 1 внешнего релейного модуля WB-MRM2.

defineRule("heater_control", { //название правила - "контроль обогревателя", может быть произвольным
  whenChanged: "wb-w1/28-0115a48fcfff", //при изменении состояния датчика 1-Wire с идентификатором 28-0115a48fcfff
  then: function (newValue, devName, cellName) { //выполняй следующие действия
    if ( newValue > 30) { //если температура датчика больше 30 градусов
      dev["wb-mrm2_130"]["Relay 1"] = 0; //установи Реле 1 модуля WB-MRM2 с адресом 130 в состояние "выключено"
    } else {
      dev["wb-mrm2_130"]["Relay 1"] = 1; //установи Реле 1 модуля WB-MRM2 с адресом 130 в состояние "включено"
    }
  }
});
  • Первая строка - кодовое слово defineRule и название правила
  • Вторая строка - кодовое слово для определения, когда выполняется правило, - whenChanged - "при изменении параметра", далее название параметра, при изменении которого запустится правило - температура с датчика 1-Wire. Название параметра записывается в виде "Device/Control", где названия Device и Control для каждого параметра можно найти на странице Settings веб-интерфейса в таблице MQTT Channels.
  • Третья строка - начало функции, которая будет исполняться
  • Затем идёт условие - "если значение температуры больше порогового, то ...". Значение параметра записывается в виде dev[Device][Control] - заметьте, оно отличается от вида записи параметра, при изменении которого запускается правило, потому что там речь идёт о параметре, а здесь - о значении того же параметра.
  • Затем мы выставляем значения для реле в каждом случае - 0 - "выключено", 1 - "включено". Названия Device и Control для реле смотрим всё в той же таблице MQTT Channels на странице Settings веб-интерфейса.


Первое правило с виртуальным устройством

Создаём виртуальный переключатель, при нажатии на который переключаются сразу два реле.

defineVirtualDevice("switch_both", {
    title: "Switch both relays",
    cells: {
	enabled: {
	    type: "switch",
	    value: false
	},
    }
});

defineRule("control_both", {
  whenChanged: "switch_both/enabled",
  then: function (newValue, devName, cellName)  {
	dev["wb-mrm2_130"]["Relay 1"] = newValue;
    dev["wb-mrm2_130"]["Relay 2"] = newValue;
  }
});


Виртуальное устройство defineVirtualDevice

Виртуальные устройства - это появляющиеся в веб-интерфейсе новые элементы управления - например, кнопка-выключатель, которая на самом деле выключает два устройства одновременно. Она не привязана напрямую ни к какому физическому устройству, а действия при её нажатии определяются написанным вами скриптом. При написании скрипта вы можете создать виртуальное устройство для включения/выключения тех или иных управляющих алгоритмов и установки их параметров.

defineVirtualDevice("wb-1", {
  title: "Отопление",
  cells: {
    "Температура": {
    type: "range",
    max: 24,
    value: 20,
    },
    "Вкл.": {
    type: "switch",
    value: false,
    }
  }
});

Типы устройств (type):

switch — переключатель. Может принимать значения true или false. По-умолчанию доступен для изменения пользователем.

wo-switch — переключатель, аналог switch. Может принимать значения true или false. По-умолчанию не доступен для изменения пользователем.

pushbutton — кнопка. Может принимать значения true. По-умолчанию доступна для нажатия. range — ползунок. Может принимать значения от 0 до max. По-умолчанию доступен для изменения пользователем.

rgb — специальный тип для задания цвета. Кодируется 3 числами от 0 до 255, разделенными точкой с запятой. Например „255;0;0“ задает красный цвет. По-умолчанию доступен для изменения пользователем.

alarm — индикатор. Может принимать значения true или false.

text — текстовое поле. По-умолчанию не доступно для редактирования пользователем.

value — значение с плавающей точкой. По-умолчанию не доступно для редактирования пользователем.

Так же существуют еще 14 специальных типов. Все они аналогичны value, но имеют соответствующие подписи в интерфейсе.

Type meta/type units value format
Temperature temperature °C float
Relative humidity rel_humidity %, RH float, 0 - 100
Atmospheric pressure atmospheric_pressure millibar (100 Pa) float
Precipitation rate (rainfall rate) rainfall mm per hour float
Wind speed wind_speed m/s float
Power power watt float
Power consumption power_consumption kWh float
Voltage voltage volts float
Water flow water_flow m^3 / hour float
Water total consumption water_consumption m^3 float
Resistance resistance resistance float
Gas concentration concentration ppm float (unsigned)
Heat power heat_power Gcal / hour float
Heat energy heat_energy Gcal float

value:

Обязательное поле. При создании устройства в первый раз его значение будет установлено в значение по-умолчанию. В дальнейшем значения сохраняются в специальное хранилище в постоянной памяти и восстанавливаются при загрузке сценария.

forceDefault:

Если необходимо каждый раз при перезагрузке скрипта восстанавливать строго определённое значение (т.е. не восстанавливать предыдущее сохранённое), нужно добавить в описание контрола поле forceDefault: true

max:

Для параметра типа range может задавать его максимально допустимое значение.

readonly:

Когда задано истинное значение — устройство становится не доступным для редактирования пользователем. Если надо предоставить пользователю возможность редактировать значение, следует добавить в описание readonly: false


Типы правил defineRule

whenChanged

Правило срабатывает при любых изменениях значений параметров или функций В примере кнопка подключена ко входу А1 контроллера. При нажатии на кнопку срабатывает реле 1 устройства MRM2-mini, при отжатии реле возвращается в исходное состояние.

defineRule("test_rule", { //имя правила test_rule
  whenChanged: "wb-gpio/A1_IN",
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 1"] = newValue;
  }
});

asSoonAs

Правила, задаваемые при помощи asSoonAs, срабатывают в случае, когда значение, возвращаемое функцией, заданной в asSoonAs, становится истинным при том, что при предыдущем просмотре данного правила оно было ложным.

defineRule({
  asSoonAs: function() {
    return dev["wb-gpio"]["A2_IN"];
  },
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 2"] = true;
  }
});
defineRule({
  asSoonAs: function() {
    return dev["wb-gpio"]["A3_IN"];
  },
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 2"] = false;
  }
});

При нажатии на кнопку, подключенную к входу А2, включаем реле, а при нажатии на кнопку, подключенную к входу А3 — выключаем.


when

Правила, задаваемые при помощи when срабатывают при каждом просмотре, при котором функция, заданная в when, возвращает истинное значение. При срабатывании правила выполняется функция, заданная в свойстве when.

defineVirtualDevice("test_button2", {
  title: "test_relay2",
  cells: {
    "switch_on": {
    type: "pushbutton",
    value: false,
    },
    "switch_off": {
    type: "pushbutton",
    value: false,
    }
  }
});

defineRule({
  when: function() {
    return dev["test_button2"]["switch_on"];
  },
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 2"] = true;
  }
});

defineRule({
  when: function() {
    return dev["test_button2"]["switch_off"];
  },
  then: function (newValue, devName, cellName) {
    dev["wb-mrm2-mini_2"]["Relay 2"] = false;
  }
});

При нажатии на кнопку switch_on, включаем реле, а при нажатии на кнопку,switch_off — выключаем.

cron-правила

Отдельный тип правил - cron-правила. Такие правила задаются следующим образом:

defineRule("crontest_hourly", {
  when: cron("@hourly"),
  then: function () {
    log("@hourly rule fired");
  }
})

Вместо @hourly здесь можно задать любое выражение, допустимое в стандартном crontab, например, 0 20 * * * (выполнять правило каждый день в 20:00). Помимо стандартных выражений допускается использование ряда расширений, см. описание формата выражений используемой cron-библиотеки.


Таймеры

setTimeout();

С помощью данного таймера можно отсрочить выполнение правила на определенное время. В примере после нажатия кнопки включается пищалка и затем выключается спустя 2 секунды.

defineVirtualDevice("test_buzzer", {
title: "Test Buzzer",
  cells: {
    enabled: {
      type: "pushbutton",
      value: false
    }
  }
});

defineRule({
  whenChanged: "test_buzzer/enabled",
    then: function (newValue, devName, cellName) {
    dev["buzzer"]["enabled"] = true;
    setTimeout(function () {
      dev["buzzer"]["enabled"] = false;
    }, 2000);
  }
});

setInterval();

defineVirtualDevice("test_buzzer", {
  title: "Test Buzzer",
  cells: {
    enabled: {
      type: "pushbutton",
      value: false
    }
  }
});


var test_interval = null;

defineRule({                                              
  whenChanged: "test_buzzer/enabled",
  then: function (newValue, devName, cellName) {
    var n = 0;
    if (dev["test_buzzer"]["enabled"]){
      test_interval = setInterval(function () {
 	dev["buzzer"]["enabled"] = !dev["buzzer"]["enabled"];
        n = n+1;
        if (n >= 10){
          clearInterval(test_interval);
        }
      }, 500); 
    }
  }
});

startTimer();

Запускает однократный таймер. При срабатывании таймера происходит просмотр правил, при этом timers.<name>.firing для этого таймера становится истинным на время этого просмотра.

defineVirtualDevice("test_buzzer", {
  title: "Test Buzzer",
  cells: {
    enabled: {
      type: "switch",
      value: false
    }
  }
});

defineRule("1",{
  asSoonAs: function () {
    return dev["test_buzzer"]["enabled"] ;
  },
  then: function () {
    startTimer("one_second", 1000);
    dev["buzzer"]["enabled"] = true;//включаем пищалку
  }
});

defineRule("2",{
  when: function () { return timers.one_second.firing; },
  then: function () {
    dev["buzzer"]["enabled"] = false;//выключаем пищалку
    dev["test_buzzer"]["enabled"] = false;
  }
});

startTicker();

Запускает периодический таймер с указанным интервалом. В примере правило 1 запускает таймер с интервалом 1 сек. Вызывая срабатывание правила 2. Метод stop() приводит к остановке таймера.

defineVirtualDevice("test_buzzer", {
  title: "Test Buzzer",
  cells: {
    enabled: {
      type: "switch",
      value: false
    }
  }
});

defineRule("1",{
  asSoonAs: function () {
    return dev["test_buzzer"]["enabled"] ;
  },
  then: function () {
    startTicker("one_second", 1000);
  }
});
defineRule("2",{
  when: function () { return timers.one_second.firing; },
  then: function () {
    dev["buzzer"]["enabled"] = !dev["buzzer"]["enabled"];
    if (dev["test_buzzer"]["enabled"] == false){
      timers.one_second.stop();
    }
  }
});

Сообщения в лог

В зависимости от функции сообщение классифицируется как отладочное (debug), информационное (info), предупреждение (warning) или сообщение об ошибке (error).

defineVirtualDevice("test_buzzer", {
  title: "Test Buzzer",
  cells: {
    enabled: {
      type: "pushbutton",
      value: false
    }
  }
});

defineRule({
  whenChanged: "test_buzzer/enabled",
  then: function (newValue, devName, cellName) {
    log.info('info');
    log.debug('debug');
    log.error('error');
    log.warning('warning');
    log('log!'); //сокращение для log.info();
    debug('deb!'); //сокращение для log.debug();
  }
});

Выполнение произвольной команды runShellCommand();

defineVirtualDevice("test_button", {
  title: "Test Button",
  cells: {
    enabled: {
      type: "pushbutton",
      value: false
    }
  }
});

defineRule({
  whenChanged: "test_button/enabled",
  then: function (newValue, devName, cellName) {
    runShellCommand("mosquitto_pub -t '/devices/test_button/controls/enabled/meta/readonly' -r -m 1");
    setTimeout(function () {
      runShellCommand("mosquitto_pub -t '/devices/test_button/controls/enabled/meta/readonly' -r -m 0");
    }, 5000);
  }
});


Пишем сложные правила

Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил:

  1. Примеры правил:
  2. Полное описание движка правил.

Совместимость скриптов при обновлении wb-rules

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

Обновление до версии 2.2

С версии 2.2 стали более строго проверяться типы устанавливаемых значений для контролов:

если для установления признака включено/выключено для контролов типа switch, клики по pushbutton или присваивание значений контролам с типом text применялась недокументировання возможность использования для этой цели числовые значения (1, 0 и т.д.) в версии движка 2.2 операция присваивания не выполнится и завершится с ошибкой. Корректный способ — устанавливать булевы значений (true/false) для switch/pushbutton и строковые значения для типа text

При возникновении подобной проблемы в логах можно видеть подобные записи:

ERROR: control wb-mr3_30/K1 SetValue() error: can't convert control value '1' (type float64) to datatype 'switch'
ERROR: control system/Reboot SetValue() error: can't convert control value '1' (type float64) to datatype 'pushbutton'
ERROR: control status/someStatus SetValue() error: can't convert control value '-1.47' (type float64) to datatype 'text'

Вместо:

dev["wb-mr3_30"]["K1"] = 1 // включение
dev["status"]["someStatus"] = -1.47 // float

Нужно:

dev["wb-mr3_30"]["K1"] = true // включение
dev["status"]["someStatus"] = (-1.47).toString() // text

Обновление до версии 1.7

Начиная с версии wb-rules 1.7, локальные переменные и функции, объявленные в файле сценария не видны в других сценариях. Таким образом, каждый сценарий может определять свои функции и переменные без риска изменить поведение других сценариев.

В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет переменные и функции. В предыдущих версиях wb-rules обращение к переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению. В версиях, начиная с 1.7, поведение строго определено и такое же, как будто сценарий единственный в системе.

Сценарий 1 (rules1.js):

var test1 = 42;

setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", test2);
}, 5000);

Сценарий 2 (rules2.js):

var test1 = 84;
var test2 = "Hello";

setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
}, 5000);

Было:

2019-01-05 17:29:50 myFuncTwo called
2019-01-05 17:29:50 test1: 84, test2: Hello
2019-01-05 17:29:50 myFuncOne called
2019-01-05 17:29:50 test1: 84
2019-01-05 17:29:50 test2: Hello

Стало:

2019-01-05 17:28:42 myFuncTwo called
2019-01-05 17:28:42 test1: 84, test2: Hello
2019-01-05 17:28:42 myFuncOne called
2019-01-05 17:28:42 test1: 42
2019-01-05 17:28:42 ECMAScript error: ReferenceError: identifier 'test2' undefined
duk_js_var.c:1232
myFuncOne /etc/wb-rules/rules1.js:6 preventsyield

Возможные способы решения проблемы:

Способ 1

Самый простой способ: перенести всю логику, которая использует доступ к переменной в другм файле в тот же файл, где определена переменная. Т.е. на примере приведённых выше правил нужно перенести всё в один файл rules1.js

var test1 = 42;
var test2 = "Hello";

setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
}, 5000);

setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", test2);
}, 5000);

Способ 2

Следующий способ заключается в использовании глобального постоянного хранилища (PersistentStorage) ====

Внимание: при использовании глобальных постоянных хранилищ может произойти совпадение имён, в этом случае возможно труднообнаруживаемое нарушение поведения.

Вышеприведённый пример можно исправить следующим образом:

Сценарий 1 (rules1.js):

var test1 = 42;

var ps = new PersistentStorage("my-global-storage", {global: true});

ps.test1 = test1;

setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", test1);
    log("test2: {}", ps.test2);
}, 5000);

Сценарий 2 (rules2.js):

var test2 = "Hello";

var ps = new PersistentStorage("my-global-storage", {global: true});

ps.test2 = test2;

setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", ps.test1, test2);
}, 5000);


Способ 3

"Грязный" способ, который использует прототип глобального объекта - использовать этот способ не рекомендуется из-за возможного замусоривания глобального пространства пользовательскими данными.

Исправленная версия:

Сценарий 1 (rules1.js):

global.__proto__.test1 = 42;

setInterval(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}", global.__proto__.test1);
    log("test2: {}", global.__proto__.test2);
}, 5000);

Сценарий 2 (rules2.js):

global.__proto__.test2 = "Hello";

setInterval(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", global.__proto__.test1, global.__proto__.test2);
}, 5000);