Как писать правила
Виртуальное устройство defineVirtualDevice
Правила делятся на два типа: непосредственно правила (defineRule) и виртуальные устройства (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);
}
});
Примеры
Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил:
- Примеры правил:
- в статье Примеры правил;
- в специальной теме на нашем форуме .
- Полное описание движка правил.
Совместимость скриптов при обновлении 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);