Написание скриптов для начинающих

Материал из Wiren Board

Введение

Попытаемся применить скрипты контроллера WB (Wiren Board 6) для управления. Текст ниже предназначен тем, кто не имеет опыта программирования, но хочет научиться.

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

Программирование

Скрипт

Скриптом называется обыкновенный текстовый файл. Для того чтобы он выполнялся движком wb-rules нужно соблюсти условия:

  1. Имя файла должно заканчиваться на .js
  2. Файл должен располагаться в каталоге /etc/wb-rules

Веб-интерфейс контроллера имеет встроенный редактор скриптов, доступный из его главного меню по ссылке "Rules"

Выполнение скриптов

Только что созданный скрипт после нажатия кнопки "Save" (и при запуске wb-rules) сразу же выполнится. То есть, будут созданы все переменные, правила, таймеры. И дальнейшее поведение скрипта будет определяться только событиями, как внешними (изменения состояния устройств, на которые "подписаны" правила) так и таймерами.

Переменные

Где хранить значения, а также строки и все остальное, включая ссылки на объекты? Именно в переменных. Можно представить каждую переменную как коробку. Подписанную коробку. Подпись - это имя переменной, а содержимое коробки - это ее значение.

Кроме "простых" переменных еще используются массивы. Массив - это "стопка" значений, которые определяются одним именем переменной и "номером" в стопке. Еще есть структуры (объекты), но о них - позже.

Важно: JS, по умолчанию, при присвоении переменной одного типа значения другого типа - меняет тип переменной. Иногда это вызывает неожиданное поведение.

Типы переменных в JS:

  • String: представляет строку
  • Number: представляет числовое значение
  • Boolean: представляет логическое значение true или false.
  • undefined: указывает, что значение не установлено
  • null: указывает на неопределенное значение

Ниже попробуем создать первый скрипт и поймем значение типов.

Логи

Использование логов для отладки - бесценно. Иметь возможность в любом месте скрипта записать текущее значение переменной, состояние устройства очень помогает. Логи в WB двух типов - с записью в journald и в mqtt топик. Лог вызывается такой командой:

log.{debug,info,warning,error}(fmt, [arg1 [, ...]])

Научимся ими пользоваться, а заодно, и напишем первый скрипт. В меню “Rules” нажимаем “New” и в поле для имени, вверху пишем “logs_and_vars_test.js” И в поле скрипта вставляем:

//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);  // при присвоении тип поменялся.

Не забывайте пустую строку в конце скрипта.


Прежде чем сохранять, давайте зададимся вопросом “как же логи читать?” Два (даже три) способа.

  • Самый простой: нажимаем на кнопку с символом гаечного ключа. Она находится в нижнем правом углу окна редактора скриптов.
Кнопка для логов
Нажимаем - выдвигается консоль. Пока пустая, но после нажатия кнопки "Save" сюда выводятся сообщения.
Панель лога
  • Чуть сложнее: открываем ssh сессию на контроллере и вводим
journalctl -u wb-rules -f

Команда просматривает журнал и при появлении в нем сообщений от "wb-rules" Нажмем опять кнопку "Save" - выводит их в stdout (на экран)

Логи в SSH сессии

Чтобы кнопка "Save" стала вновь активной, надо что-нибудь изменить в окне редактора. Например, добавить пробел.

Можно убедиться, что скрипт выполняется и даже совершает полезное действие - выводит сообщения.

Как определить тип

Функция

typeof

принимает в качестве аргумента (то, что в скобках) переменную и возвращает нам ее тип

Если выполнить код:

variable1 = "yellow submarine";
variable2 = typeof(variable1);

То

variable2

будет иметь тип "string" и значение "string"


2do: Дописать про форматирование вывода логов вида log("Инфо текст {} {} {}".format(var1, var2, var3));

Условия

Очень часто нужно что-нибудь с чем-то сравнить. Например, температуру с желаемой (с уставкой на тайном языке наладчиков-проектировщиков). Напишем простой пример, в котором сравним два значения.

//if_then.js 
//Просто комментарий. Начинается с двойного слеша "//"
var t_real =20; //Переменная в которой будет хранитьтся измеренная (реальная) температура
var t_ust = 22; //Переменная в которой хранится уставка (желаемая) температура

if (t_real > t_ust) { //В "()"скобках - условие.
  log.info("Температура", t_real, "БОЛЬШЕ уставки", t_ust); 
}

Не забывайте пустую строку в конце скрипта.


Использование

Сама конструкция условий основана на простой вещи: Если то, что в круглых скобках после оператора "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

И про "равно" "==":

//if_then_eq.js 
var test1 = 2; 
var test2 = 3; 

if (test1 = test2) {
  log.info("Выполняется! А почему?!"); 
}

log.info("test1", test1); 
log.info("test2", test2);

Все просто. Для того чтобы определить истинность условия компилятор выполняет выражение в скобках. Первой переменной можно присвоить вторую? Можно! Все, условие выполнено. Поэтому не надо использовать "=" в условиях. Только "==".


Что делать, когда надо выполнять разные действия в зависимости от условия? Оператор elseсинтаксис ниже:

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

Отладка

Если условие не работает так как нам надо - то:

  • Проверяем тип переменных перед условием
//if_then_types.js
 var test1 = 4; 
var test2 = "aaa"; //Переменная тип string
log.info("перед условием"); 
if (test1 > test2) { //В "()"скобках - условие.
  log.info("Больше"); 
}

Сравнивать переменные разных типов - некорректно. Выведем типы чтобы оценить возможность сравения:

//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("И - работает, потому что обе переменные содержат отличное от нуля"); 
}

"!" - это модификатор "НЕ" для логических условий. !true==false

Оператор switch-case

Если нужно различить какой-то набор заранее известных значений то можно использовать не конструкцию условий "if - if - if -if ...", заменив ее более простой "switch-case". Пример:

//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( "Нет таких значений" );
}

В отличии от "if" **каждый** оператор "case" проверяет переменную, указанную в операторе "switch" на **равенство** аргументу "case". Если ни одно из условий "case" **не** истинно - то отрабатывают операторы "default" То есть скрипт можно записать и так:

//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("от шести до девяти");
}

Отличие в том что если ни одно из условий **не** выполнится - то не произойдет ничего.

Управление оборудованием

"Управлять" - значит получать состояния (входы) и реагировать на них, изменяя состояния выходов. Научиимся первому.

Входы и правила

Подключим модуль WBIO-DI-WD-14 и настроим его.

В Devices модуль отображается так:

Входы WD-14

Входы модуля имеют названия от EXT1_IN1 до EXT1_IN14. "EXT1" - потому что модуль первый от контроллера. Если подключить еще один такой же его названия входов будут начинаться с "EXT2".

При замыкании входа (возьмем EXT1_IN14) на клемму iGnd состояние входа меняется:

Вход EXT1_IN14 активен

Для того чтобы использовать устройство (вход или выход) в скриптах мы должны знать как к ним обращаться. Как посмотреть имена проще всего? Три пути:

  • Открыть в веб-интерфейсе MQTTChannels
    Каналы MQTT
    . Берем 'Имя устройства' - из первой колонки, 'имя канала' - из второй.
  • Открыть в веб-интерфейсе Devices и щелкнуть мышью на имени:
    Всплывающее имя в Devices
    канала. И имя уже будет скопировано в буфер обмена.
  • Выполнить в консоли команду
    mosquitto_sub -v -t /#
    
    Про MQTT

В конструкции 'dev[...]' скриптов используется из полного пути топика, например "/devices/wb-gpio/controls/EXT1_IN14", только имя устройства ("wb-gpio") и имя канала ("EXT1_IN14").

Напомню, что в контроллере используется событийная модель, то есть действовать, реагировать будет на изменения состояния. Для демонстрации создаем новый скрипт

//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); //Это лог. 
  }
});


И сохраним его. После сохранения ничего не происходит. Почему? Как и писал выше - ключевое слово "событие". Мы "подписались" на нужный нам топик MQTT и теперь правило сработает при его изменении. Замкнем-разомкнем клемму "14" модуля WD-14 на его клемму "iGnd":

Правило сработало

То есть конструкция (синтаксис) правила такова:

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); //Это лог. Тут мы видим что в функцию передается как НОВОЕ значение топика так и имя устройстви с именем входа. Зачем, если и так знаем на что была подписка? Об этом дальше.
  }
});

Циклы

Как выполнить что-нибудь несколько раз? Для этого существует конструкция "цикл". В JS описывается так: for ([Счетчик]; [условие]; [выражение]){Операторы}

"Операторы - это часть кода, которая будет выполняться несколько раз, от настроек цикла"

  • Счетчик - выполняется один раз, обычно используют для обьявления переменной
  • условие - выполняется каждый цикл, если условие верно (true) то цикл повторяется.
  • выражение - выполняется каждый цикл, после выполнения операторов и до проверки условие.

Попробуем?

//for.js
for (i = 0; i<5; i++){
  log.info("Переменная-счетчик i=", i);
}

В окне лога видно:

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

Самое интересное, что ни один из параметров цикла не является обязательным. Пример:

//for_1.js
var i = 0
for (; ; ){
  log.info("Переменная-счетчик i=", i);
  i++;
  if (i > 5)  {break}; //
}

Главное не допускать "бесконечных" циклов

Функции

Выходы

Таймеры

Полезные ссылки