Примеры правил wb-rules: различия между версиями
Fizikdaos (обсуждение | вклад) (Перенаправление на Rule Examples) Метка: новое перенаправление |
Gavrilov (обсуждение | вклад) |
||
(не показано 8 промежуточных версий 2 участников) | |||
Строка 1: | Строка 1: | ||
# | <languages/> | ||
<translate> | |||
<!--T:1--> | |||
[[File:Wb rules demo.png|400px|thumb|right|Редактирование правил в веб-интерфейсе]] | |||
Для контроллера можно писать правила, например: "Если температура датчика меньше 18°С, включи нагреватель". Правила создаются через [[Special:MyLanguage/Веб-интерфейс Wiren Board|веб-интерфейс]] и пишутся на простом языке, похожем на Javascript. | |||
<!--T:2--> | |||
Самое полное описание движка правил: https://github.com/contactless/wb-rules | |||
== Как создавать и редактировать правила == <!--T:3--> | |||
<!--T:4--> | |||
*Список файлов с правилами находится на странице ''Scripts'' веб-интерфейса. | |||
*Нажмите на название файла, чтобы открыть его для редактирования. | |||
**Чтобы создать новый файл, нажмите на пункт ''New...'', вверху введите название скрипта (используйте для названия только латинские буквы и цифры, в качестве расширения укажите ''.js''), в основное поле введите текст скрипта, затем нажмите кнопку ''Save'' вверху. | |||
*Правило начинает работать автоматически после нажатия кнопки ''Save'', если в нём нет ошибок (смотрите ниже). | |||
Примечания: | |||
#Файлы с правилами хранятся на контроллере в виде обычных текстовых файлов в папке <code>/etc/wb-rules/</code>, поэтому [[Special:MyLanguage/Просмотр файлов контроллера с компьютера|их можно редактировать и загружать напрямую с компьютера]]. | |||
#Правила исполняются сервисом ''wb-rules'', документацию по нему смотрите [https://github.com/contactless/wb-rules странице сервиса в Github]. | |||
== Пишем первое правило == <!--T:5--> | |||
<!--T:6--> | |||
[[File:Web-scripts-rule1.png|400px|thumb|Правило для управления обогревателем, записанное через веб-интерфейс]] | |||
<!--T:7--> | |||
Правила бывают двух типов - непосредственно правила (начинаются со слов ''defineRule'') и виртуальные устройства (начинаются со слов ''defineVirtualDevice''). Виртуальные устройства - это появляющиеся в веб-интерфейсе новые элементы управления - например, кнопка-выключатель, которая на самом деле выключает два устройства одновременно. Она не привязана напрямую ни к какому физическому устройству, а действия при её нажатии определяются написанным вами скриптом. | |||
<!--T:8--> | |||
Любое количество разных правил можно хранить в одном файле. Обычно в одном файле хранятся правила, отвечающие за близкие функции. | |||
=== Первое правило === <!--T:9--> | |||
<!--T:10--> | |||
Для начала разберём простое правило - при превышении температуры выключи обогреватель. Температуру получаем с датчика [[Special:MyLanguage/1-Wire|1-Wire]], обогреватель подключён к Реле 1 внешнего релейного модуля [[Special:MyLanguage/WB-MRM2|WB-MRM2]]. | |||
<syntaxhighlight lang="ecmascript"> | |||
<!--T:11--> | |||
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 в состояние "включено" | |||
} | |||
} | |||
}); | |||
<!--T:12--> | |||
</syntaxhighlight> | |||
*Первая строка - кодовое слово ''defineRule'' и название правила | |||
*Вторая строка - кодовое слово для определения, когда выполняется правило, - ''whenChanged'' - "при изменении параметра", далее название параметра, при изменении которого запустится правило - температура с датчика 1-Wire. Название параметра записывается в виде "Device/Control", где названия ''Device'' и ''Control'' для каждого параметра можно найти на странице ''Settings'' веб-интерфейса в таблице ''MQTT Channels''. | |||
*Третья строка - начало функции, которая будет исполняться | |||
*Затем идёт условие - "если значение температуры больше порогового, то ...". Значение параметра записывается в виде ''dev[Device][Control]'' - заметьте, оно отличается от вида записи параметра, при изменении которого запускается правило, потому что там речь идёт о ''параметре'', а здесь - о ''значении'' того же параметра. | |||
*Затем мы выставляем значения для реле в каждом случае - ''0'' - "выключено", ''1'' - "включено". Названия ''Device'' и ''Control'' для реле смотрим всё в той же таблице ''MQTT Channels'' на странице ''Settings'' веб-интерфейса. | |||
=== Первое правило с виртуальным устройством === <!--T:13--> | |||
<!--T:14--> | |||
Создаём виртуальный переключатель, при нажатии на который переключаются сразу два реле. | |||
<!--T:15--> | |||
<syntaxhighlight lang="ecmascript"> | |||
defineVirtualDevice("switch_both", { | |||
title: "Switch both relays", | |||
cells: { | |||
enabled: { | |||
type: "switch", | |||
value: false | |||
}, | |||
} | |||
}); | |||
<!--T:16--> | |||
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; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
== Виртуальное устройство defineVirtualDevice == <!--T:17--> | |||
Виртуальные устройства - это появляющиеся в веб-интерфейсе новые элементы управления - например, кнопка-выключатель, которая на самом деле выключает два устройства одновременно. Она не привязана напрямую ни к какому физическому устройству, а действия при её нажатии определяются написанным вами скриптом. | |||
При написании скрипта вы можете создать виртуальное устройство для включения/выключения тех или иных управляющих алгоритмов и установки их параметров. | |||
<!--T:20--> | |||
<syntaxhighlight lang="ecmascript"> | |||
defineVirtualDevice("wb-1", { | |||
title: "Отопление", | |||
cells: { | |||
"Температура": { | |||
type: "range", | |||
max: 24, | |||
value: 20, | |||
}, | |||
"Вкл.": { | |||
type: "switch", | |||
value: false, | |||
} | |||
} | |||
}); | |||
</syntaxhighlight> | |||
=== Типы устройств (type): === | |||
'''switch''' — переключатель. Может принимать значения true или false. По-умолчанию доступен для изменения пользователем. | |||
<!--T:21--> | |||
'''wo-switch''' — переключатель, аналог switch. Может принимать значения true или false. По-умолчанию не доступен для изменения пользователем. | |||
<!--T:22--> | |||
'''pushbutton''' — кнопка. Может принимать значения true. По-умолчанию доступна для нажатия. | |||
range — ползунок. Может принимать значения от 0 до max. По-умолчанию доступен для изменения пользователем. | |||
<!--T:23--> | |||
'''rgb''' — специальный тип для задания цвета. Кодируется 3 числами от 0 до 255, разделенными точкой с запятой. Например „255;0;0“ задает красный цвет. По-умолчанию доступен для изменения пользователем. | |||
<!--T:24--> | |||
'''alarm''' — индикатор. Может принимать значения true или false. | |||
<!--T:25--> | |||
'''text''' — текстовое поле. По-умолчанию не доступно для редактирования пользователем. | |||
<!--T:26--> | |||
'''value''' — значение с плавающей точкой. По-умолчанию не доступно для редактирования пользователем. | |||
<!--T:27--> | |||
Так же существуют еще 14 специальных типов. Все они аналогичны value, но имеют соответствующие подписи в интерфейсе. | |||
<!--T:28--> | |||
{| 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--> | |||
=== value: === | |||
Обязательное поле. При создании устройства в первый раз его значение будет установлено в значение по-умолчанию. В дальнейшем значения сохраняются в специальное хранилище в постоянной памяти и восстанавливаются при загрузке сценария. | |||
<!--T:31--> | |||
=== forceDefault: === | |||
Если необходимо каждый раз при перезагрузке скрипта восстанавливать строго определённое значение (т.е. не восстанавливать предыдущее сохранённое), нужно добавить в описание контрола поле f'''orceDefault: true''' | |||
<!--T:33--> | |||
=== max: === | |||
Для параметра типа range может задавать его максимально допустимое значение. | |||
<!--T:35--> | |||
=== readonly: === | |||
Когда задано истинное значение — устройство становится не доступным для редактирования пользователем. Если надо предоставить пользователю возможность редактировать значение, следует добавить в описание '''readonly: false''' | |||
== Типы правил defineRule == | |||
=== whenChanged === | |||
Правило срабатывает при любых изменениях значений параметров или функций | |||
В примере кнопка подключена ко входу А1 контроллера. При нажатии на кнопку срабатывает реле 1 устройства MRM2-mini, при отжатии реле возвращается в исходное состояние. | |||
<!--T:40--> | |||
<syntaxhighlight lang="ecmascript"> | |||
defineRule("test_rule", { //имя правила test_rule | |||
whenChanged: "wb-gpio/A1_IN", | |||
then: function (newValue, devName, cellName) { | |||
dev["wb-mrm2-mini_2"]["Relay 1"] = newValue; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
<!--T:41--> | |||
=== asSoonAs === | |||
Правила, задаваемые при помощи asSoonAs, срабатывают в случае, когда значение, возвращаемое функцией, заданной в asSoonAs, становится истинным при том, что при предыдущем просмотре данного правила оно было ложным. | |||
<!--T:43--> | |||
<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; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
При нажатии на кнопку, подключенную к входу А2, включаем реле, а при нажатии на кнопку, подключенную к входу А3 — выключаем. | |||
=== when === | |||
Правила, задаваемые при помощи when срабатывают при каждом просмотре, при котором функция, заданная в when, возвращает истинное значение. При срабатывании правила выполняется функция, заданная в свойстве when. | |||
<syntaxhighlight lang="ecmascript"> | |||
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; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
При нажатии на кнопку switch_on, включаем реле, а при нажатии на кнопку,switch_off — выключаем. | |||
<!--T:49--> | |||
=== cron-правила === | |||
Отдельный тип правил - cron-правила. Такие правила задаются следующим образом: | |||
<syntaxhighlight lang="ecmascript"> | |||
defineRule("crontest_hourly", { | |||
when: cron("@hourly"), | |||
then: function () { | |||
log("@hourly rule fired"); | |||
} | |||
}) | |||
</syntaxhighlight> | |||
Вместо @hourly здесь можно задать любое выражение, допустимое в стандартном crontab, например, 0 20 * * * (выполнять правило каждый день в 20:00). Помимо стандартных выражений допускается использование ряда расширений, см. описание формата выражений используемой cron-библиотеки. | |||
== Таймеры == | |||
=== setTimeout(); === | |||
С помощью данного таймера можно отсрочить выполнение правила на определенное время. В примере после нажатия кнопки включается пищалка и затем выключается спустя 2 секунды. | |||
<syntaxhighlight lang="ecmascript"> | |||
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); | |||
} | |||
}); | |||
</syntaxhighlight> | |||
<!--T:59--> | |||
=== setInterval(); ===<!--T:60--> | |||
<syntaxhighlight lang="ecmascript"> | |||
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); | |||
} | |||
} | |||
}); | |||
</syntaxhighlight> | |||
<!--T:61--> | |||
=== startTimer(); === | |||
Запускает однократный таймер. При срабатывании таймера происходит просмотр правил, при этом timers.<name>.firing для этого таймера становится истинным на время этого просмотра. | |||
<!--T:63--> | |||
<syntaxhighlight lang="ecmascript"> | |||
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; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
<!--T:64--> | |||
=== startTicker(); === | |||
Запускает периодический таймер с указанным интервалом. В примере правило 1 запускает таймер с интервалом 1 сек. Вызывая срабатывание правила 2. Метод stop() приводит к остановке таймера. | |||
<!--T:66--> | |||
<syntaxhighlight lang="ecmascript"> | |||
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(); | |||
} | |||
} | |||
}); | |||
</syntaxhighlight> | |||
<!--T:67--> | |||
== Сообщения в лог == | |||
В зависимости от функции сообщение классифицируется как отладочное (debug), информационное (info), предупреждение (warning) или сообщение об ошибке (error). | |||
<!--T:69--> | |||
<syntaxhighlight lang="ecmascript"> | |||
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(); | |||
} | |||
}); | |||
</syntaxhighlight> | |||
<!--T:70--> | |||
== Выполнение произвольной команды runShellCommand(); == | |||
<syntaxhighlight lang="ecmascript"> | |||
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); | |||
} | |||
}); | |||
</syntaxhighlight> | |||
== Пишем сложные правила == <!--T:189--> | |||
<!--T:190--> | |||
Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил: | |||
#Примеры правил: | |||
#* на этой же странице ниже; | |||
#* в [http://forums.contactless.ru/t/dvizhok-pravil-primery-koda/483 специальной теме на нашем форуме ]. | |||
#[https://github.com/contactless/wb-rules Полное описание движка правил]. | |||
== Совместимость скриптов при обновлении wb-rules == | |||
* [[Special:MyLanguage/Движок_правил_wb-rules_1.7|Движок правил wb-rules 1.7]] | |||
* [[Special:MyLanguage/Движок_правил_wb-rules_2.0|Движок правил wb-rules 2.0]] | |||
Предполагается, что при обновлении с предыдущей на следующую версию 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> | |||
</translate> |
Версия 13:39, 28 апреля 2020
Для контроллера можно писать правила, например: "Если температура датчика меньше 18°С, включи нагреватель". Правила создаются через веб-интерфейс и пишутся на простом языке, похожем на Javascript.
Самое полное описание движка правил: https://github.com/contactless/wb-rules
Как создавать и редактировать правила
- Список файлов с правилами находится на странице Scripts веб-интерфейса.
- Нажмите на название файла, чтобы открыть его для редактирования.
- Чтобы создать новый файл, нажмите на пункт New..., вверху введите название скрипта (используйте для названия только латинские буквы и цифры, в качестве расширения укажите .js), в основное поле введите текст скрипта, затем нажмите кнопку Save вверху.
- Правило начинает работать автоматически после нажатия кнопки Save, если в нём нет ошибок (смотрите ниже).
Примечания:
- Файлы с правилами хранятся на контроллере в виде обычных текстовых файлов в папке
/etc/wb-rules/
, поэтому их можно редактировать и загружать напрямую с компьютера. - Правила исполняются сервисом 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);
}
});
Пишем сложные правила
Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил:
- Примеры правил:
- на этой же странице ниже;
- в специальной теме на нашем форуме .
- Полное описание движка правил.
Совместимость скриптов при обновлении 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);