Как писать правила

From Wiren Board


Виртуальное устройство 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;
  }
});

При нажатии на кнопку, подключенную к входу А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, например, 00 00 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);