|
Метка: новое перенаправление |
(не показано 7 промежуточных версий 3 участников) |
Строка 1: |
Строка 1: |
| {{DISPLAYTITLE: Написание скриптов для начинающих}}
| | #REDIRECT [[wb-rules]] |
| == Введение ==
| |
| Попытаемся применить скрипты контроллера WB (Wiren Board 6) для управления.
| |
| Текст ниже предназначен тем, кто не имеет опыта программирования, но хочет научиться.
| |
| | |
| == Используемое оборудование ==
| |
| * [[Wiren_Board_6|Контроллер]]
| |
| * [[WBIO-DI-WD-14_I/O_Module|Модуль WBIO-DI-WD-14]]
| |
| * [[WBIO-DO-R1G-16_I/O_Module|WBIO-DO-R1G-16]]
| |
| | |
| == Программирование ==
| |
| | |
| === Скрипт ===
| |
| Скриптом называется обыкновенный текстовый файл. Для того чтобы он выполнялся движком [[Движок_правил_wb-rules|wb-rules]] нужно выполнить три условия:
| |
| # Имя файла должно заканчиваться на '''.js'''
| |
| # Файл должен располагаться в каталоге '''/etc/wb-rules'''
| |
| # Файл должен заканчиваться пустой строкой. | |
| [[Веб-интерфейс_Wiren_Board|Веб-интерфейс]] контроллера имеет встроенный редактор скриптов, доступный из его главного меню по ссылке '''"Rules"'''
| |
| | |
| == Выполнение скриптов ==
| |
| Только что созданный скрипт после нажатия кнопки "Save" (и при запуске wb-rules) сразу же выполнится. То есть, будут созданы все переменные, правила, таймеры. И дальнейшее поведение скрипта будет определяться только событиями, как внешними (изменения состояния устройств, на которые "подписаны" правила) так и таймерами.
| |
| | |
| == Переменные ==
| |
| Где хранить значения, а также строки и все остальное, включая ссылки на объекты? Именно в переменных.
| |
| Можно представить каждую переменную как коробку. Подписанную коробку.
| |
| Подпись - это имя переменной, а содержимое коробки - это ее значение.
| |
| | |
| Кроме "простых" переменных еще используются массивы. Массив - это "стопка" значений, которые определяются одним именем переменной и "номером" в стопке.
| |
| ''Еще есть структуры (объекты), но о них - позже.''
| |
| | |
| <blockquote>'''Важно:''' JS, по умолчанию, при присвоении переменной одного типа значения другого типа - меняет тип переменной. Иногда это вызывает неожиданное поведение.</blockquote>
| |
| | |
| Типы переменных в JS:
| |
| * String: представляет строку
| |
| * Number: представляет числовое значение
| |
| * Boolean: представляет логическое значение true или false.
| |
| * undefined: указывает, что значение не установлено
| |
| * null: указывает на неопределенное значение
| |
| | |
| Ниже попробуем создать первый скрипт и поймем значение типов.
| |
| | |
| == Логи ==
| |
| Использование логов для отладки - бесценно. Иметь возможность в любом месте скрипта записать текущее значение переменной, состояние устройства очень помогает.
| |
| Логи в WB двух типов - с записью в файл (/var/log/mesages) и в mqtt топик.
| |
| Лог вызывается такой командой:
| |
| log.{debug,info,warning,error}(fmt, [arg1 [, ...]])
| |
| Научимся ими пользоваться, а заодно, и напишем первый скрипт.
| |
| В меню “Rules” нажимаем “New” и в поле для имени, вверху пишем “logs_and_vars_test.js”
| |
| И в поле скрипта вставляем:
| |
| <syntaxhighlight lang="ecmascript">
| |
| //logs_and_vars_test.js
| |
| //Просто комментарий. Начинается с двойного слеша "//"
| |
| var testvar1; //Переменная "testvar1" Ничего не присваиваем при объявлении(создании), "пустая"
| |
| log.info("Тип и значение переменной testvar1", typeof(testvar1), testvar1); // Тип (и значение) undefined
| |
| testvar1 = "yellow submarine"; // Присвоим строку
| |
| log.info("Тип и значение переменной testvar1", typeof(testvar1), testvar1);
| |
| testvar1 = 42; //Присвоим число
| |
| log.info("Тип и значение переменной testvar1", typeof(testvar1), testvar1);
| |
| testvar1 = false; //Булево (true/false)
| |
| log.info("Тип и значение переменной testvar1", typeof(testvar1), testvar1);
| |
| | |
| testvar2 = "red submarine"; //Сразу при объявлении присваиваем значение
| |
| log.info("Тип и значение переменной testvar2", typeof(testvar2), testvar2);
| |
| | |
| testvar1 = testvar2; //Тип testvar1 - булево. testvar2 - строка
| |
| log.info("Тип и значение переменной testvar1", typeof(testvar1), testvar1); // при присвоении тип поменялся.
| |
| | |
| </syntaxhighlight>
| |
| | |
| <blockquote>''Не забывайте '''пустую''' строку в конце скрипта.</blockquote>
| |
| | |
| <br>Прежде чем сохранять, давайте зададимся вопросом “как же логи читать?”
| |
| Два (даже три) способа.
| |
| * Самый простой: нажимаем на кнопку с символом гаечного ключа. Она находится в нижнем правом углу окна редактора скриптов.
| |
| [[Файл:Wb-jscript-Logs-1.png|200px|thumb|center|Кнопка для логов]]
| |
| Нажимаем - выдвигается консоль. Пока пустая, но после нажатия ''кнопки "Save"'' сюда выводятся сообщения.
| |
| [[Файл:Wb-jscript-Logs-2.png|400px|thumb|center|Панель лога]] | |
| | |
| * Чуть сложнее: открываем ssh сессию на контроллере и вводим
| |
| <syntaxhighlight lang="bash">
| |
| tail -f /var/log/messages |grep wb-rules
| |
| </syntaxhighlight>
| |
| Команда просматривает файл и при появлении в нем строчек с "'''wb-rules'''"
| |
| Нажмем опять ''кнопку "Save"'' - выводит их в '''stdout''' (на экран)
| |
| [[Файл:Wb-jscript-Logs-bash.png|400px|thumb|center|Логи в SSH сессии]]
| |
| <blockquote>
| |
| Чтобы кнопка "Save" стала вновь активной, надо что-нибудь изменить в окне редактора. Например, добавить пробел.
| |
| </blockquote>
| |
| Можно убедиться, что скрипт выполняется и даже совершает полезное действие - выводит сообщения.
| |
| | |
| === Как определить тип ===
| |
| Функция <syntaxhighlight lang="ecmascript">typeof</syntaxhighlight> принимает в качестве аргумента (то, что в скобках) '''переменную''' и возвращает нам ее '''тип'''
| |
| | |
| Если выполнить код:
| |
| <syntaxhighlight lang="ecmascript">
| |
| variable1 = "yellow submarine";
| |
| variable2 = typeof(variable1);
| |
| </syntaxhighlight>
| |
| | |
| То <syntaxhighlight lang="ecmascript">variable2</syntaxhighlight> будет иметь тип "'''string'''" и значение "'''string'''"
| |
| | |
| | |
| 2do: Дописать про форматирование вывода логов вида log("Инфо текст {} {} {}".format(var1, var2, var3));
| |
| | |
| == Условия ==
| |
| Очень часто нужно что-нибудь с чем-то сравнить. Например, температуру с желаемой (с ''уставкой'' на тайном языке наладчиков-проектировщиков).
| |
| Напишем простой пример, в котором сравним два значения.
| |
| <syntaxhighlight lang="ecmascript">
| |
| //if_then.js
| |
| //Просто комментарий. Начинается с двойного слеша "//"
| |
| var t_real =20; //Переменная в которой будет хранитьтся измеренная (реальная) температура
| |
| var t_ust = 22; //Переменная в которой хранится уставка (желаемая) температура
| |
| | |
| if (t_real > t_ust) { //В "()"скобках - условие.
| |
| log.info("Температура", t_real, "БОЛЬШЕ уставки", t_ust);
| |
| }
| |
| | |
| </syntaxhighlight>
| |
| | |
| <blockquote>''Не забывайте '''пустую''' строку в конце скрипта.</blockquote>
| |
| | |
| | |
| === Использование ===
| |
| Сама конструкция условий основана на простой вещи: Если то, что в круглых скобках после оператора "if" ''верно'' - код в "{}" выполнитя
| |
| Надо отвлечься на понятие - "верно" то есть '''истина''', '''true'''
| |
| Если мы говорим о условиях - сразу перечислим самые распростарненные:
| |
| * '''>''' - больше '''true''' когда ( 5 > 3 )
| |
| * '''<''' - меньше '''true''' когда ( 1 < 3 )
| |
| * '''>=''' - больше или равно '''true''' когда ( 5 >= 3) и когда ( 5 >= 5 )
| |
| * '''==''' - равно '''true''' когда ( 5 == 5) '''Внимание! про == ниже'''
| |
| * '''||''' - ИЛИ '''true''' когда ( true || true ), кода ( true || false ), хоть одно из условий true
| |
| * '''&&''' - И '''true''' когда ( true && true ), кода оба true
| |
| | |
| И про "равно" "'''=='''":
| |
| <syntaxhighlight lang="ecmascript">
| |
| //if_then_eq.js
| |
| var test1 = 2;
| |
| var test2 = 3;
| |
| | |
| if (test1 = test2) {
| |
| log.info("Выполняется! А почему?!");
| |
| }
| |
| | |
| log.info("test1", test1);
| |
| log.info("test2", test2);
| |
| </syntaxhighlight>
| |
| Все просто. Для того чтобы определить ''истинность'' условия компилятор '''выполняет''' выражение в скобках. Первой переменной можно присвоить вторую? Можно! Все, условие выполнено. Поэтому не надо использовать "=" в условиях. Только "==".
| |
| | |
| | |
| Что делать, когда надо выполнять разные действия в зависимости от условия? Оператор '''else'''синтаксис ниже:
| |
| <syntaxhighlight lang="ecmascript">
| |
| //if_then_else.js
| |
| var t_real = 18; //Переменная в которой будет хранитьтся измеренная (реальная) температура
| |
| var t_ust = 22; //Переменная в которой хранится уставка (желаемая) температура
| |
| | |
| if (t_real > t_ust) { //В "()"скобках - условие.
| |
| log.info("Температура", t_real, "БОЛЬШЕ уставки", t_ust);
| |
| }
| |
| else { //Если условие НЕ выполняется
| |
| log.info("Температура", t_real, "МЕНЬШЕ уставки", t_ust);
| |
| }
| |
| | |
| </syntaxhighlight>
| |
| | |
| === Отладка ===
| |
| Если условие не работает так как нам надо - то:
| |
| * Проверяем '''[[Wb-jscript#.D0.9A.D0.B0.D0.BA_.D0.BE.D0.BF.D1.80.D0.B5.D0.B4.D0.B5.D0.BB.D0.B8.D1.82.D1.8C_.D1.82.D0.B8.D0.BF |тип]]''' переменных перед условием
| |
| <syntaxhighlight lang="ecmascript">
| |
| //if_then_types.js
| |
| var test1 = 4;
| |
| var test2 = "aaa"; //Переменная тип string
| |
| log.info("перед условием");
| |
| if (test1 > test2) { //В "()"скобках - условие.
| |
| log.info("Больше");
| |
| }
| |
| </syntaxhighlight>
| |
| Сравнивать переменные разных типов - некорректно.
| |
| Выведем типы чтобы оценить возможность сравения:
| |
| <syntaxhighlight lang="ecmascript">
| |
| //if_then_types_1.js
| |
| var test1 = 4;
| |
| var test2 = "aaa"; //Переменная тип string
| |
| log.info("перед условием. Тип test1", typeof(test1), "Тип test2", typeof(test2));
| |
| if (typeof(test1) !== typeof(test2)) { //А вот типы сравнивать - можно.
| |
| log.info("Типы разные");
| |
| }
| |
| else {
| |
| log.info("Типы одинаковые");
| |
| }
| |
| | |
| if (test1 && test2) { //В "()"скобках - условие.
| |
| log.info("И - работает, потому что обе переменные содержат отличное от нуля");
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| <blockquote>"!" - это модификатор "НЕ" для логических условий. !true==false</blockquote>
| |
| | |
| == Оператор switch-case ==
| |
| Если нужно различить какой-то набор заранее известных значений то можно использовать не конструкцию условий "if - if - if -if ...", заменив ее более простой "switch-case".
| |
| Пример:
| |
| <syntaxhighlight lang="ecmascript">
| |
| //switch_case.js
| |
| var a = 4;
| |
| | |
| switch (a) {
| |
| case 3:
| |
| log.info("Три");
| |
| break;
| |
| case 4:
| |
| log.info("Четыре" );
| |
| break;
| |
| case 5:
| |
| log.info('Пять');
| |
| break;
| |
| case 6:
| |
| case 7:
| |
| case 8:
| |
| case 9:
| |
| log.info("от шести до девяти");
| |
| break;
| |
| default:
| |
| log.info( "Нет таких значений" );
| |
| }
| |
| </syntaxhighlight>
| |
| В отличии от "if" **каждый** оператор "case" проверяет переменную, указанную в операторе "switch" на **равенство** аргументу "case".
| |
| Если ни одно из условий "case" **не** истинно - то отрабатывают операторы "default"
| |
| То есть скрипт можно записать и так:
| |
| <syntaxhighlight lang="ecmascript">
| |
| //if.js
| |
| var a = 0;
| |
| | |
| if (a==3) {
| |
| log.info("Три");
| |
| }
| |
| if (a==4) {
| |
| log.info("Четыре" );
| |
| }
| |
| if (a==5) {
| |
| log.info('Пять');
| |
| }
| |
| if ((a==6)||(a==7)||(a==8)||(a==9)) {
| |
| log.info("от шести до девяти");
| |
| }
| |
| | |
| </syntaxhighlight>
| |
| Отличие в том что если ни одно из условий **не** выполнится - то не произойдет ничего.
| |
| | |
| == Управление оборудованием ==
| |
| "Управлять" - значит получать состояния (входы) и реагировать на них, изменяя состояния выходов.
| |
| Научиимся первому.
| |
| === Входы и правила ===
| |
| [https://wirenboard.com/wiki/WBIO-DI-WD-14_I/O_Module#.D0.9C.D0.BE.D0.BD.D1.82.D0.B0.D0.B6 Подключим модуль] [https://wirenboard.com/wiki/WBIO-DI-WD-14_I/O_Module WBIO-DI-WD-14] и [https://wirenboard.com/wiki/%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D0%B8_%D0%B2%D0%B2%D0%BE%D0%B4%D0%B0-%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4%D0%B0#.D0.90.D0.BA.D1.82.D0.B8.D0.B2.D0.B8.D1.80.D0.BE.D0.B2.D0.B0.D0.BD.D0.B8.D0.B5_.D0.B2_.D0.B2.D0.B5.D0.B1-.D0.B8.D0.BD.D1.82.D0.B5.D1.80.D1.84.D0.B5.D0.B9.D1.81.D0.B5_.D0.BA.D0.BE.D0.BD.D1.82.D1.80.D0.BE.D0.BB.D0.BB.D0.B5.D1.80.D0.B0 настроим] его.
| |
| В [https://wirenboard.com/wiki/%D0%92%D0%B5%D0%B1-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81_Wiren_Board#Devices_.28.D0.A3.D1.81.D1.82.D1.80.D0.BE.D0.B9.D1.81.D1.82.D0.B2.D0.B0.29 Devices] модуль отображается так: [[Файл:Wb-jscript-devices-WD-14.png|200px|thumb|left|Входы WD-14]]
| |
| Входы модуля имеют названия от '''EXT1_IN1''' до '''EXT1_IN14'''. "EXT1" - потому что модуль ''первый'' от контроллера. Если подключить еще один такой же его названия входов будут начинаться с "EXT2".
| |
| При замыкании входа (возьмем EXT1_IN14) на клемму iGnd состояние входа меняется:[[Файл:Wb-jscript-devices-WD-14-IN14-true.png|100px|thumb|left|Вход EXT1_IN14 активен]]
| |
| | |
| Для того чтобы использовать устройство (вход или выход) в скриптах мы должны знать как к ним обращаться. Как посмотреть имена проще всего?
| |
| Три пути:
| |
| * Открыть в веб-интерфейсе '''MQTTChannels''' [[Файл:Wb-jscript-MQTTChannels.png|200px|thumb|left|Каналы MQTT]]
| |
| * Открыть в веб-интерфейсе '''Devices''' и щелкнуть мышью на имени:[[Файл:Wb-jscript_Devices_name.png|200px|thumb|left| Всплывающее имя в Devices]] И имя уже будет скопировано в буфер обмена.
| |
| * Выполнить в консоли команду <syntaxhighlight lang="bash">mosquitto_sub -v -t /#</syntaxhighlight>[https://wirenboard.com/wiki/MQTT Про MQTT]
| |
| | |
| Напомню, что в контроллере используется '''событийная''' модель, то есть действовать, реагировать будет на '''изменения''' состояния.
| |
| Для демонстрации создаем новый скрипт
| |
| | |
| <syntaxhighlight lang="ecmascript">
| |
| //wd-14_in_14.js
| |
| var inputName = "wb-gpio/EXT1_IN14"; //Сохраним в переменной имя входа.
| |
| | |
| defineRule("wd-14_in_14", { //название правила
| |
| whenChanged: inputName, //при изменении указанного значения
| |
| then: function (newValue, devName, cellName) { //выполняй следующие действия
| |
| log.info("newValue", newValue, "devName", devName, "cellName", cellName); //Это лог.
| |
| }
| |
| });
| |
| </syntaxhighlight>
| |
| | |
| | |
| И сохраним его. После сохранения ничего не происходит. Почему?
| |
| Как и писал выше - ключевое слово '''"событие"'''. Мы [https://wirenboard.com/wiki/MQTT#.D0.9F.D1.80.D0.B8.D0.BC.D0.B5.D1.80_.D0.BF.D0.BE.D0.B4.D0.BF.D0.B8.D1.81.D0.BA.D0.B8 "подписались"] на нужный нам топик MQTT и теперь правило сработает при его изменении.
| |
| Замкнем-разомкнем клемму '''"14"''' модуля [https://wirenboard.com/wiki/WBIO-DI-WD-14_I/O_Module WD-14] на его клемму '''"iGnd"''':
| |
| [[Файл:Wb-jscript_input_rule_1.png|350px|thumb|left|Правило сработало]]
| |
| | |
| То есть конструкция (синтаксис) правила такова:
| |
| <syntaxhighlight lang="ecmascript">
| |
| defineRule("wd-14_in_14", { //название (то, что в кавычках). Можно использовать переменную. Правил с одинаковыми названиями быть не должно.
| |
| whenChanged: "wb-gpio/EXT1_IN14", //Имя топика, на который подписываемся. Можно "строкой", можно переменной
| |
| then: function (newValue, devName, cellName) { //Как и в конструкции if-then. Но в качестве выполняемых операторов - функция.
| |
| log.info("newValue", newValue, "devName", devName, "cellName", cellName); //Это лог. Тут мы видим что в функцию передается как НОВОЕ значение топика так и имя устройстви с именем входа. Зачем, если и так знаем на что была подписка? Об этом дальше.
| |
| }
| |
| });
| |
| </syntaxhighlight>
| |
| | |
| ===Циклы===
| |
| Как выполнить что-нибудь несколько раз? Для этого существует конструкция "цикл".
| |
| В JS описывается так:
| |
| '''for ([Счетчик]; [условие]; [выражение]){Операторы}'''
| |
| <blockquote>"Операторы - это часть кода, которая будет выполняться несколько раз, от настроек цикла"</blockquote>
| |
| * '''Счетчик''' - выполняется один раз, обычно используют для ''обьявления переменной''
| |
| * '''условие''' - выполняется каждый цикл, если условие ''верно'' (''true'') то цикл повторяется.
| |
| * '''выражение''' - выполняется каждый цикл, после выполнения операторов и до проверки '''условие'''.
| |
| | |
| Попробуем?
| |
| <syntaxhighlight lang="ecmascript">
| |
| //for.js
| |
| for (i = 0; i<5; i++){
| |
| log.info("Переменная-счетчик i=", i);
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| В окне лога видно:
| |
| <pre>
| |
| 2020-09-17 12:54:36Переменная-счетчик i= 0
| |
| 2020-09-17 12:54:36Переменная-счетчик i= 1
| |
| 2020-09-17 12:54:36Переменная-счетчик i= 2
| |
| 2020-09-17 12:54:36Переменная-счетчик i= 3
| |
| 2020-09-17 12:54:36Переменная-счетчик i= 4
| |
| </pre>
| |
| | |
| Самое интересное, что ни один из параметров цикла не является обязательным.
| |
| Пример:
| |
| <syntaxhighlight lang="ecmascript">
| |
| //for_1.js
| |
| var i = 0
| |
| for (; ; ){
| |
| log.info("Переменная-счетчик i=", i);
| |
| i++;
| |
| if (i > 5) {break}; //
| |
| }
| |
| </syntaxhighlight>
| |
| | |
| <blockquote>Главное не допускать "бесконечных" циклов</blockquote>
| |
| | |
| ===Функции===
| |
| | |
| ===Выходы===
| |
| | |
| == Таймеры ==
| |