Домашние вкусняшки на Raspberry PI. Веб-морда робота /окончание/
Продолжение. Начало здесь. Для тех кто не читал первую часть - в ней речь шла о том, как на основе raspberry pi создать блок управления, скажем, роботом, задействуя в качестве выходных сигналов 4 дискретных выхода GPIO и с управляющим алгоритмом на языке SFC.
6. Теперь, собственно, сам веб-интерфейс.
Для начала разработки скачайте архив Inkscape и распакуйте его в любой удобной папке. Это портабельный, не требующий инсталляции редактор SVG-графики. Запускать надо файл inkscape.exe, для удобства можно сделать ярлык на рабочем столе.
Скачайте также библиотеку виджетов, при помощи которых будем оживлять нашу мнемосхему. Распакуйте в любом удобном месте.
Создайте целевую папку (назовём её web), в которой будет лежать вся веб-начинка, содержимое этой папки и будет заливаться в ПЛК в виде zip- архива. И скопируйте туда содержимое папки web из архива виджетов.
7. Запускаем inkscape:
Кнопками + и - выставляем масштаб. На экране появилось что-то несуразное. Но это только заготовка с тремя виджетами - кнопка(button), лампочка(led) и рамка(border). Каждый из этих виджетов находится внутри своей группы, или слоя. Их три:
- слой ввода (inp) - кнопки и поля ввода;
- слой динамических элементов (dyn) - лампочки, индикаторы;
- слой статики (stat) - рамки, шкалы плюс всё что нарисуете сами в качестве заднего плана;
Чтобы эти слои сразу обозначить, начальная страница-заготовка и содержит три этих элемента/виджета, вместо которых нужно будет разместить что-то своё. Чтобы войти в нужный слой, выбираем левой кнопкой элемент, затем по правой кнопке выбираем в выпадающем меню "Войти в группу #inp, #dyn или #stat".
В правой части экрана inkscape фрейм xml-редактора. Если у вас он не появился, жмите shift+ctrl+x или поищите соотв.кнопку на правом поле экрана. Редактор показывает содержимое SVG-файла, который в xml формате. И не только показывает, но и позволяет менять свойства виджетов, так что без него никуда.
8. Итак, заходим в слой inp и размещаем кнопки управления. Кнопки берём из папки виджетов, для этого не выходя из inkscape открываем файл buttons.mns.svg (он откроется в новом окне):
Берём так - в окне buttons.mns.svg заходим в слой inp, выделяем кнопку и копируем её в буфер (ctrl+c), в окне mns.svg заходим в слой inp и пастим (ctrl+v).
После копирования кнопки можно расставить и выровнять:
Здесь кроме выравнивания кнопок я удалил ненужную старую кнопку и добавил четыре стрелки, которые будут светиться зелёным цветом при движении. Стрелки берутся из arrows.mns.svg аналогично кнопкам, только вставлять их нужно в слой #dyn. Для поворачивания стрелок есть соотв. команда в меню Объект, либо это можно делать изменяя свойство transform через xml-редактор:
9. Теперь подпишем кнопки, делается это так:
А самое главное - изменить у кнопок свойство click так, чтобы мы потом в javascript-е смогли идентифицировать кнопку при её нажатии:
Соответственно, остальным кнопкам также нужно задать свойства onclick:
- кнопка ВЛЕВО: buttonclick('left')
- кнопка ВПРАВО: buttonclick('right')
- кнопка НАЗАД: buttonclick('revers')
- кнопка ВПЕРЁД: buttonclick('forward')
- кнопка СТОП: buttonclick('stop')
10. Теперь второстепенные дела - вместо прежней панели берём другую (взята из panels.mns.svg и немного растянута) и передвигаем лампочку на панель. Вот что получилось:
Проверьте - динамические виджеты (стрелки и лампочки) должны находиться с слое (группе) dyn. Прописываем следующие идентификаторы (св-во id виджета) :
- лампочка - led1
- стрелка вправо - ar
- стрелка влево - al
- стрелка назад - ab
- стрелка вперёд - af
По этим id мы будем обращаться к ним из программы javascript.
11. Осталось бросить последний камень в огород визуализации - оживить полученную мнемосхему. Код javascript (js) должен отправлять команды от кнопок на ПЛК и забирать оттуда данные - булевские переменные do_left, do_right, do_up, do_down и по их состоянию зажигать соответствующие стрелки.
11.1 Откроем текстовым редактором файл index.js . Именно в нём сосредоточена логика визуализации. Этот файл дан для примера и его нужно подправить под наше ТЗ. Начнём с начала:
var PlcIP = "http://192.168.0.179:1200";
// массив структур для ajax-запроса чтения
var InBuf = [
{"name": "ana1", "rw":"r"},
{"name": "prg1.bool1", "rw":"r"},
{"name": "prg2.msg1", "rw":"r"}
];
// массив структур для ajax-запроса записи
var OutBuf = [
{"name": "ana2", "rw":"w"},
{"name": "prg2.msg2", "rw":"w"},
{"name": "prg1.bool1", "rw":"w"}
];
// переменные для чтения из ПЛК
var glob_ana, prg1_bool, prg2_msg;
Скорректируем это под нашу задачу, переименовав фиктивные переменные в реальные:
var PlcIP = "http://192.168.0.179:1200";
// массив структур для ajax-запроса чтения
var InBuf = [
{"name": "do_left", "rw":"r"},
{"name": "do_right", "rw":"r"},
{"name": "do_down", "rw":"r"},
{"name": "do_up", "rw":"r"}
];
// массив структур для ajax-запроса записи
var OutBuf = [
{"name": "main.cmd", "rw":"w"}
];
// переменные для чтения из ПЛК
var do_up, do_down, do_left, do_right;
Как видите, доступ к локальным переменным изаграфа тоже возможен, только нужно указать имя программы main. IP-адрес ПЛК нужно будет поставить реальный, не изменяя порт 1200.
11.2 Далее:
// считывает ответ ПЛК
// эту функцию следует указать как аргумент f_ok для PlcIO()
function on_plc_read(data) {
InBuf = data; // не удаляйте эту строку, это сохранит кэш низкоуровневых адресов тэгов и
// ускорит их поиск на стороне ПЛК
glob_ana = data[0]["value"];
prg1_bool = data[1]["value"];
prg2_msg = data[2]["value"];
}
Это функция-обработчик события успешного чтения данных. Вытаскивает из ответа нужные данные и кладёт их в соотв.переменные. Снова прописываем реальные имена:
function on_plc_read(data) {
InBuf = data; // не удаляйте эту строку, это сохранит кэш низкоуровневых адресов тэгов и
// ускорит их поиск на стороне ПЛК
do_left = data[0]["value"];
do_right = data[1]["value"];
do_down = data[2]["value"];
do_up = data[3]["value"];
}
11.3 Вот этот код необходимо пояснить:
// точка входа программы
$(function() {
Mns.setup({
onRdy: init,
onBtnClick: click, // обработчик ввода от кнопок
onInput: input // обработчик ввода от InputText
});
});
// запускается из mns.js при завершении загрузки mns.svg
function init() {
setInterval(function() {cycle();}, 1000);
}
// срабатывает по нажатию кнопки
function click(id) {
alert("You press button " + id);
}
// срабатывает по вводу в inputText
function input(id) {
alert("You input '" + Mns.inp[id].get() + "' into " + id);
}
// рабочий чикл программы, настраивается в init()
function cycle() {
// опросить ПЛК
PlcIO(PlcIP, InBuf, on_plc_read, f_error);
// записать в ПЛК
OutBuf[0]["value"] = 123;
OutBuf[1]["value"] = "ok";
OutBuf[2]["value"] = true;
PlcIO(PlcIP, OutBuf, on_plc_write, f_error);
}
Строка $(function() {}) это точка входа в данный скрипт и отсюда всё начинает крутиться: вызывается библиотечная функция Mns.setup(), та вызывает init(), init() создаёт нить, запускающую функцию cycle() с интервалом 1000мС.
Немного модифицируем этот код, начиная с init():
// запускается из mns.js при завершении загрузки mns.svg
function init() {
setInterval(function() {cycle();}, 100);
}
// срабатывает по нажатию кнопки
function click(id) {
var cmd;
switch(id) {
case 'stop':
cmd = 0;
break;
case 'left':
cmd = 3;
break;
case 'right':
cmd = 4;
break;
case 'forward':
cmd = 2;
break;
case 'revers':
cmd = 1;
break;
default:
return;
}
OutBuf[0]["value"] = cmd;
PlcIO(PlcIP, OutBuf, on_plc_write, f_error);
}
// срабатывает по вводу в inputText
function input(id) {
alert("You input '" + Mns.inp[id].get() + "' into " + id);
}
// рабочий чикл программы, настраивается в init()
function cycle() {
// опросить ПЛК
PlcIO(PlcIP, InBuf, on_plc_read, f_error);
}Во-первых, увеличили частоту вызова cycle(), которая производит опрос ПЛК. Во-вторых, сделали обработку нажатия кнопок. Помните свойство onclick в виджете button, мы прописали там аргументы "left","right" и т.д? Теперь эти строковые значения прилетают сюда, мы преобразовываем их в код команды и посылаем в ПЛК. Ну и в-третьих, убрали из cycle() запись данных, т.к. запись в ПЛК будет теперь производиться по факту нажатия кнопок в функции click().
11.4 Ну и осталась только функция update_screen(), которая вызывается при успешном чтении:
Чтобы в целях отладки отвязаться от ПЛК, давайте приведём функции cycle() и update_screen() к такому виду:
// рабочий чикл программы, настраивается в init()
function cycle() {
// опросить ПЛК
PlcIO(PlcIP, InBuf, on_plc_read, f_error);
/* отладка ...*/
update_screen();
do_up = (OutBuf[0]["value"]==2)? true:false;
do_down = (OutBuf[0]["value"]==1)? true:false;
do_left = (OutBuf[0]["value"]==3)? true:false;
do_right = (OutBuf[0]["value"]==4)? true:false;
/* ... отладка */
}
function update_screen() {
Mns.dyn["led1"].set({f: do_left|do_right|do_up|do_down ? "green" : "black"} );
Mns.dyn["ar"].set({f: do_right? "green" : "gray"} );
Mns.dyn["al"].set({f: do_left? "green" : "gray"} );
Mns.dyn["ab"].set({f: do_down? "green" : "gray"} );
Mns.dyn["af"].set({f: do_up? "green" : "gray"} );
}
Строки между /* отладка */ временные, для отладки, затем их нужно будет поудалять. Отладочный код устанавливает значения переменных do_* в соответствии с нажатой кнопкой, чтобы увидеть это на мнемосхеме без подключения ПЛК.
Теперь щёлкните по файлу index.html и убедитесь в работоспособности интерфейса:
Если этого не произошло, то скорее всего по причине несовместимости браузера. Это точно не будет работать под ms explorer-ом. Я пользуюсь мозиллой, но тестировал также Opera и Chrome, всё работало. Если у вас и под ними не работает, то причиной может быть только устаревшая версия браузера. Если же и с новой версией что-то пошло не так, можно попытаться локализовать ошибку в отладчике браузера.
Теперь по содержимому update_screen().
Разработчик библиотеки все обращения к виджетам привёл к одному виду:
Mns.<слой>[].set({<свойство>: <значение>});
Свойства такие:
t - текст
v - числовое значение
f - цвет заливки
Если вам не понятны конструкции вида
do_left|do_right|do_up|do_down ? "green" : "black", то вместо таковых в своей работе можете использовать аналоги:
if(do_left==true || do_right==true || do_up==true || do_down==true ) {
Mns.dyn["led1"].set({f:"green"});else
Mns.dyn["led1"].set({f:"black"});
}Как видите, громоздко получается, поэтому лучше всё-таки продвинуть свой жабаскрипт.
12. Вот теперь веб-проект, можно сказать, готов. Уберите отладочные строки в index.js и загружайте проект изаграфа в контроллер, как это показано здесь. И не забудьте грузить в ПЛК не только tic-код, но и символы приложения, эта информация нужна html-шлюзу таргета для разрешения имён запрашиваемых тегов.
IP-адрес в index.js должен соответствовать вашему ПЛК. Кстати, таргет можно запустить и на ПК!
Если всё пошло по маслу, щёлкнув по index.html вы увидите полностью рабочий проект, пользуйтесь на здоровье!
13. Теперь осталось только запаковать папку web в zip-архив (в архиве должно быть содержимое каталога web, но не каталог web с содержимым!) и присоединить его в проекту nScada как ресурс. Делается это так:
- копируем web.zip в папку проекта nScada (например c:\IsaWin\Apl\nScada)
- открываем Создать->Ресурсы окна программ и вводим
BinaryFile 'web_zip'
BEGIN
AnyTarget
From 'web.zip'
To 'web.zip'
End
- создаём и загружаем проект в ПЛК
Ну вот и всё. Теперь набрав в браузере IP нашего ПЛК, получим то что видели в предыдущем шаге, но уже полностью размещённое в контроллере и доступное с любого другого подключенного устройства сети (включая wifi), будь то планшет или мобильник.
Если для наглядности подключать к задействованным каналам GPIO светодиоды, то желательно делать это через ограничивающие ток резисторы номиналом 200...300 ом.
P.S. Всё вышеописанное доступно в виде похожего готового проекта "nScada", входящего в состав архива библиотеки для ISaGRAF Workbench (архив здесь, описание здесь), т.е. скачав эту библиотеку можно вытащить оттуда данный проект. Веб-составляющие файлы находятся в архиве web.zip директории проекта.
P.S.2 Данный пример ни в коем случае не претендует на какую-либо практическую завершённость, просто здесь показан в действии иструмент, позволяющий разрабатывать вполне полезные вещи на базе вполне доступного железа и софта.
P.S.3 В следующем посте постараюсь изыскать и привести пример более близкий к тематике умного дома.
Arduino & Pi
1.4K постов20.6K подписчиков
Правила сообщества
В нашем сообществе запрещается:
• Добавлять посты не относящиеся к тематике сообщества, либо не несущие какой-либо полезной нагрузки (флуд)
• Задавать очевидные вопросы в виде постов, не воспользовавшись перед этим поиском
• Выкладывать код прямо в посте - используйте для этого сервисы ideone.com, gist.github.com или схожие ресурсы (pastebin запрещен)
• Рассуждать на темы политики
• Нарушать установленные правила Пикабу