Скриптер: логика кодом
Скриптер — узел U-Logic (редактор программы управления), в котором логику управления вы описываете не цепочкой узлов, а кодом на JavaScript: реагируете на нажатия на панели, отправляете команды устройствам, обновляете надписи и кнопки на панели. Устройства и элементы панели вы называете по именам. Подписки на события Скриптер снимает сам при каждом новом «Развернуть» и при остановке узла.
Скриптер нужен тем, кто умеет программировать и хочет вместо десятка узлов change, switch и delay написать несколько строк кода: когда одно нажатие управляет несколькими устройствами, логика зависит от состояния или от времени, а однотипные кнопки обрабатываются по шаблону. Если вы только начинаете, соберите программу цепочкой узлов — это проще и нагляднее, см. «Связь интерфейса с программой». К Скриптеру можно перейти позже, когда цепочки станут громоздкими.
Скриптер не заменяет драйвер. Драйвер объясняет системе, как говорить с устройством определённого типа: какие у него команды и какие ответы. Скриптер описывает, что делать, когда нажали кнопку, и использует команды уже существующих драйверов. Если устройства нет в списке драйверов, сначала добавьте драйвер, см. Dynamic Driver.
Три узла Скриптера
В палитре U-Logic, в категории «функция», есть три узла:
Узел |
Зачем нужен |
|---|---|
|
сам Скриптер: в нём пишется код. Один вход и один выход (число выходов можно изменить), как у обычного узла |
|
отдельная точка выхода из кода: код отправляет сообщение в узел по его ID, а дальше оно идёт по проводам к любым другим узлам программы |
|
отдельная точка входа в код: сообщения, которые пришли в узел по проводам, код получает по ID узла |
Обычно хватает одного узла scripter. Узлы scripter - sender и scripter - receiver нужны, когда Скриптеру надо отдать результат другим узлам программы в нескольких местах или принять сообщения из нескольких источников. У узла scripter один вход и один выход.
Как их настроить:
scripter - sender: поле ID (например,toLog), поле Выходов (от 1 до 64), необязательное поле Имя. У узла нет входа. Из кода:ctrl.sender('toLog').send(msg); второй аргумент — номер выхода, считая с нуля (по умолчанию 0 — первый выход):ctrl.sender('toLog').send(msg, 1).scripter - receiver: поле ID (например,sensors), необязательное поле Имя. У узла один вход и нет выходов. Из кода:ctrl.receiver('sensors').on(msg => { … }). Нужно несколько входов в код — добавьте несколько узловscripter - receiverс разными ID.
После добавления узла на холст подпись показывает ID: sender: toLog, receiver: sensors.
Как открыть Скриптер и из чего состоит окно
Есть два способа открыть редактор кода:
Дважды щёлкните по узлу
scripterна холсте. Откроется окно «Изменить узел scripter».Откройте в боковой панели U-Logic вкладку Scripter. В ней кнопка Открыть Scripter IDE открывает расширенный редактор в отдельной вкладке браузера (см. «Редактор Скриптера (IDE)»). Список Скриптеры в проекте показывает все узлы Скриптера (имя узла и имя потока). Щелчок по строке открывает редактор этого узла. Пока в программе нет ни одного узла, в списке написано «Нет узлов scripter в текущих потоках.».
В окне узла:
поле Name — подпись узла на холсте; если оставить пустым, узел подписан
scripter;поле Outputs — число выходов узла;
вкладки кода On Start, On Message и On Stop — три фазы работы узла (следующий раздел);
кнопка + добавляет свою именованную вкладку: её код выполняется в составе On Start, в порядке вкладок. Так настройку можно разбить на части, например «Источники», «Звук», «Микрофоны». Двойной щелчок по названию вкладки переименовывает её, крестик удаляет. Три основные вкладки переименовать и удалить нельзя;
вкладка Скрипты — ваши скрипты и папки; кнопки Скрипт, Папка, Импорт .js, Из библиотеки, Экспорт всё (.json) и Открыть в отдельной вкладке;
вкладка Диагностика — показывает, что сейчас происходит в узле (см. «Где смотреть ошибки»);
кнопка Сгенерировать в окне узла;
переключатель Включено внизу окна: выключенный узел не работает (см. «Как запустить и остановить Скриптер»);
у каждого редактора кнопка разворота на весь экран.
В коде доступны объекты ctrl, node, msg, send, log, env, context, flow, global, console, Buffer, а также таймеры setTimeout и setInterval: их Скриптер остановит сам, когда остановится узел. Объект node содержит id, name, log, warn, error, status и send. Окно узла не проверяет, правильно ли вы написали имена устройств, команд и сигналов: это обычные строки, ошибку в них видно только при работе. Такую проверку делает расширенный редактор.
Три фазы: On Start, On Message, On Stop
Код делится на три части. От того, в какую вкладку вы его положите, зависит, когда он выполняется.
Вкладка |
Когда выполняется |
Что здесь писать |
|---|---|---|
On Start |
один раз: при «Развернуть» и при запуске U-Logic |
вся настройка: подписки на нажатия кнопок и ответы устройств, таймеры, начальные надписи на панели. Здесь живёт большая часть программы |
On Message |
на каждое сообщение, которое пришло в узел по проводу |
только действия: отправить команду, обновить панель. Сообщение лежит в переменной |
On Stop |
один раз при остановке узла или повторном «Развернуть» |
завершающие действия, если они нужны. Подписки и таймеры снимать не нужно — Скриптер делает это сам |
Главное правило: подписки объявляют один раз, в On Start. При каждом «Развернуть» узел запускается заново: старые подписки снимаются, On Start выполняется снова. Поэтому двойных подписок не бывает. Если объявить подписку в On Message, она будет добавляться на каждое сообщение: через 50 сообщений одно нажатие кнопки выполнит обработчик 50 раз. Подробнее — в разделе «Правила» ниже.
В On Start код выполняется синхронно: паузу здесь делают через ctrl.wait (раздел «Таймеры»). В On Message можно писать await — вкладка работает как асинхронная функция, и вызывать done не нужно: достаточно return.
Как запустить и остановить Скриптер
Запустить. Напишите код, закройте окно узла и нажмите Развернуть. Узел запустится, On Start выполнится один раз. После изменения кода снова нажмите Развернуть: только так новый код начинает работать.
Остановить. У узла нет отдельной кнопки «Стоп». Есть два способа, и в обоих нужно нажать Развернуть:
откройте окно узла, выключите переключатель Включено и нажмите Развернуть — узел остановится, но останется в программе, его можно включить снова;
удалите узел и нажмите Развернуть — Скриптер снят насовсем.
При остановке подписки и таймеры снимаются сами. После «Развернуть» на узле зелёная точка с надписью «onStart» означает, что On Start выполнился.
Где смотреть ошибки. Куда попадёт ошибка, зависит от того, где она случилась:
Где ошибка |
Что вы увидите |
|---|---|
В самом On Start (например, в строке 3 вызвана несуществующая функция) |
на узле красное кольцо со статусом «onStart L3 ReferenceError» (фаза, строка, тип ошибки); в боковой панели отладка — сообщение от узла «scripter onStart: … is not defined»; во вкладке Диагностика → Ошибки — строка «On Start · строка 3 · … is not defined» |
Внутри |
статус узла остаётся зелёным «onStart»; в отладке — предупреждение «sdk timer handler error: …»; вкладка Диагностика пишет «Ошибок нет» |
Внутри обработчика, например |
проверяйте шаги сами: поставьте |
Из последней строки следуют два правила: держите код обработчика коротким и проверяйте его выводом node.warn('дошли до шага 2') (сообщение появится в отладке), а паузы между шагами делайте через ctrl.wait, как в примере ниже.
Ошибка в одном обработчике не останавливает ни узел, ни остальные обработчики и таймеры. Вкладка Диагностика в окне узла (и такая же панель в расширенном редакторе) показывает состояние Скриптера. Кнопка Обновить перечитывает данные; внизу написано, когда был последний прогон.
Раздел |
Что показывает |
|---|---|
Строки On Start, On Message, On Stop |
длительность последнего выполнения каждой фазы (в миллисекундах) и дату |
Ошибки |
ошибки с указанием фазы, строки и текста; если их нет — «Ошибок нет» |
Трасса |
по строке на каждую команду: время, кому и что отправлено, например «Демо: матрица → route {«input»:0,»output»:1}» |
Значения |
последние значения сигналов; если их нет — «Значений нет» |
Подключения |
состояние подключений к устройствам; если их нет — «Подключений нет» |
Консоль |
вывод |
Если код не удаётся разобрать (например, пропущена скобка), сообщение об ошибке называет узел и фазу, в которой она найдена.
Как Скриптер связан с устройствами и панелью
flowchart LR
S["Скриптер<br/>(ваш код)"]
D["Подключение к устройству<br/>(device - connection)"]
P["UI-Panel<br/>экран панели"]
S -- "команда" --> D
D -- "ответ устройства" --> S
S -- "изменить кнопку, текст" --> P
P -- "нажатия на панели" --> S
Устройства. Код обращается к устройству по имени:
ctrl.device('Демо: матрица'). Имя — это поле Name узла подключенияdevice - connectionв вашей программе U-Logic, а не тип драйвера. Одинаковых устройств может быть несколько (три камеры одного типа), и имя выбирает нужное. Если узла подключения с таким именем в программе нет, команда никуда не уйдёт (sendвернётfalse, без ошибки).Команды и ответы устройства берутся из его драйвера. Скриптер не знает протокол устройства: он говорит драйверу «выполни команду
routeс такими параметрами», а драйвер превращает её в команду устройства. Ответ устройства драйвер превращает в сигнал с именем, напримерroute; на это имя вы подписываетесь. Имена команд, их параметры и имена сигналов смотрите в описании драйвера или в редакторе драйверов.Панель. Код обращается к панели и её элементам по именам:
ctrl.panel('Демо-зал').onTap('Src_Cam1_b', …). Имя панели — название интерфейса, из которого панель присылает события; имена элементов — как в UI-Designer. Подробнее про интерфейсы — UI-Panel.
Устройства работают сами по себе: команды им уйдут и без панели. Панель нужна только для методов ctrl.panel.
Первая программа
Задача: при нажатии кнопки Src_Cam1_b на панели «Демо-зал» переключить вход матрицы на камеру 1 и подсветить кнопку. В программе должен быть узел подключения device - connection с именем «Демо: матрица», как в учебной программе «Демо-зала».
Примечание
В готовой программе «Демо-зала» эти кнопки уже обслуживает цепочка узлов. Пробуйте на новой пустой вкладке потока и не оставляйте на одной кнопке две обработки сразу: иначе при нажатии сработают обе.
Перетащите из палитры (категория «функция») узел
scripterна вкладку потока.Дважды щёлкните по узлу и откройте вкладку On Start.
Впишите код:
ctrl.panel('Демо-зал').onTap('Src_Cam1_b', () => { ctrl.device('Демо: матрица').send('route', { input: 1, output: 1 }); ctrl.panel('Демо-зал').setSelected('Src_Cam1_b'); // подсветить кнопку });
Закройте окно узла и нажмите Развернуть.
Нажмите кнопку
Src_Cam1_bна панели. Матрица переключит выход 1 на вход 1, кнопка подсветится.
Что здесь происходит:
onTap— подписка на короткое нажатие элемента. Она живёт, пока работает узел, и снимается при следующем «Развернуть».send('route', { input: 1, output: 1 })— команда драйверу матрицы: имя команды и параметры определяет драйвер.setSelected— команда панели: показать кнопку выбранной.
Частые ошибки:
Забыли нажать Развернуть — работает прежний код.
Имя устройства не совпадает с полем Name узла подключения (лишний пробел, другой регистр) — команда молча не уходит. Проверьте список имён:
node.log('Устройства: ' + ctrl.devices().join(', ')).Имя панели или кнопки не совпадает с тем, что в UI-Designer — подписка ничего не ловит.
Связь с остальной программой: msg, return, sender и receiver
Узел scripter стоит в потоке как обычный узел: сообщение, подведённое проводом к его входу, приходит в On Message в переменной msg. Читайте msg.topic и msg.payload и выполняйте действие:
// On Message
if (msg.topic === 'camera') {
ctrl.device('Демо: камера 1').send('recallPreset', { preset: msg.payload });
}
Чтобы отдать сообщение дальше по проводам, верните его из On Message через return msg либо вызовите send(msg) или node.send(msg). Это подходит, когда сообщений несколько или они появляются позже, например в обработчике кнопки из On Start:
// On Message
msg.payload = { handled: true };
return msg; // уйдёт на выход узла
// On Start: сообщение из обработчика кнопки
ctrl.panel('Демо-зал').onTap('Home_Meeting_b', () => {
node.send({ topic: 'scenario', payload: 'meeting' });
});
Не путайте с ctrl.publish(тема, данные) и ctrl.on(тема, fn): это обмен сообщениями между разными узлами Скриптера, он на выход узла по проводам не идёт.
Если у узла scripter не хватает одного входа и одного выхода, используйте scripter - sender и scripter - receiver (раздел «Три узла Скриптера»).
Справочник API: объект ctrl
В Скриптере ctrl уже готов к работе, ничего подключать не нужно.
Устройства: ctrl.device(имя)
ctrl.device('Демо: матрица') возвращает объект устройства. Его можно сохранить: const matrix = ctrl.device('Демо: матрица');.
send(команда, параметры?) — отправить команду устройству. команда — имя команды из драйвера, параметры — объект (необязательно); какие ключи нужны, определяет драйвер. Возвращает true, если команда ушла, и false, если устройства с таким именем ещё нет.
matrix.send('route', { input: 1, output: 1 }); // выход 1 — вход 1
camera.send('recallPreset', { preset: 2 }); // команда с параметром
codec.send('hangup'); // команда без параметров
on(сигнал, fn) — вызывать fn(resp) на каждый ответ устройства указанного типа. сигнал — имя ответа из драйвера; в resp приходит весь объект ответа. Возвращает функцию off(): вызов снимает подписку досрочно (обычно не нужно). Если подписка молчит, подпишитесь через onAny и посмотрите, какое значение resp.type приходит на самом деле.
matrix.on('route', (resp) => {
if (resp.ack) node.log('Матрица подтвердила команду, выход ' + resp.output);
});
onAny(fn) — вызывать fn(resp) на любой ответ устройства. Пригодится при отладке. Ответ без поля type (служебный пакет, пустой кадр) попадает только сюда.
onStatus(fn) — следить за связью: fn({ connected }) вызывается при каждом изменении, connected равно true или false.
onOnline(fn) и onOffline(fn) — вызвать fn(), когда устройство подключилось / отключилось. Если на момент подписки устройство уже в нужном состоянии, fn() вызывается сразу, поэтому начальный запрос состояния сработает и после повторного «Развернуть».
codec.onOnline(() => codec.send('getContacts'));
matrix.onOffline(() => panel.setText('Status_t', 'Нет связи с матрицей'));
isConnected() — true или false; undefined, если события связи ещё не приходили.
connect() и disconnect() — подключить или отключить устройство вручную.
get(сигнал) — последний ответ указанного типа без подписки или undefined, если такого ответа ещё не было. Первый вызов get включает запоминание, поэтому сразу после запуска он может вернуть undefined.
Особенности:
Узлы программы запускаются в произвольном порядке. Если устройство ещё не готово,
sendмолча возвращаетfalse. Чтобы гарантированно отправить команду на старте, дождитесь устройства черезctrl.ready(ниже).Подписки
on,onStatusи другие можно объявить до появления устройства: они заработают, когда оно появится. Порядок узлов в программе не важен.Все подписки возвращают
off(), но снимать их вручную обычно не нужно.
Панель: ctrl.panel(имя)
ctrl.panel('Демо-зал') возвращает объект панели. У него две группы методов: события (узнать, что сделал пользователь) и команды (изменить элемент). Первым аргументом всегда идёт имя элемента.
События
Все методы принимают имя элемента (или массив имён) и обработчик fn(e), возвращают off().
Метод |
Когда срабатывает |
|---|---|
|
короткое нажатие. Самое частое событие для кнопок |
|
палец нажал / отпустил элемент. Пара нужна, когда важно удержание («пока держишь») |
|
долгое удержание |
|
начали двигать слайдер |
|
значение изменилось: слайдер сдвинули, переключатель переключили. Новое значение в |
|
слайдер отпустили. Подходит, чтобы слать команду один раз в конце движения |
|
выбран пункт списка |
|
любое событие любого элемента панели (без имени элемента) |
|
любое событие по имени. |
Группа кнопок одним обработчиком. Вместо имени можно передать массив имён: обработчик срабатывает на любую из этих кнопок, а e.object подскажет, на какую именно нажали.
// 7 кнопок выбора источника — один обработчик; e.object — нажатая кнопка
panel.onTap(['Src_Cam1_b', 'Src_Cam2_b', 'Src_Cam3_b'], (e) => node.log('источник: ' + e.object));
// слайдер: по отпусканию обновить подпись
panel.onSlideEnd('Snd_Hall_vol_sl', (e) => panel.setText('Snd_Hall_val_lb', Math.round(e.value) + ' %'));
Объект события e:
Поле |
Что это |
|---|---|
|
имя панели, на которой произошло событие |
|
имя элемента |
|
имя события ( |
|
значение: положение слайдера, состояние переключателя |
|
|
|
какая панель прислала событие |
|
исходное событие целиком, если нужны нестандартные поля |
Команды
Вид кнопки. Состояния кнопки: 'default' (обычная), 'selected' (выбрана, подсвечена), 'pressed' (нажата), 'disabled' (заблокирована).
setUiState(имя, состояние)— задать состояние (можно и своё, настроенное на панели).setDefault(имя),setSelected(имя),setPressed(имя),setDisabled(имя)— сокращения для частых состояний.setSelected— самый частый: «эта кнопка сейчас активна».
Текст и значения.
setText(имя, текст)— надпись кнопки, подписи, заголовка.setValue(имя, значение)— позиция слайдера, значение индикатора.fontColor(имя, цвет)иbackgroundColor(имя, цвет)— цвет текста и фона, например'#E53935'.
panel.setText('Status_t', 'Связь есть');
panel.setValue('Snd_Hall_vol_sl', 68);
panel.fontColor('Status_t', '#E53935');
Видимость, положение, размер.
hidden(имя, скрыть)—trueскрывает элемент,falseпоказывает.moveX(имя, x),moveY(имя, y),width(имя, w),height(имя, h)— положение и размер (целые числа в единицах раскладки панели). ВариантыmoveXFloat,moveYFloat,widthFloat,heightFloatпринимают дробные значения.
Всплывающая страница. overlay(страница, фрейм) показывает всплывающую страницу поверх экрана в указанном фрейме; overlay(null, фрейм) закрывает её.
Списки. setList(имя, список) заполняет список массивом пунктов, clearList(имя) очищает, listItems(имя, ключ, значение) обновляет пункты по ключу, removeListItems(имя, ключ) удаляет пункты по ключу.
Встроенный браузер. webUrl(имя, адрес) открывает адрес в элементе Web URL, reload(имя) перезагружает страницу. Пример использования — «Просмотр RTSP-потока в интерфейсе».
Расширенная кнопка. advancedButton(имя, ключСостояния, свойство, значение) задаёт отдельное свойство расширенной кнопки для указанного состояния.
Страницы. navigate(страница) переключает панель на страницу.
Пакетное обновление. batch(действия) выполняет несколько команд одним сообщением: быстрее и надёжнее, чем по одной. действия — массив объектов { name, event, value }, где name — элемент, event — что меняем ('uiState', 'text' …), value — значение. Пустой массив ничего не делает.
panel.batch([
{ name: 'Src_Cam1_b', event: 'uiState', value: 'selected' },
{ name: 'Src_Cam2_b', event: 'uiState', value: 'default' },
{ name: 'Src_Cam3_b', event: 'uiState', value: 'default' }
]);
Несколько панелей с одинаковыми интерфейсами
Если один интерфейс открыт на двух панелях (например, настенной и настольной), имена элементов совпадают, а логика нужна одна. ctrl.panel берёт одну панель, поэтому логику описывают один раз как функцию и вызывают для каждой панели. Команды надо отправлять на все панели, иначе экраны разойдутся:
const panels = ['Панель 1', 'Панель 2'].map((name) => ctrl.panel(name));
const all = (fn) => panels.forEach(fn);
const SOURCES = ['Src_Cam1_b', 'Src_Cam2_b', 'Src_Cam3_b'];
function showSource(active) {
all((p) => SOURCES.forEach((b) => (b === active ? p.setSelected(b) : p.setDefault(b))));
}
all((panel) => {
// нажатие на любой панели управляет матрицей; подсветка придёт по ответу устройства
panel.onTap(SOURCES, (e) => matrix.send('route', { input: SOURCES.indexOf(e.object) + 1, output: 1 }));
});
e.panel в обработчике показывает, с какой панели пришло событие. ctrl.interlock и ctrl.toggle (ниже) берут одну панель: для зеркальных панелей проще ручная подсветка, как в примере.
Реестр и готовность
ctrl.devices()— массив имён всех устройств, известных программе.ctrl.ready(имя)—Promise, который выполнится, когда устройство с этим именем станет доступно (сразу, если уже доступно). Так команда на старте не потеряется:ctrl.ready('Демо: матрица').then(() => matrix.send('route', { input: 0, output: 1 }));
ctrl.on(тема, fn)иctrl.publish(тема, данные)— обмен сообщениями между узлами Скриптера по названию темы.onвозвращаетoff().ctrl.dispose()— снять все подписки вручную. Обычно не нужно.
Таймеры: ctrl.wait и ctrl.timer
Таймеры работают внутри узла, время задаётся в секундах. При остановке узла или новом «Развернуть» они снимаются сами. Ошибка внутри таймера пишется в отладку и таймер не останавливает.
ctrl.wait(секунды, fn) — один раз вызвать fn() через указанное время. Возвращает объект: cancel() — отменить, restart() — начать отсчёт заново, change(секунды) — сменить задержку и начать заново.
const idle = ctrl.wait(300, () => matrix.send('route', { input: 0, output: 1 })); // через 5 минут
panel.onAny(() => idle.restart()); // любой жест сбрасывает
ctrl.timer(секунды, fn) — вызывать fn(count) каждые секунды, count — номер срабатывания с 1. Возвращает объект: cancel() или stop() — остановить и обнулить счётчик, pause() — приостановить, resume() — продолжить, restart() — начать заново, count — текущий номер.
const poll = ctrl.timer(10, () => codec.send('getCallInfo')); // каждые 10 секунд
Пауза между шагами. В On Message паузу между командами делают через await и ctrl.wait:
const pause = (sec) => new Promise((resolve) => ctrl.wait(sec, resolve));
codec.send('hangup');
await pause(0.4);
matrix.send('route', { input: 0, output: 1 });
Группа взаимоисключающих кнопок: ctrl.interlock(панель, имена, опции?)
Группа кнопок, из которых выбрана ровно одна: выбранная становится selected, остальные default, одним сообщением.
панель— имя панели,имена— массив имён кнопок группы.Методы группы:
onSelect(cb)— при нажатии на любую кнопку группы она выбирается, и вызываетсяcb(имя, событие);select(имя)— выбрать кнопку из кода (например, стартовый источник);selected()— имя последней выбранной кнопки илиundefined;off()— снять подпискиonSelect.Опции:
onState— состояние выбранной кнопки (по умолчанию'selected'),offState— остальных (по умолчанию'default'),serverManaged(по умолчаниюfalse): приtrueотправляется только состояние выбранной кнопки, а остальные гасит сама панель.
const src = ctrl.interlock('Демо-зал', ['Src_Cam1_b', 'Src_Cam2_b', 'Src_Cam3_b']);
src.onSelect((name) => {
const input = { Src_Cam1_b: 1, Src_Cam2_b: 2, Src_Cam3_b: 3 }[name];
matrix.send('route', { input, output: 1 });
});
src.select('Src_Cam1_b'); // стартовый источник
Подсветка здесь идёт «по клику». Если нужна подсветка строго по ответу устройства, вместо interlock подпишитесь на ответ матрицы и расставляйте состояния вручную.
Переключатель: ctrl.toggle(панель, имя, опции?)
Наблюдает за двухпозиционной кнопкой (вкл/выкл). Если кнопка настроена на панели как переключатель, панель сама меняет её вид при нажатии, а ctrl.toggle сообщает вам об этом.
панель— имя панели,имя— имя кнопки,опции—{ onState }, состояние «включено» (по умолчанию'selected').Методы:
onChange(cb)—cb(on, событие),onравноtrue, когда кнопка стала «включённой»;value()— последнее состояние (true,falseилиundefined);set(on)— выставить состояние из кода;off()— снять подписку.
const vcsMic = ctrl.toggle('Демо-зал', 'Vcs_Mic_b');
vcsMic.onChange((on) => codec.send('setMute', { mute: !on }));
Память узла: ctrl.context, ctrl.flow, ctrl.global, ctrl.env
Хранилища для состояния между нажатиями:
ctrl.context— память этого узла;ctrl.flow— память вкладки потока (общая для всех её узлов);ctrl.global— общая память всей программы;у первых трёх методы
get(ключ),set(ключ, значение)иkeys();ctrl.env— только чтение:get(имя)возвращает переменную окружения вкладки или группы.
ctrl.flow.set('lastSource', 'Src_Cam1_b');
const last = ctrl.flow.get('lastSource');
Обычные переменные из On Start живут, пока работает узел. Если состояние должно пережить новое «Развернуть» или быть видно другим узлам, храните его в flow или global.
Файлы: ctrl.files
ctrl.files читает и пишет файлы в папке uploads контроллера: выгрузки, журналы, конфигурации. В методах указывайте имя файла (можно с подпапкой, например logs/today.txt) — он всегда лежит внутри uploads. Эти же файлы можно загрузить с компьютера и забрать обратно по SFTP, см. Сетевые порты. Все методы асинхронные, вызывайте их через await.
Метод |
Что делает |
|---|---|
|
прочитать файл. С кодировкой ( |
|
записать файл (перезаписывает); недостающие подпапки создаются |
|
дописать в конец файла |
|
имена файлов в папке или подпапке (без вложенных) |
|
есть ли файл: |
|
удалить файл |
|
создать папку |
// On Message
await ctrl.files.write('state.json', JSON.stringify({ source: 'Src_Cam1_b' }));
if (await ctrl.files.exists('state.json')) {
const saved = JSON.parse(await ctrl.files.read('state.json', 'utf8'));
node.log('Прошлый источник: ' + saved.source);
}
Для обычного состояния между перезапусками достаточно ctrl.flow и ctrl.global; ctrl.files нужен, когда данные — именно файлы.
Подключение прямо из кода: ctrl.connect(опции)
Создаёт подключение к устройству из кода, без узла device - connection. Нужно, когда адрес устройства известен только во время работы. Возвращает то же, что ctrl.device(имя).
// On Start
ctrl.connect({
name: 'Демо: камера 1', // как обращаться: ctrl.device('Демо: камера 1')
driver: 'visca-generic', // имя драйвера
transport: 'udp', // по умолчанию 'tcp'
host: '192.168.1.21',
port: 52381
});
Обязательные опции — name и driver; необязательные — transport (tcp по умолчанию, udp, telnet, ssh), host, port, params (дополнительные параметры драйвера), autoConnect, timeout.
Подключение временное: оно существует, пока работает узел, исчезает при новом «Развернуть» и не видно среди узлов подключений. Для постоянных, редактируемых подключений используйте узлы device - connection.
Правила
Все правила сводятся к одному: настройка — один раз в On Start, дальше только реакции.
Подписки объявляйте в On Start. Любая подписка (
onTap,on,onValueChanged,onStatus…) — это правило, которое должно работать всё время, пока работает узел.Очистка происходит сама. При остановке узла или «Развернуть» Скриптер снимает все подписки на панель и устройства и останавливает таймеры. Вызывать
off()иdispose()вручную не нужно.Новое «Развернуть» безопасно. Узел запускается заново, On Start создаёт подписки с нуля, двойных не будет.
В On Message только действия. Подписка, объявленная в On Message, создаётся заново на каждое сообщение, а старые остаются. Одно нажатие запускает все накопленные обработчики сразу (после 50 сообщений — 50 раз), а память растёт, пока узел не остановят.
// On Message: НЕПРАВИЛЬНО panel.onTap('Src_Cam1_b', () => matrix.send('route', { input: 1, output: 1 })); // On Start: правильно — один раз panel.onTap('Src_Cam1_b', () => matrix.send('route', { input: 1, output: 1 })); // On Message: только действие в ответ на пришедшее сообщение if (msg.topic === 'camera') camera.send('recallPreset', { preset: msg.payload });
Ошибки изолированы. Ошибка в одном обработчике или таймере не останавливает остальные. Шаги обработчика касания проверяйте так, как сказано в «Где смотреть ошибки».
Расширенный редактор Скриптера
Кроме окна узла, у Скриптера есть расширенный редактор (Scripter IDE) — отдельная страница с автодополнением по именам вашей программы, проверкой имён, диагностикой и терминалом. Открывается кнопкой Открыть Scripter IDE на вкладке Scripter боковой панели U-Logic. Как им пользоваться и как отлаживать код — на странице «Редактор Скриптера (IDE)».
Устранение неполадок
Команда устройству не срабатывает (send вернул false). Устройства с таким именем нет. Чаще всего написано имя драйвера вместо поля Name узла device - connection либо узел устройства ещё не запустился. Сверьте имя, выведите список node.log(ctrl.devices().join(', ')); если проблема только на старте, оберните отправку в ctrl.ready('имя').then(…).
Нажатия панели не ловятся (onTap молчит). Проверьте, что имя панели и имя элемента точно совпадают с интерфейсом в UI-Designer. Если имена верные, а панель не реагирует, версия UI-Panel может не поддерживать Скриптер — обратитесь к интегратору.
Команды панели не доходят (setText, navigate). Причины те же: неверное имя панели или элемента либо версия UI-Panel без поддержки Скриптера.
Обработчик срабатывает много раз. Подписки объявлены в On Message. Перенесите их в On Start и нажмите Развернуть.
Новый код не действует. После правки нужно нажать Развернуть; в расширенном редакторе — Deploy.
Окно узла не подсвечивает опечатку в имени. Это ожидаемо: имена устройств, команд и сигналов — строки, и окно узла их не проверяет. Откройте код в расширенном редакторе: он подчёркивает неизвестные имена жёлтым. Ищите ошибку и по боковой панели отладка, вкладке Диагностика (раздел Трасса покажет, какие команды ушли) и списку ctrl.devices().
Не понятно, где ошибка и ушла ли команда. Запустите код тестом и посмотрите диагностику: пошаговый разбор — в разделе «Отладка по шагам» редактора.
Пример: сценарий «Перерыв» в «Демо-зале»
Этот пример уже лежит в программе «Демо-зал» (блок 14 «Скриптер: сценарий «Перерыв»», см. «Программа «Демо-зал»: обзор»). Ниже — что он делает, как его собрать с нуля и как проверить.
Зачем он нужен. Главная страница «Демо-зала» запускает сценарии одной кнопкой: «Начало совещания», «Начало видеоконференции», «Выключить». Четвёртый сценарий, «Перерыв», собран не цепочкой узлов, а одним узлом scripter. Нажатие на «Перерыв» делает сразу несколько действий с паузами между ними. В цепочке для этого нужны узлы change, split, delay, switch, здесь — один небольшой скрипт.
Что делает сценарий. После нажатия кнопки «Перерыв» на главной:
сразу: кнопка «Перерыв» подсвечивается, микрофоны зала выключаются;
через 1 с: с экрана зала убирается источник: на панели «Нет источника»;
через 2 с: камера 1 возвращается в положение «Домой»;
через 3 с: надпись в шапке меняется на «Демо-зал — перерыв».
Когда нажимают другой сценарий («Начало совещания», «Начало видеоконференции» или «Выключить» в окне «Выключить всё?»), перерыв считается оконченным: подсветка с кнопки «Перерыв» снимается, а в шапке снова «Демо-зал».
Примечание
Если вы загрузили программу «Демо-зал» целиком, узел «Сценарий «Перерыв»» в ней уже есть, и кнопку «Перерыв» обрабатывает именно он. Чтобы пройти шаги ниже с нуля, сначала выключите готовый узел (переключатель Включено в его окне) и нажмите Развернуть: иначе при нажатии сработают оба узла и каждая команда уйдёт дважды. Пример рассчитан на подключения «Демо: звуковой процессор», «Демо: матрица», «Демо: камера 1» и интерфейс «Демо-зал»: без них команды не дойдут.
Как собрать сценарий
Откройте U-Logic и вкладку потока, на которой лежит программа «Демо-зал» (или новую вкладку потока).
Найдите в палитре слева узел
scripterв категории «функция» и перетащите его на холст.
Палитра, категория «функция»: 1 —
scripter, 2 —scripter - sender, 3 —scripter - receiverДважды щёлкните по узлу. Откроется окно «Изменить узел scripter».
В поле Name впишите
Сценарий «Перерыв». Так узел будет подписан на холсте.Откройте вкладку On Start.
Впишите в неё код:
// Сценарий «Перерыв»: кнопка Home_Break_b на главной интерфейса «Демо-зал». // Кнопку не ловит ни один узел ui - switch — весь сценарий здесь. const panel = ctrl.panel('Демо-зал'); // интерфейс — по имени, как в UI-Designer const sound = ctrl.device('Демо: звуковой процессор'); // устройства — по имени подключения const matrix = ctrl.device('Демо: матрица'); const camera = ctrl.device('Демо: камера 1'); panel.onTap('Home_Break_b', () => { // Шаг 1 — сразу: подсветить кнопку и выключить микрофоны зала panel.setUiState('Home_Break_b', 'pressed'); sound.send('setMute', { instanceTag: 'DemoMicMaster', channel: 1, state: true }); // Шаг 2 — через 1 с: убрать источник с экрана (как кнопка Src_Clear_b) ctrl.wait(1, () => matrix.send('route', { input: 0, output: 1 })); // Шаг 3 — через 2 с: камера 1 в положение «Домой» (как кнопка Cam_Home_b) ctrl.wait(2, () => camera.send('recallPreset', { preset: 1 })); // Шаг 4 — через 3 с: надпись в шапке ctrl.wait(3, () => panel.setText('Hdr_Title_lb', 'Демо-зал — перерыв')); }); // Начался другой сценарий — перерыв окончен: снять подсветку и вернуть заголовок panel.onTap(['Home_Meeting_b', 'Home_Vcs_b', 'Pop_Yes_b'], () => { panel.setUiState('Home_Break_b', 'default'); panel.setText('Hdr_Title_lb', 'Демо-зал'); });
Окно «Изменить узел scripter»: 1 — поле Name, 2 — вкладка On Start, 3 — код сценария, 4 — переключатель Включено
Проверьте, что внизу окна переключатель Включено включён, и нажмите Готово.
Нажмите Развернуть.
Что вы увидите. Под узлом на холсте появится зелёная точка со статусом «onStart»: On Start выполнился, подписки на кнопки созданы. Красное кольцо вместо точки означает ошибку в On Start, см. «Где смотреть ошибки».
Как устроен код
ctrl.panel('Демо-зал')— панель, заданная именем интерфейса (так он назван в UI-Designer). Строка сохраняет её в переменнуюpanel, чтобы не повторять имя.ctrl.device('Демо: матрица')— устройство, заданное именем подключения: это поле Name узлаdevice - connectionв программе, а не тип драйвера. Три строки подряд берут звуковой процессор, матрицу и камеру 1.panel.onTap('Home_Break_b', () => { … })— «когда на панели коротко нажмут элементHome_Break_b(кнопка «Перерыв»), выполни всё, что в скобках». Это подписка: она создаётся один раз, при «Развернуть», и дальше срабатывает на каждое нажатие.panel.setUiState('Home_Break_b', 'pressed')— команда панели: показать кнопку нажатой (подсвеченной). Имя элемента — как в UI-Designer.sound.send('setMute', { instanceTag: 'DemoMicMaster', channel: 1, state: true })— команда устройству: имя команды из драйвера и её параметры. Параметры определяет драйвер процессора; здесь команда выключает микрофоны зала.ctrl.wait(1, () => …)— «через 1 секунду выполни то, что после стрелки». Паузы отсчитываются от нажатия: 1, 2 и 3 секунды. Это не «подождать 1 с, потом ещё 2 с»: все три таймера запускаются сразу.matrix.send('route', { input: 0, output: 1 })— матрица: на выход 1 подать вход 0, то есть отключить выход. Экран зала остаётся без источника.camera.send('recallPreset', { preset: 1 })— камера 1: вызвать пресет 1 («Домой»).panel.setText('Hdr_Title_lb', 'Демо-зал — перерыв')— поменять надпись в шапке.Вторая подписка
panel.onTap([…], …)принимает сразу три имени кнопок — один обработчик на все. Она возвращает кнопку и заголовок в обычный вид.
Код ничего не делает с видом кнопок источников и микрофонов. «Нет источника» на экране и «Микрофоны» в шапке меняются по ответам устройств, как и при нажатии обычных кнопок: команда запускает действие, а вид на панели обновляет ответ устройства.
Паузы сделаны через ctrl.wait, а не через await: в On Start код выполняется синхронно.
Что вы увидите на панели
Откройте интерфейс «Демо-зал» в UI-Panel и нажмите «Перерыв» на главной:
Главная «Демо-зала» через 3 секунды после нажатия: 1 — кнопка «Перерыв» подсвечена, 2 — надпись в шапке «Демо-зал — перерыв», 3 — на экране «Нет источника»
Сначала подсвечивается кнопка, через 1–2 секунды экран показывает «Нет источника», а через три секунды меняется надпись в шапке. Нажмите «Начало совещания»: подсветка снимется, в шапке снова «Демо-зал».
Как проверить, что команды ушли
Откройте окно узла, перейдите на вкладку Диагностика и нажмите Обновить.
Вкладка Диагностика: 1 — вкладка, 2 — раздел Трасса, 3 — строки команд: «Демо: звуковой процессор → setMute …», «Демо: матрица → route …», «Демо: камера 1 → recallPreset …»
В разделе Трасса — по строке на каждую отправленную команду, с временем. После нажатия «Перерыв» там должны быть строки setMute, route, recallPreset. Если строк нет, подписка не сработала: проверьте, что вы нажали Развернуть и что имя Home_Break_b и имя интерфейса совпадают с UI-Designer. Если строка есть, а устройство не отреагировало, проверьте подключение устройства.
Все узлы Скриптера видны в боковой панели на вкладке Scripter:
Боковая панель: 1 — вкладка Scripter, 2 — кнопка Открыть Scripter IDE, 3 — список Скриптеры в проекте
В списке — строка «Сценарий «Перерыв» · Демо-зал» (имя узла и вкладки потока); щелчок по ней открывает редактор этого узла.
Если что-то не работает
Статус узла — красное кольцо. Ошибка в On Start: статус называет фазу, строку и тип ошибки, подробности — на вкладке Диагностика → Ошибки и в боковой панели отладка.
Кнопка подсвечивается, но дальнейшие шаги не происходят. Добавьте в обработчик
node.warn('шаг 1')перед каждым шагом и смотрите панель отладка; проверьте имена устройств черезnode.log(ctrl.devices().join(', ')).Команды уходят дважды. Кнопку «Перерыв» обрабатывают два узла: готовый из «Демо-зала» и ваш. Оставьте один (см. примечание выше).
Нажатие на панели ничего не запускает. Имя интерфейса или кнопки в коде не совпадает с UI-Designer, либо вы не нажали Развернуть после правки.
Как остановить сценарий
Откройте окно узла, выключите переключатель Включено и нажмите Развернуть: нажатия на «Перерыв» перестанут запускать шаги. Можно удалить узел и нажать Развернуть.