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

Материал из Wiren Board
(Подготовка страницы к переводу)
Строка 1: Строка 1:
<languages/>
<translate>
В обновлённом движке правил wb-rules присутствует ряд важных нововведений, касающихся логики написания сценариев.
В обновлённом движке правил wb-rules присутствует ряд важных нововведений, касающихся логики написания сценариев.


= Сценарии =
= Сценарии =


== Изоляция сценариев ==
== Изоляция сценариев ==
Строка 7: Строка 10:
Начиная с версии wb-rules 2.0, каждый файл сценария запускается в своём отдельном пространстве имён - '''контексте'''. Таким образом, каждый сценарий может определять
Начиная с версии wb-rules 2.0, каждый файл сценария запускается в своём отдельном пространстве имён - '''контексте'''. Таким образом, каждый сценарий может определять
свои функции и глобальные переменные без риска изменить поведение других сценариев.
свои функции и глобальные переменные без риска изменить поведение других сценариев.


=== Пример ===
=== Пример ===
В качестве примера приведём два сценария, одновременно запускаемых в движке правил.
В качестве примера приведём два сценария, одновременно запускаемых в движке правил.
Каждый сценарий определяет глобальные переменные и функции.
Каждый сценарий определяет глобальные переменные и функции.
Строка 43: Строка 48:
}, 1000);
}, 1000);
</syntaxhighlight>
</syntaxhighlight>


=== Примечание ===
=== Примечание ===
Строка 56: Строка 62:
Начиная с версии 2.0, в подобной конструкции нет необходимости. Тем не менее, старые сценарии,
Начиная с версии 2.0, в подобной конструкции нет необходимости. Тем не менее, старые сценарии,
использующие эту конструкцию, продолжат работу без изменений в поведении.
использующие эту конструкцию, продолжат работу без изменений в поведении.


=== Обходные пути ===
=== Обходные пути ===
Строка 61: Строка 68:
Если в вашей системе использовалось общее глобальное пространство для хранения общих данных и функций,
Если в вашей системе использовалось общее глобальное пространство для хранения общих данных и функций,
есть несколько способов реализации такого поведения:
есть несколько способов реализации такого поведения:


==== Использование модулей ====
==== Использование модулей ====


Можно написать модуль для организации взаимодействия. У модулей есть статическое хранилище,
Можно написать модуль для организации взаимодействия. У модулей есть статическое хранилище,
общее для всех файлов, импортировавших модуль. (см. [[#Модули|Модули]])
общее для всех файлов, импортировавших модуль. (см. [[Special:MyLanguage/#модули|Модули]])
 


==== Постоянное хранилище ====
==== Постоянное хранилище ====
Строка 81: Строка 90:
ps.myvar = "value"; // это значение доступно для всех пользователей хранилища с именем "my-global-storage"
ps.myvar = "value"; // это значение доступно для всех пользователей хранилища с именем "my-global-storage"
</syntaxhighlight>
</syntaxhighlight>


==== Прототип глобального объекта ====
==== Прототип глобального объекта ====
Строка 111: Строка 121:


Таким образом, первый способ обращения будет работать только в том случае, если myVar не определена в верхних областях видимости.
Таким образом, первый способ обращения будет работать только в том случае, если myVar не определена в верхних областях видимости.


== Анонимные правила ==
== Анонимные правила ==
Строка 123: Строка 134:
'''ВНИМАНИЕ:''' начиная с версии 2.0, при объявлении правил с одинаковыми
'''ВНИМАНИЕ:''' начиная с версии 2.0, при объявлении правил с одинаковыми
именами в одном файле теперь будет возвращаться ошибка.
именами в одном файле теперь будет возвращаться ошибка.


=== Пример ===
=== Пример ===
Строка 134: Строка 146:
});
});
</syntaxhighlight>
</syntaxhighlight>


== Управление правилами ==
== Управление правилами ==
Строка 144: Строка 157:


По умолчанию, все правила включены.
По умолчанию, все правила включены.


=== Пример ===
=== Пример ===
Строка 163: Строка 177:
// на текущий момент не поддерживается передача аргументов в then
// на текущий момент не поддерживается передача аргументов в then
</syntaxhighlight>
</syntaxhighlight>


== Постоянное хранилище данных ==
== Постоянное хранилище данных ==
Строка 188: Строка 203:


Если такое хранилище уже было создано когда-либо ранее, сценарий получит к нему доступ.
Если такое хранилище уже было создано когда-либо ранее, сценарий получит к нему доступ.


== Виртуальные устройства ==
== Виртуальные устройства ==
Строка 217: Строка 233:
Начиная с версии 2.0, в движке правил wb-rules появилась поддержка подключаемых JS-модулей (похожая по поведению на
Начиная с версии 2.0, в движке правил wb-rules появилась поддержка подключаемых JS-модулей (похожая по поведению на
аналогичную в Node.js, но с некоторыми особенностями).
аналогичную в Node.js, но с некоторыми особенностями).


== Расположение ==
== Расположение ==
Поиск модулей происходит по следующим путям (в заданном порядке):
Поиск модулей происходит по следующим путям (в заданном порядке):


Строка 234: Строка 252:
...
...
</syntaxhighlight>
</syntaxhighlight>


== Подключение модуля к сценарию ==
== Подключение модуля к сценарию ==
Строка 266: Строка 285:
и заново выполнен весь код модуля. Если модулю требуется использовать данные, общие для всех файлов сценариев,
и заново выполнен весь код модуля. Если модулю требуется использовать данные, общие для всех файлов сценариев,
для хранения данных следует использовать объект module.static.
для хранения данных следует использовать объект module.static.


== Создание модуля ==
== Создание модуля ==
Строка 274: Строка 294:
В этом файле будут доступны все стандартные функции wb-rules, а также набор специальных объектов, с помощью  
В этом файле будут доступны все стандартные функции wb-rules, а также набор специальных объектов, с помощью  
которого можно реализовать необходимый функционал модуля.
которого можно реализовать необходимый функционал модуля.


=== Объект exports ===
=== Объект exports ===


С помощью объекта exports можно передавать пользовательскому сценарию параметры и методы.
С помощью объекта exports можно передавать пользовательскому сценарию параметры и методы.


==== Пример ====
==== Пример ====
Строка 318: Строка 340:
};
};
</syntaxhighlight>
</syntaxhighlight>


=== Объект module ===
=== Объект module ===


Объект module содержит параметры, относящиеся непосредственно к файлу модуля.
Объект module содержит параметры, относящиеся непосредственно к файлу модуля.


==== module.filename ====
==== module.filename ====
Содержит полный путь до файла ''модуля''. Например, для модуля, сохранённого в /etc/wb-rules-modules/myModule.js:
Содержит полный путь до файла ''модуля''. Например, для модуля, сохранённого в /etc/wb-rules-modules/myModule.js:


Строка 329: Строка 354:
log(module.filename); // выведет /etc/wb-rules-modules/myModule.js
log(module.filename); // выведет /etc/wb-rules-modules/myModule.js
</syntaxhighlight>
</syntaxhighlight>


==== module.static ====
==== module.static ====
Объект, хранящий данные, общие для всех экземпляров данного модуля. Его следует использовать для тех данных,
Объект, хранящий данные, общие для всех экземпляров данного модуля. Его следует использовать для тех данных,
которые должны быть доступны сразу во всех сценариях, использующих данный модуль.
которые должны быть доступны сразу во всех сценариях, использующих данный модуль.
Строка 369: Строка 396:
Number of calls: 5
Number of calls: 5
</syntaxhighlight>
</syntaxhighlight>


=== __filename ===
=== __filename ===
Строка 392: Строка 420:


= Замеры производительности =
= Замеры производительности =
Cм. [[Движок_правил_wb-rules_2.0/Производительность|Замеры производительности]]
Cм. [[Special:MyLanguage/Движок_правил_wb-rules_2.0/Производительность|Замеры производительности]]
</translate>

Версия 19:15, 3 июня 2019

В обновлённом движке правил wb-rules присутствует ряд важных нововведений, касающихся логики написания сценариев.

Сценарии

Изоляция сценариев

Начиная с версии wb-rules 2.0, каждый файл сценария запускается в своём отдельном пространстве имён - контексте. Таким образом, каждый сценарий может определять свои функции и глобальные переменные без риска изменить поведение других сценариев.


Пример

В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет глобальные переменные и функции.

В предыдущих версиях wb-rules обращение к глобальной переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению. В версиях, начиная с 2.0, поведение строго определено и такое же, как будто сценарий единственный в системе.

В комментариях указан вывод команд log для ранних версий и для актуальной версии.

Сценарий 1 (rules1.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);

Сценарий 2 (rules2.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);


Примечание

В предыдущих версиях wb-rules для изоляции правил рекомендовалось использовать замыкание, т.е. оборачивание кода сценария в конструкцию:

(function() {
    // код сценария идёт здесь
})();

Начиная с версии 2.0, в подобной конструкции нет необходимости. Тем не менее, старые сценарии, использующие эту конструкцию, продолжат работу без изменений в поведении.


Обходные пути

Если в вашей системе использовалось общее глобальное пространство для хранения общих данных и функций, есть несколько способов реализации такого поведения:


Использование модулей

Можно написать модуль для организации взаимодействия. У модулей есть статическое хранилище, общее для всех файлов, импортировавших модуль. (см. Модули)


Постоянное хранилище

Для обмена данными также можно использовать глобальные постоянные хранилища (PersistentStorage).

Внимание: при использовании глобальных постоянных хранилищ может произойти совпадение имён, в этом случае возможно труднообнаруживаемое нарушение поведения.

var ps = new PersistentStorage("my-global-storage", {global: true});

/// ...

ps.myvar = "value"; // это значение доступно для всех пользователей хранилища с именем "my-global-storage"


Прототип глобального объекта

ВНИМАНИЕ: метод считается "грязным", т.к. все переменные и функции, опубликованные таким образом, становятся доступными всем сценариям в системе. Старайтесь избегать этого способа. За неопределённое поведение при использовании этого метода несёт ответственность сам программист.

Глобальные объекты всех сценариев имеют общий объект-прототип, в котором определены стандартные функции wb-rules (такие, как defineRule, setTimeout и т.д.). Через него можно передавать переменные или функции в общую область видимости.

global.__proto__.myVar = 42; // теперь myVar - общая переменная для всех сценариев

// из других сценариев к переменной можно обращаться так
log("shared myVar: {}", myVar);

// или вот так, что чуть более аккуратно, т.к. однозначно показывает, где определена переменная
log("shared myVar: {}", global.__proto__.myVar);

Правило поиска переменной в первом случае будет выглядеть так:

  1. Проверяем, есть ли myVar среди локальных переменных (определённой как var myVar = ...).
  2. Если нет, проверяем, есть ли myVar в глобальном объекте (определённой как myVar = ...).
  3. Если нет, проверяем, есть ли myVar в прототипе глобального объекта (определённой как global.__proto__.myVar).

Поиск останавливается, как только переменная найдена.

Таким образом, первый способ обращения будет работать только в том случае, если myVar не определена в верхних областях видимости.


Анонимные правила

Теперь правила можно объявлять анонимно (без задания специального имени). Это позволит уменьшить путаницу и неочевидное поведение системы при дублировании имён правил в одном скрипте.

Уникальные имена для анонимных правил генерируются автоматически.

Старый синтаксис (с явным заданием имени правила) продолжит работу без изменений.

ВНИМАНИЕ: начиная с версии 2.0, при объявлении правил с одинаковыми именами в одном файле теперь будет возвращаться ошибка.


Пример

defineRule({
    whenChanged: "mydev/test",
    then: function() {
        log("mydev/test changed");
    }
});


Управление правилами

В wb-rules 2.0 также появляется возможность управлять выполнением правил. Теперь функция defineRule() возвращает идентификатор созданного правила (аналогично setTimeout()/setInterval() ), который можно использовать позже для:

  • выключения/включения отработки правила;
  • принудительного запуска тела правила.

По умолчанию, все правила включены.


Пример

var myRule = defineRule({
    whenChanged: "mydev/test",
    then: function() {
        log("mydev/test changed");
    }
});

// ...

disableRule(myRule); // отключить проверку и выполнение правила
enableRule(myRule); // разрешить выполнение правила

runRule(myRule); // принудительно запустить тело правила (функцию then)
// на текущий момент не поддерживается передача аргументов в then


Постоянное хранилище данных

В wb-rules 2.0 добавлена поддержка постоянных хранилищ. По сути, это объекты, значения в которых будут сохраняться даже при потере питания контроллера. Такие хранилища удобно использовать для хранения состояний или конфигурации.

var ps = new PersistentStorage("my-storage");

ps.key = "Hello World";
log(ps.key);

По умолчанию, хранилища создаются локальными для данного файла сценария, с привязкой к имени файла. Таким образом, при создании хранилищ с одинаковыми именами в разных файлах сценариев, создастся два разных хранилища (и сценарий не получит доступа к "чужим" данным).

Однако, есть возможность создавать глобальные хранилища. Для этого нужно добавить аргумент { global: true } в вызов конструктора:

var ps = new PersistentStorage("my-storage", { global: true });

Если такое хранилище уже было создано когда-либо ранее, сценарий получит к нему доступ.


Виртуальные устройства

В предыдущих версиях wb-rules значения контролов виртуальных устройств хранились только в MQTT retained, что не очень надёжно (в случае потери питания данные могли быть легко утеряны). Начиная с версии 2.0, эти значения сохраняются также в специальное хранилище в постоянной памяти и восстанавливаются при загрузке сценария.

Если необходимо каждый раз при перезагрузке скрипта восстанавливать строго определённое значение (т.е. не восстанавливать предыдущее сохранённое), можно добавить в описание контрола поле forceDefault:

defineVirtualDevice("vdev", {
    ...
    cells: {
        ...
        mycell: {
            type: "value",
            value: "10",
            forceDefault: true // при каждой загрузке сценария поле mycell будет получать значение 10
        }
    }
});

По умолчанию поле принимает значение false.

Модули

Начиная с версии 2.0, в движке правил wb-rules появилась поддержка подключаемых JS-модулей (похожая по поведению на аналогичную в Node.js, но с некоторыми особенностями).


Расположение

Поиск модулей происходит по следующим путям (в заданном порядке):

  • /etc/wb-rules-modules
  • /usr/share/wb-rules-modules

Таким образом, пользовательские модули удобно складывать в /etc/wb-rules-modules.

Добавить свои пути можно редактированием /etc/default/wb-rules добавлением путей к переменной WB_RULES_MODULES через разделитель (:):

...
WB_RULES_MODULES="/etc/wb-rules-modules:/usr/share/wb-rules-modules"
...


Подключение модуля к сценарию

Подключение модуля происходит с помощью функции require(). Она возвращает объект, экспортированный модулем (exports).

...
var myModule = require("myModule");
...

При этом движок правил будет искать файл myModule.js по очереди в директориях поиска (см. Расположение).

Также допустим поиск файла модуля по поддиректориям в директориях поиска, тогда вызов будет выглядеть так:


...
var myModule = require("path/to/myModule");
...

После того, как файл будет найден, его содержимое будет выполнено, и из файла будет передан объект exports.

Примечание 1: если модуль был подключен в одном сценарии несколько раз (несколько вызовов require("myModule")), содержимое файла модуля будет выполнено только в первый раз, а при повторных вызовах будет возвращаться сохранённый объект exports.

Примечание 2: если модуль подключается в разных сценариях, для каждого сценария будет создан свой объект модуля и заново выполнен весь код модуля. Если модулю требуется использовать данные, общие для всех файлов сценариев, для хранения данных следует использовать объект module.static.


Создание модуля

Для создания модуля достаточно создать файл с именем, соответсвующим имени модуля (с расширением .js) в директории /etc/wb-rules-modules.

В этом файле будут доступны все стандартные функции wb-rules, а также набор специальных объектов, с помощью которого можно реализовать необходимый функционал модуля.


Объект exports

С помощью объекта exports можно передавать пользовательскому сценарию параметры и методы.


Пример

Файл модуля /etc/wb-rules-modules/myModule.js

exports.hello = function(text) {
    log("Hello from module, {}", text);
};

exports.answer = 42;

Файл сценария scenario.js

var m = require("myModule");
m.hello("world"); // выведет в лог "Hello from module, world"
log("The answer is {}", m.answer); // выведет в лог "The answer is 42"

Будьте внимательны: объект exports можно только дополнять значениями, но не переопределять. Иначе значения экспортированы не будут!

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
};


Объект module

Объект module содержит параметры, относящиеся непосредственно к файлу модуля.


module.filename

Содержит полный путь до файла модуля. Например, для модуля, сохранённого в /etc/wb-rules-modules/myModule.js:

log(module.filename); // выведет /etc/wb-rules-modules/myModule.js


module.static

Объект, хранящий данные, общие для всех экземпляров данного модуля. Его следует использовать для тех данных, которые должны быть доступны сразу во всех сценариях, использующих данный модуль.

Файл /etc/wb-rules-modules/myModule.js

exports.counter = function() {
    if (module.static.count === undefined) {
        module.static.count = 1;
    }
    log("Number of calls: {}", module.static.count);
    module.static.count++;
};

Файл сценария scenario1.js

var m = require("myModule");
m.counter();
m.counter();

Файл сценария scenario2.js

var m = require("myModule");
m.counter();
m.counter();
m.counter();

В результате работы двух скриптов в логе окажется 5 сообщений:

Number of calls: 1
Number of calls: 2
Number of calls: 3
Number of calls: 4
Number of calls: 5


__filename

Переменная __filename берётся из глобального объекта сценария, к которому подключается модуль, и содержит имя файла сценария.

В случае, если модуль подключается в другом модуле, переменная __filename, тем не менее, будет содержать именно имя файла сценария - вершины дерева зависимостей.

Файл /etc/wb-rules-modules/myModule.js

exports.hello = function() {
    log(__filename);
};

Файл сценария /etc/wb-rules/scenario1.js

var m = require("myModule");
m.hello(); // выведет scenario1.js

Замеры производительности

Cм. Замеры производительности