|
Метка: новое перенаправление |
(не показано 5 промежуточных версий этого же участника) |
Строка 1: |
Строка 1: |
| В обновлённом движке правил wb-rules присутствует ряд важных нововведений, касающихся логики написания сценариев.
| | #REDIRECT [[wb-rules]] |
| | |
| = Сценарии =
| |
| | |
| == Изоляция сценариев ==
| |
| | |
| Начиная с версии wb-rules 2.0, каждый файл сценария запускается в своём отдельном пространстве имён — '''контексте'''. Таким образом, каждый сценарий может определять
| |
| свои функции и глобальные переменные без риска изменить поведение других сценариев.
| |
| | |
| === Пример ===
| |
| | |
| В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет глобальные переменные и функции.
| |
| | |
| В предыдущих версиях wb-rules обращение к глобальной переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению. В версиях, начиная с 2.0, поведение строго определено и такое же, как будто сценарий единственный в системе.
| |
| | |
| В комментариях указан вывод команд log для ранних версий и для актуальной версии.
| |
| | |
| '''Сценарий 1 (rules1.js)'''
| |
| <syntaxhighlight lang="js">
| |
| test1 = 42;
| |
| | |
| 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>
| |
| | |
| '''Сценарий 2 (rules2.js)'''
| |
| <syntaxhighlight lang="js">
| |
| test1 = 84;
| |
| test2 = "Hello";
| |
| | |
| setTimeout(function myFuncTwo() {
| |
| log("myFuncTwo called");
| |
| log("test1: {}, test2: {}", test1, test2);
| |
| // раньше: test1: [либо 42, либо 84], test2: Hello
| |
| // теперь: test1: 84, test2: Hello
| |
| }, 1000);
| |
| </syntaxhighlight>
| |
| | |
| === Примечание ===
| |
| | |
| В предыдущих версиях wb-rules для изоляции правил рекомендовалось использовать замыкание, т.е.
| |
| оборачивание кода сценария в конструкцию:
| |
| <syntaxhighlight lang="js">
| |
| (function() {
| |
| // код сценария идёт здесь
| |
| })();
| |
| </syntaxhighlight>
| |
| | |
| Начиная с версии 2.0, в подобной конструкции нет необходимости. Тем не менее, старые сценарии, использующие эту конструкцию, продолжат работу без изменений в поведении.
| |
| | |
| === Обходные пути ===
| |
| Если в вашей системе использовалось общее глобальное пространство для хранения общих данных и функций, есть несколько способов реализации такого поведения:
| |
| | |
| ==== Использование модулей ====
| |
| Можно написать модуль для организации взаимодействия. У модулей есть статическое хранилище, общее для всех файлов, импортировавших модуль. (см. [[#module.static|module.static]])
| |
| | |
| ==== Постоянное хранилище ====
| |
| | |
| Для обмена данными также можно использовать глобальные постоянные хранилища (PersistentStorage).
| |
| | |
| '''Внимание:''' при использовании глобальных постоянных хранилищ может произойти совпадение имён, в этом случае возможно нарушение поведения, которое трудно обнаружить.
| |
| | |
| <syntaxhighlight lang="js">
| |
| var ps = new PersistentStorage("my-global-storage", {global: true});
| |
| | |
| /// ...
| |
| | |
| ps.myvar = "value"; // это значение доступно для всех пользователей хранилища с именем "my-global-storage"
| |
| </syntaxhighlight>
| |
| | |
| ==== Прототип глобального объекта ====
| |
| | |
| '''ВНИМАНИЕ:''' метод считается «грязным», т.к. все переменные и функции, опубликованные таким образом, становятся доступными всем сценариям в системе. Старайтесь избегать этого способа. За неопределённое поведение при использовании этого метода несёт ответственность сам программист.
| |
| | |
| Глобальные объекты всех сценариев имеют общий объект — ''прототип'', в котором определены стандартные функции wb-rules (такие, как <code>defineRule</code>, <code>setTimeout</code> и т.д.). Через него можно передавать переменные или функции в общую область видимости.
| |
| | |
| <syntaxhighlight lang="js">
| |
| global.__proto__.myVar = 42; // теперь myVar — общая переменная для всех сценариев
| |
| | |
| // из других сценариев к переменной можно обращаться так
| |
| log("shared myVar: {}", myVar);
| |
| | |
| // или вот так, что чуть более аккуратно, т.к. однозначно показывает, где определена переменная
| |
| log("shared myVar: {}", global.__proto__.myVar);
| |
| </syntaxhighlight>
| |
| | |
| Правило поиска переменной в первом случае будет выглядеть так:
| |
| | |
| # Проверяем, есть ли <code>myVar</code> среди локальных переменных (определённой как <code>var myVar = ...</code>).
| |
| # Если нет, проверяем, есть ли <code>myVar</code> в глобальном объекте (определённой как <code>myVar = ...</code>).
| |
| # Если нет, проверяем, есть ли <code>myVar</code> в ''прототипе'' глобального объекта (определённой как <code>global.__proto__.myVar</code>).
| |
| | |
| Поиск останавливается, как только переменная найдена.
| |
| | |
| Таким образом, первый способ обращения будет работать только в том случае, если <code>myVar</code> не определена в верхних областях видимости.
| |
| | |
| == Анонимные правила ==
| |
| | |
| Теперь правила можно объявлять анонимно (без задания специального имени). Это позволит уменьшить путаницу и неочевидное поведение системы при дублировании имён правил в одном скрипте.Уникальные имена для анонимных правил генерируются автоматически. Старый синтаксис (с явным заданием имени правила) продолжит работу без изменений.
| |
| | |
| '''ВНИМАНИЕ:''' начиная с версии 2.0, при объявлении правил с одинаковыми именами в одном файле теперь будет возвращаться ошибка.
| |
| | |
| === Пример ===
| |
| | |
| <syntaxhighlight lang="js">
| |
| defineRule({
| |
| whenChanged: "mydev/test",
| |
| then: function() {
| |
| log("mydev/test changed");
| |
| }
| |
| });
| |
| </syntaxhighlight>
| |
| | |
| == Управление правилами ==
| |
| | |
| В wb-rules 2.0 также появляется возможность управлять выполнением правил. Теперь функция <code>defineRule()</code> возвращает идентификатор созданного правила (аналогично <code>setTimeout()/setInterval()</code>), который можно использовать позже для:
| |
| * выключения/включения отработки правила;
| |
| * принудительного запуска тела правила.
| |
| | |
| По умолчанию, все правила включены.
| |
| | |
| === Пример ===
| |
| | |
| <syntaxhighlight lang="js">
| |
| var myRule = defineRule({
| |
| whenChanged: "mydev/test",
| |
| then: function() {
| |
| log("mydev/test changed");
| |
| }
| |
| });
| |
| | |
| // ...
| |
| | |
| disableRule(myRule); // отключить проверку и выполнение правила
| |
| enableRule(myRule); // разрешить выполнение правила
| |
| | |
| runRule(myRule); // принудительно запустить тело правила (функцию then)
| |
| // на текущий момент не поддерживается передача аргументов в then
| |
| </syntaxhighlight>
| |
| | |
| == Постоянное хранилище данных ==
| |
| | |
| В wb-rules 2.0 добавлена поддержка постоянных хранилищ. По сути, это объекты, значения в которых будут сохраняться
| |
| даже при потере питания контроллера. Такие хранилища удобно использовать для хранения состояний или конфигурации.
| |
| | |
| <syntaxhighlight lang="js">
| |
| var ps = new PersistentStorage("my-storage");
| |
| | |
| ps.key = "Hello World";
| |
| log(ps.key);
| |
| </syntaxhighlight>
| |
| | |
| По умолчанию, хранилища создаются локальными для данного файла сценария, с привязкой к имени файла. Таким образом, при создании хранилищ с одинаковыми именами в разных файлах сценариев, создастся два разных хранилища (и сценарий не получит доступа к «чужим» данным).
| |
| | |
| Однако, есть возможность создавать глобальные хранилища. Для этого нужно добавить аргумент { global: true } в вызов конструктора:
| |
| | |
| <syntaxhighlight lang="js">
| |
| var ps = new PersistentStorage("my-storage", { global: true });
| |
| </syntaxhighlight>
| |
| | |
| Если такое хранилище уже было создано когда-либо ранее, сценарий получит к нему доступ.
| |
| | |
| == Виртуальные устройства ==
| |
| | |
| В предыдущих версиях wb-rules значения контролов виртуальных устройств хранились только в MQTT retained, что не очень надёжно (в случае потери питания данные могли быть легко утеряны). Начиная с версии 2.0, эти значения сохраняются также в специальное хранилище в постоянной памяти и восстанавливаются при загрузке сценария.
| |
| | |
| Если необходимо каждый раз при перезагрузке скрипта восстанавливать строго определённое значение (т.е. не восстанавливать предыдущее сохранённое),
| |
| можно добавить в описание контрола поле <code>forceDefault</code>:
| |
| | |
| <syntaxhighlight lang="js">
| |
| defineVirtualDevice("vdev", {
| |
| ...
| |
| cells: {
| |
| ...
| |
| mycell: {
| |
| type: "value",
| |
| value: 10,
| |
| forceDefault: true // при каждой загрузке сценария поле mycell будет получать значение 10
| |
| }
| |
| }
| |
| });
| |
| </syntaxhighlight>
| |
| | |
| По умолчанию поле принимает значение <code>false</code>.
| |
| | |
| = Модули =
| |
| Начиная с версии 2.0, в движке правил wb-rules появилась поддержка подключаемых JS-модулей (похожая по поведению на аналогичную в Node.js, но с некоторыми особенностями).
| |
| | |
| == Расположение ==
| |
| | |
| Поиск модулей происходит по следующим путям (в заданном порядке):
| |
| | |
| * <code>/etc/wb-rules-modules</code>
| |
| * <code>/usr/share/wb-rules-modules</code>
| |
| | |
| Таким образом, пользовательские модули удобно складывать в <code>/etc/wb-rules-modules</code>.
| |
| | |
| Добавить свои пути можно редактированием <code>/etc/default/wb-rules</code> добавлением путей
| |
| к переменной WB_RULES_MODULES через разделитель <code>:</code>:
| |
| | |
| <syntaxhighlight lang="bash">
| |
| ...
| |
| WB_RULES_MODULES="/etc/wb-rules-modules:/usr/share/wb-rules-modules"
| |
| ...
| |
| </syntaxhighlight>
| |
| | |
| == Подключение модуля к сценарию ==
| |
| | |
| Подключение модуля происходит с помощью функции <code>require()</code>. Она возвращает объект, экспортированный
| |
| модулем (exports).
| |
| | |
| <syntaxhighlight lang="js">
| |
| ...
| |
| var myModule = require("myModule");
| |
| ...
| |
| </syntaxhighlight>
| |
| | |
| При этом движок правил будет искать файл <code>myModule.js</code> по очереди в директориях поиска (см. Расположение).
| |
| | |
| Также допустим поиск файла модуля по поддиректориям в директориях поиска, тогда вызов будет выглядеть так:
| |
| | |
| <syntaxhighlight lang="js">
| |
| ...
| |
| var myModule = require("path/to/myModule");
| |
| ...
| |
| </syntaxhighlight>
| |
| | |
| После того, как файл будет найден, его содержимое будет выполнено, и из файла будет передан объект exports.
| |
| | |
| '''Примечание 1:''' если модуль был подключен в одном сценарии несколько раз (несколько вызовов require("myModule")),
| |
| содержимое файла модуля будет выполнено только в первый раз, а при повторных вызовах будет возвращаться сохранённый
| |
| объект exports.
| |
| | |
| '''Примечание 2:''' если модуль подключается в разных сценариях, для каждого сценария будет создан свой объект модуля
| |
| и заново выполнен весь код модуля. Если модулю требуется использовать данные, общие для всех файлов сценариев,
| |
| для хранения данных следует использовать объект <code>module.static</code>.
| |
| | |
| == Создание модуля ==
| |
| | |
| Для создания модуля достаточно создать файл с именем, соответствующим имени модуля (с расширением .js) в директории <code>/etc/wb-rules-modules</code>.
| |
| | |
| В этом файле будут доступны все стандартные функции wb-rules, а также набор специальных объектов, с помощью
| |
| которого можно реализовать необходимый функционал модуля.
| |
| | |
| === Объект exports ===
| |
| | |
| С помощью объекта exports можно передавать пользовательскому сценарию параметры и методы.
| |
| | |
| ==== Пример ====
| |
| | |
| Файл модуля <code>/etc/wb-rules-modules/myModule.js</code>
| |
| <syntaxhighlight lang="js">
| |
| exports.hello = function(text) {
| |
| log("Hello from module, {}", text);
| |
| };
| |
| | |
| exports.answer = 42;
| |
| </syntaxhighlight>
| |
| | |
| Файл сценария 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>
| |
| | |
| '''Будьте внимательны:''' объект exports можно '''только дополнять значениями''', но не переопределять.
| |
| Иначе значения экспортированы не будут!
| |
| | |
| <syntaxhighlight lang="js">
| |
| exports = function(text) {
| |
| log("Hello from module, {}", text);
| |
| };
| |
| | |
| // Ожидание:
| |
| var m = require("my-module");
| |
| m("world"); // не работает
| |
| | |
| // На практике m будет пустым объектом.
| |
| // Та же проблема произойдёт при использовании такой конструкции:
| |
| exports = {
| |
| hello: function(text) {
| |
| log("Hello from module, {}", world);
| |
| },
| |
| answer: 42
| |
| };
| |
| </syntaxhighlight>
| |
| | |
| === Объект module ===
| |
| | |
| Объект module содержит параметры, относящиеся непосредственно к файлу модуля.
| |
| | |
| ==== module.filename ====
| |
| | |
| Содержит полный путь до файла ''модуля''. Например, для модуля, сохранённого в <code>/etc/wb-rules-modules/myModule.js</code>:
| |
| | |
| <syntaxhighlight lang="js">
| |
| log(module.filename); // выведет /etc/wb-rules-modules/myModule.js
| |
| </syntaxhighlight>
| |
| | |
| ==== module.static ====
| |
| | |
| Объект, хранящий данные, общие для всех экземпляров данного модуля. Его следует использовать для тех данных,
| |
| которые должны быть доступны сразу во всех сценариях, использующих данный модуль.
| |
| | |
| Файл <code>/etc/wb-rules-modules/myModule.js</code>
| |
| <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>
| |
| | |
| Файл сценария <code>scenario1.js</code>
| |
| <syntaxhighlight lang="js">
| |
| var m = require("myModule");
| |
| m.counter();
| |
| m.counter();
| |
| </syntaxhighlight>
| |
| | |
| Файл сценария <code>scenario2.js</code>
| |
| <syntaxhighlight lang="js">
| |
| var m = require("myModule");
| |
| m.counter();
| |
| m.counter();
| |
| m.counter();
| |
| </syntaxhighlight>
| |
| | |
| В результате работы двух скриптов в логе окажется 5 сообщений:
| |
| | |
| <syntaxhighlight>
| |
| Number of calls: 1
| |
| Number of calls: 2
| |
| Number of calls: 3
| |
| Number of calls: 4
| |
| Number of calls: 5
| |
| </syntaxhighlight>
| |
| | |
| === __filename ===
| |
| | |
| Переменная <code>__filename</code> берётся из глобального объекта сценария, к которому подключается модуль, и содержит имя файла
| |
| сценария.
| |
| | |
| В случае, если модуль подключается в другом модуле, переменная <code>__filename</code>, тем не менее, будет содержать именно
| |
| имя файла сценария — вершины дерева зависимостей.
| |
| | |
| Файл <code>/etc/wb-rules-modules/myModule.js</code>
| |
| <syntaxhighlight lang="js">
| |
| exports.hello = function() {
| |
| log(__filename);
| |
| };
| |
| </syntaxhighlight>
| |
| | |
| Файл сценария <code>/etc/wb-rules/scenario1.js</code>
| |
| <syntaxhighlight lang="js">
| |
| var m = require("myModule");
| |
| m.hello(); // выведет scenario1.js
| |
| </syntaxhighlight>
| |
| | |
| = Замеры производительности =
| |
| Cм. [[Special:MyLanguage/Движок_правил_wb-rules_2.0/Производительность|Замеры производительности]]
| |