Wb-rules 2.0: различия между версиями

Материал из Wiren Board
(Убрал кавычки в при каждой загрузке сценария поле mycell будет получать значение 10)
(Перенаправление на Wb-rules)
Метка: новое перенаправление
 
(не показано 6 промежуточных версий этого же участника)
Строка 1: Строка 1:
<languages/>
#REDIRECT [[wb-rules]]
<translate>
<!--T:1-->
В обновлённом движке правил wb-rules присутствует ряд важных нововведений, касающихся логики написания сценариев.
 
= Сценарии = <!--T:2-->
 
 
== Изоляция сценариев == <!--T:3-->
 
<!--T:4-->
Начиная с версии wb-rules 2.0, каждый файл сценария запускается в своём отдельном пространстве имён - '''контексте'''. Таким образом, каждый сценарий может определять
свои функции и глобальные переменные без риска изменить поведение других сценариев.
 
 
=== Пример === <!--T:5-->
 
<!--T:6-->
В качестве примера приведём два сценария, одновременно запускаемых в движке правил.
Каждый сценарий определяет глобальные переменные и функции.
 
<!--T:7-->
В предыдущих версиях wb-rules обращение к глобальной переменной, изменяемой в нескольких
файлах сценариев, может привести к неопределённому поведению. В версиях, начиная с 2.0,
поведение строго определено и такое же, как будто сценарий единственный в системе.
 
<!--T:8-->
В комментариях указан вывод команд log для ранних версий и для актуальной версии.
 
<!--T:9-->
'''Сценарий 1 (rules1.js)'''
<syntaxhighlight lang="js">
test1 = 42;
 
<!--T:10-->
setTimeout(function myFuncOne() {
    log("myFuncOne called");
    log("test1: {}, test2: {}", test1, test2);
    // раньше: test1: [либо 42, либо 84], test2: Hello
    // теперь: test1: 42, test2: (undefined)
    // (будет выведена ошибка выполнения: ReferenceError: identifier 'test2' undefined)
}, 1000);
</syntaxhighlight>
 
<!--T:11-->
'''Сценарий 2 (rules2.js)'''
<syntaxhighlight lang="js">
test1 = 84;
test2 = "Hello";
 
<!--T:12-->
setTimeout(function myFuncTwo() {
    log("myFuncTwo called");
    log("test1: {}, test2: {}", test1, test2);
    // раньше: test1: [либо 42, либо 84], test2: Hello
    // теперь: test1: 84, test2: Hello
}, 1000);
</syntaxhighlight>
 
 
=== Примечание === <!--T:13-->
 
<!--T:14-->
В предыдущих версиях wb-rules для изоляции правил рекомендовалось использовать замыкание, т.е.
оборачивание кода сценария в конструкцию:
<syntaxhighlight lang="js">
(function() {
    // код сценария идёт здесь
})();
</syntaxhighlight>
 
<!--T:15-->
Начиная с версии 2.0, в подобной конструкции нет необходимости. Тем не менее, старые сценарии,
использующие эту конструкцию, продолжат работу без изменений в поведении.
 
 
=== Обходные пути === <!--T:16-->
 
<!--T:17-->
Если в вашей системе использовалось общее глобальное пространство для хранения общих данных и функций,
есть несколько способов реализации такого поведения:
 
 
==== Использование модулей ==== <!--T:18-->
 
<!--T:19-->
Можно написать модуль для организации взаимодействия. У модулей есть статическое хранилище,
общее для всех файлов, импортировавших модуль. (см. [[#module.static|module.static]])
 
 
==== Постоянное хранилище ==== <!--T:20-->
 
<!--T:21-->
Для обмена данными также можно использовать глобальные постоянные хранилища (PersistentStorage).
 
<!--T:22-->
'''Внимание:''' при использовании глобальных постоянных хранилищ может произойти совпадение имён, в
этом случае возможно труднообнаруживаемое нарушение поведения.
 
<!--T:23-->
<syntaxhighlight lang="js">
var ps = new PersistentStorage("my-global-storage", {global: true});
 
<!--T:24-->
/// ...
 
<!--T:25-->
ps.myvar = "value"; // это значение доступно для всех пользователей хранилища с именем "my-global-storage"
</syntaxhighlight>
 
 
==== Прототип глобального объекта ==== <!--T:26-->
 
<!--T:27-->
'''ВНИМАНИЕ:''' метод считается "грязным", т.к. все переменные и функции, опубликованные таким образом,
становятся доступными всем сценариям в системе. Старайтесь избегать этого способа. За неопределённое
поведение при использовании этого метода несёт ответственность сам программист.
 
<!--T:28-->
Глобальные объекты всех сценариев имеют общий объект-''прототип'', в котором определены стандартные функции
wb-rules (такие, как defineRule, setTimeout и т.д.). Через него можно передавать переменные или функции
в общую область видимости.
 
<!--T:29-->
<syntaxhighlight lang="js">
global.__proto__.myVar = 42; // теперь myVar - общая переменная для всех сценариев
 
<!--T:30-->
// из других сценариев к переменной можно обращаться так
log("shared myVar: {}", myVar);
 
<!--T:31-->
// или вот так, что чуть более аккуратно, т.к. однозначно показывает, где определена переменная
log("shared myVar: {}", global.__proto__.myVar);
</syntaxhighlight>
 
<!--T:32-->
Правило поиска переменной в первом случае будет выглядеть так:
 
<!--T:33-->
# Проверяем, есть ли myVar среди локальных переменных (определённой как var myVar = ...).
# Если нет, проверяем, есть ли myVar в глобальном объекте (определённой как myVar = ...).
# Если нет, проверяем, есть ли myVar в ''прототипе'' глобального объекта (определённой как global.__proto__.myVar).
 
<!--T:34-->
Поиск останавливается, как только переменная найдена.
 
<!--T:35-->
Таким образом, первый способ обращения будет работать только в том случае, если myVar не определена в верхних областях видимости.
 
 
== Анонимные правила == <!--T:36-->
 
<!--T:37-->
Теперь правила можно объявлять анонимно (без задания специального имени). Это позволит уменьшить путаницу и неочевидное
поведение системы при дублировании имён правил в одном скрипте.
 
<!--T:38-->
Уникальные имена для анонимных правил генерируются автоматически.
 
<!--T:39-->
Старый синтаксис (с явным заданием имени правила) продолжит работу без изменений.
 
<!--T:40-->
'''ВНИМАНИЕ:''' начиная с версии 2.0, при объявлении правил с одинаковыми
именами в одном файле теперь будет возвращаться ошибка.
 
 
=== Пример === <!--T:41-->
 
<!--T:42-->
<syntaxhighlight lang="js">
defineRule({
    whenChanged: "mydev/test",
    then: function() {
        log("mydev/test changed");
    }
});
</syntaxhighlight>
 
 
== Управление правилами == <!--T:43-->
 
<!--T:44-->
В wb-rules 2.0 также появляется возможность управлять выполнением правил. Теперь функция defineRule() возвращает идентификатор
созданного правила (аналогично setTimeout()/setInterval() ), который можно использовать позже для:
 
<!--T:45-->
* выключения/включения отработки правила;
* принудительного запуска тела правила.
 
<!--T:46-->
По умолчанию, все правила включены.
 
 
=== Пример === <!--T:47-->
 
<!--T:48-->
<syntaxhighlight lang="js">
var myRule = defineRule({
    whenChanged: "mydev/test",
    then: function() {
        log("mydev/test changed");
    }
});
 
<!--T:49-->
// ...
 
<!--T:50-->
disableRule(myRule); // отключить проверку и выполнение правила
enableRule(myRule); // разрешить выполнение правила
 
<!--T:51-->
runRule(myRule); // принудительно запустить тело правила (функцию then)
// на текущий момент не поддерживается передача аргументов в then
</syntaxhighlight>
 
 
== Постоянное хранилище данных == <!--T:52-->
 
<!--T:53-->
В wb-rules 2.0 добавлена поддержка постоянных хранилищ. По сути, это объекты, значения в которых будут сохраняться
даже при потере питания контроллера. Такие хранилища удобно использовать для хранения состояний или конфигурации.
 
<!--T:54-->
<syntaxhighlight lang="js">
var ps = new PersistentStorage("my-storage");
 
<!--T:55-->
ps.key = "Hello World";
log(ps.key);
</syntaxhighlight>
 
<!--T:56-->
По умолчанию, хранилища создаются локальными для данного файла сценария, с привязкой к имени файла. Таким образом, при создании хранилищ с
одинаковыми именами в разных файлах сценариев, создастся два разных хранилища (и сценарий не получит
доступа к "чужим" данным).
 
<!--T:57-->
Однако, есть возможность создавать глобальные хранилища. Для этого нужно добавить аргумент { global: true } в
вызов конструктора:
 
<!--T:58-->
<syntaxhighlight lang="js">
var ps = new PersistentStorage("my-storage", { global: true });
</syntaxhighlight>
 
<!--T:59-->
Если такое хранилище уже было создано когда-либо ранее, сценарий получит к нему доступ.
 
 
== Виртуальные устройства == <!--T:60-->
 
<!--T:61-->
В предыдущих версиях wb-rules значения контролов виртуальных устройств хранились только в MQTT retained, что не очень надёжно (в случае
потери питания данные могли быть легко утеряны). Начиная с версии 2.0, эти значения сохраняются также в специальное хранилище в постоянной
памяти и восстанавливаются при загрузке сценария.
 
<!--T:62-->
Если необходимо каждый раз при перезагрузке скрипта восстанавливать строго определённое значение (т.е. не восстанавливать предыдущее сохранённое),
можно добавить в описание контрола поле forceDefault:
 
<!--T:63-->
<syntaxhighlight lang="js">
defineVirtualDevice("vdev", {
    ...
    cells: {
        ...
        mycell: {
            type: "value",
            value: 10,
            forceDefault: true // при каждой загрузке сценария поле mycell будет получать значение 10
        }
    }
});
</syntaxhighlight>
 
<!--T:64-->
По умолчанию поле принимает значение false.
 
= Модули = <!--T:65-->
Начиная с версии 2.0, в движке правил wb-rules появилась поддержка подключаемых JS-модулей (похожая по поведению на
аналогичную в Node.js, но с некоторыми особенностями).
 
 
== Расположение == <!--T:66-->
 
<!--T:67-->
Поиск модулей происходит по следующим путям (в заданном порядке):
 
<!--T:68-->
* /etc/wb-rules-modules
* /usr/share/wb-rules-modules
 
<!--T:69-->
Таким образом, пользовательские модули удобно складывать в /etc/wb-rules-modules.
 
<!--T:70-->
Добавить свои пути можно редактированием /etc/default/wb-rules добавлением путей
к переменной WB_RULES_MODULES через разделитель (:):
 
<!--T:71-->
<syntaxhighlight lang="bash">
...
WB_RULES_MODULES="/etc/wb-rules-modules:/usr/share/wb-rules-modules"
...
</syntaxhighlight>
 
 
== Подключение модуля к сценарию == <!--T:72-->
 
<!--T:73-->
Подключение модуля происходит с помощью функции require(). Она возвращает объект, экспортированный
модулем (exports).
 
<!--T:74-->
<syntaxhighlight lang="js">
...
var myModule = require("myModule");
...
</syntaxhighlight>
 
<!--T:75-->
При этом движок правил будет искать файл myModule.js по очереди в директориях поиска (см. Расположение).
 
<!--T:76-->
Также допустим поиск файла модуля по поддиректориям в директориях поиска, тогда вызов будет выглядеть так:
 
 
<!--T:77-->
<syntaxhighlight lang="js">
...
var myModule = require("path/to/myModule");
...
</syntaxhighlight>
 
<!--T:78-->
После того, как файл будет найден, его содержимое будет выполнено, и из файла будет передан объект exports.
 
<!--T:79-->
'''Примечание 1:''' если модуль был подключен в одном сценарии несколько раз (несколько вызовов require("myModule")),
содержимое файла модуля будет выполнено только в первый раз, а при повторных вызовах будет возвращаться сохранённый
объект exports.
 
<!--T:80-->
'''Примечание 2:''' если модуль подключается в разных сценариях, для каждого сценария будет создан свой объект модуля
и заново выполнен весь код модуля. Если модулю требуется использовать данные, общие для всех файлов сценариев,
для хранения данных следует использовать объект module.static.
 
 
== Создание модуля == <!--T:81-->
 
<!--T:82-->
Для создания модуля достаточно создать файл с именем, соответсвующим имени модуля (с расширением .js) в директории
/etc/wb-rules-modules.
 
<!--T:83-->
В этом файле будут доступны все стандартные функции wb-rules, а также набор специальных объектов, с помощью
которого можно реализовать необходимый функционал модуля.
 
 
=== Объект exports === <!--T:84-->
 
<!--T:85-->
С помощью объекта exports можно передавать пользовательскому сценарию параметры и методы.
 
 
==== Пример ==== <!--T:86-->
 
<!--T:87-->
Файл модуля /etc/wb-rules-modules/myModule.js
<syntaxhighlight lang="js">
exports.hello = function(text) {
    log("Hello from module, {}", text);
};
 
<!--T:88-->
exports.answer = 42;
</syntaxhighlight>
 
<!--T:89-->
Файл сценария scenario.js
<syntaxhighlight lang="js">
var m = require("myModule");
m.hello("world"); // выведет в лог "Hello from module, world"
log("The answer is {}", m.answer); // выведет в лог "The answer is 42"
</syntaxhighlight>
 
<!--T:90-->
'''Будьте внимательны:''' объект exports можно '''только дополнять значениями''', но не переопределять.
Иначе значения экспортированы не будут!
 
<!--T:91-->
<syntaxhighlight lang="js">
exports = function(text) {
    log("Hello from module, {}", text);
};
 
<!--T:92-->
// Ожидание:
var m = require("my-module");
m("world"); // не работает
 
<!--T:93-->
// На практике m будет пустым объектом.
// Та же проблема произойдёт при использовании такой конструкции:
exports = {
    hello: function(text) {
        log("Hello from module, {}", world);
    },
    answer: 42
};
</syntaxhighlight>
 
 
=== Объект module === <!--T:94-->
 
<!--T:95-->
Объект module содержит параметры, относящиеся непосредственно к файлу модуля.
 
 
==== module.filename ==== <!--T:96-->
 
<!--T:97-->
Содержит полный путь до файла ''модуля''. Например, для модуля, сохранённого в /etc/wb-rules-modules/myModule.js:
 
<!--T:98-->
<syntaxhighlight lang="js">
log(module.filename); // выведет /etc/wb-rules-modules/myModule.js
</syntaxhighlight>
 
 
==== module.static ==== <!--T:99-->
 
<!--T:100-->
Объект, хранящий данные, общие для всех экземпляров данного модуля. Его следует использовать для тех данных,
которые должны быть доступны сразу во всех сценариях, использующих данный модуль.
 
<!--T:101-->
Файл /etc/wb-rules-modules/myModule.js
<syntaxhighlight lang="js">
exports.counter = function() {
    if (module.static.count === undefined) {
        module.static.count = 1;
    }
    log("Number of calls: {}", module.static.count);
    module.static.count++;
};
</syntaxhighlight>
 
<!--T:102-->
Файл сценария scenario1.js
<syntaxhighlight lang="js">
var m = require("myModule");
m.counter();
m.counter();
</syntaxhighlight>
 
<!--T:103-->
Файл сценария scenario2.js
<syntaxhighlight lang="js">
var m = require("myModule");
m.counter();
m.counter();
m.counter();
</syntaxhighlight>
 
<!--T:104-->
В результате работы двух скриптов в логе окажется 5 сообщений:
 
<!--T:105-->
<syntaxhighlight>
Number of calls: 1
Number of calls: 2
Number of calls: 3
Number of calls: 4
Number of calls: 5
</syntaxhighlight>
 
 
=== __filename === <!--T:106-->
 
<!--T:107-->
Переменная __filename берётся из глобального объекта сценария, к которому подключается модуль, и содержит имя файла
сценария.
 
<!--T:108-->
В случае, если модуль подключается в другом модуле, переменная __filename, тем не менее, будет содержать именно
имя файла сценария - вершины дерева зависимостей.
 
<!--T:109-->
Файл /etc/wb-rules-modules/myModule.js
<syntaxhighlight lang="js">
exports.hello = function() {
    log(__filename);
};
</syntaxhighlight>
 
<!--T:110-->
Файл сценария /etc/wb-rules/scenario1.js
<syntaxhighlight lang="js">
var m = require("myModule");
m.hello(); // выведет scenario1.js
</syntaxhighlight>
 
= Замеры производительности = <!--T:111-->
Cм. [[Special:MyLanguage/Движок_правил_wb-rules_2.0/Производительность|Замеры производительности]]
</translate>

Текущая версия на 13:37, 19 января 2022

Перенаправление на: