Подключение серво-машинок к роутеру. Часть 2
Во второй части предлагаю рассмотреть еще одну интересную вещь под названием — Luci.
Управление сервами из командной строки - это конеш круто, но, наверное, хотелось бы чего-то более графического. Поэтому предлагаю сделать управление нашей серво-сетью через веб-интерфейс.
Как известно, в OpenWrt за веб-интерфейс отвечает некая (почему-то так охота, чтоб это была «некая») Luci. Вообще luci - это очень хороший пример,, как красиво, грамотно и рационально можно использовать в одном проекте целую комбинацию языков — JavaScript, Ajax, Lua, Html, Css и Си. Тот кто написал Luci, наверно, очень крут )))
Что же нужно сделать нам? Писать Luci с нуля нам не надо ). Нам нужно всего лишь написать свой мааааленький модулёк для люси. Как это сделать, хорошо написано на официальном сайте Luci. Попробуем это повторить.
Создаем директорию luci-servo-manager в /trunk/build_dir/target-mipsel_uClibc-0.9.32/luci-0.10/applications.
Создаем в ней директорию luasrc. В luasrc создаем директории view и controller.
Немного комментариев. Если зайти в make menuconfig, то можно увидеть, что люси имеет модульную структуру. Каждый модуль добавляется в люси, как только мы поставим <*> напротив его. Если вы помните, мы уже собирали люси с модулем samba в одном из предыдущих разделов. Так вот, все модули имеют абсолютно одинаковую структуру, которую заложили авторы люси. Поэтому, чтобы добавить свой модуль, мы должны создать эту структуру, что и делаем сейчас.
В директории controller создаем файл servo-manager.lua следующего содержания:
module("luci.controller.servo-manager", package.seeall) function index() entry({"admin", "ServoManager"}, template("servo-manager/sm_cfg"), "ServoManager", 50).dependent=false end |
Этот файл имеет единственную функцию index, которая добавит наш модуль в структуру люси.
{"admin", "ServoManager"} — это путь в строке адреса до страницы, которую мы создаем.
template("servo-manager/sm_cfg") - означает, что для отображения нашей страницы используется файл sm_cfg.htm, который находится в поддиректории с именем «servo-manager».
"ServoManager" — это имя вкладки в веб интерфейсе
50 — это число нужно, чтобы элементы меню в веб интерфейсе можно было располагать в определенном порядке.
module("luci.controller.servo-manager", package.seeall) - создает наш модуль с именем servo-manager.
Теперь в директории /luci-0.10/applications/luci-servo-manager/luasrc/view/servo-manager нам нужно создать html страничку, на которой будет возможность управлять сервой.
Как управлять сервой из веб-интерфейса? Для этого предлагаю использовать элемент TrackBar — это бегунок с дискретным шагом. Элемента TrackBar нет в стандартном html, поэтому будем использовать виджет из библиотеки jquery. В jquery данный виджет называется slider.
А это сама страничка на html:
<%# страница servo-manager -%> <% -- Код на lua. Здесь происходит обработка ajax запроса. Т.е. этот код -- выполняется на сервере. -- проверим, что параметр "status" равен 1 if luci.http.formvalue("status") == "1" then -- отладка. использовал её при запуске люси на хосте -- io.write ("получен ajax запрос","\n") -- отрабатываем изменение положения -- отладка. использовал её при запуске люси на хосте -- print (luci.http.formvalue("serv_num")) -- print(luci.http.formvalue("state")) -- сформируем командную строку str = "lua /home/WebServoManager.lua".." "..(luci.http.formvalue("serv_num")).." "..(luci.http.formvalue("state")) -- выполним команду os.execute (str) -- отладка -- io.output (io.open ("/home/temp.txt", "w")) -- io.output (io.open(("/home/temp.txt","a")) -- io.write (str) -- io.close() local temp_value = 100; local rv = { temp_value = temp_value, } -- сформируем данные для отправки клиенту и отправим luci.http.prepare_content("application/json") luci.http.write_json(rv) return end -- local system, model = luci.sys.sysinfo() -%> <%+header%> <fieldset class="cbi-section"> <div class="cbi-map"> <link rel="stylesheet" href="<%=resource%>/jslider.css" type="text/css" /> <script type="text/javascript" src="<%=resource%>/jquery-1.4.2.js"></script> <script type="text/javascript" src="<%=resource%>/jquery.dependClass.js"></script> <script type="text/javascript" src="<%=resource%>/jquery.slider-min.js"></script> <style type="text/css" media="screen"> td {border:2px solid LightSlateGray;text-align:center;} th {border:2px solid LightSlateGray;cellspacing:0px;text-align:center;} table {table-layout:auto; width:1000px; border:2px solid LightSlateGray;border-collapse:collapse} .layout-slider { margin-top:20px;margin-bottom:20px; width: 100%;} </style> <fieldset class="cbi-section"> <legend><%:Servo-Manager%></legend> <table> <tr> <th style="width:50px"><%:Номер сервы%></th> <th style="width:120px"><%:Рисунок сервы%></th> <th><%:Положение Сервы%></th> </tr> <tr> <td> <span style="background-color:#FFFFFF; border:1px solid #CCCCCC; padding:2px">1</span> </td> <td> <div><img src="<%=resource%>/servo1.jpg" width="100%"></img></div> </td> <td> <div> <div class="layout-slider"> <input id="SliderSingle" type="slider" name="rotate" value="20" /> </div> <script type="text/javascript" charset="utf-8"> // создаем объект jQuery - слайдер jQuery("#SliderSingle").slider({ from: 0, to: 180, step: 1, round: 0, dimension: ' grad', skin: "round", callback: CallOnRelease, }); var iwxhr = new XHR(); /* ajax запрос к серверу. Функция SetNewState будет вызвана при изменении положения слайдера. В этой функции будет осуществлен ajax запрос к серверу. Параметры для сервера будут переданы следующие: status:1,serv_num:serv_num,state:new_state */ function SetNewState(serv_num,new_state) { iwxhr.get('<%=REQUEST_URI%>', { status:1,serv_num:serv_num,state:new_state }, function(x, info) { } ) } // обработка события - "изменение положения слайдера" function CallOnRelease() { SetNewState(1,jQuery("#SliderSingle").slider('value')) // отладка в консоль firebug console.log ("Change_handle=",jQuery("#SliderSingle").slider('value')) } // состояние по умолчанию jQuery("#SliderSingle").slider('value',90) // отладка в консоль firebug console.log("slider-state=",jQuery("#SliderSingle").slider('value')) </script> </div> </td> </tr> </table> </fieldset> </div> </fieldset> <%+footer%> |
На первый взгляд код странички очень сложен. И с этим можно согласиться, потому что здесь используется lua, html, css, javascript, ajax. С другой стороны, всё очень просто.
Между <%+header%> и <%+footer%> формируется сама страничка. Создается табличка из трех столбцов. В третьем столбце помещаем слайдер, который реализован на javascript. Там же описана функция SetNewState, которая будет вызываться всякий раз, когда слайдер изменит своё значение. В этой функции происходит ajax обращение к серверу с помощью библиотеки xhr.js. При ajax запросе, происходит передача параметров: передается флаг статус = 1, номер сервы и её новое положение.
Код на луа, который располагается вверху файла, как раз занимается обработкой ajax запроса и выполняется на сервере. Проверяется значение параметра status, и если оно равно 1, то происходит вызов скрипта WebServoManager.lua с параметрами.
Скрипт WebServoManager.lua - это скрипт ServoManager.lua, но немного изменен. Вот так он выгладит:
--[[ Подключаем библиотеку для работы с com портом. Можно было бы написать просто require("luars232"), то тогда обращение бы шло по имени модуля luars232.RS232_BAUD_115200 ]] rs232 = require("luars232") local SET_NEW_STATE = 10 local SET_NEW_STATE_ANSWER = 11 -- Определяем имя порта port_name = "/dev/ttyUSB0" -- определим канал для вывода сообщений local out = io.stderr -- определим канал для ввода данных с клавиатуры local user_in = io.stdin --[[ Вызываем функцию open для открытия порта.Эта функция возвращает два значения, которые присваиваются переменным e и p e - тип ошибки, p - дескриптор порта ]] local e, p = rs232.open(port_name) -- если ошибка открытия if e ~= rs232.RS232_ERR_NOERROR then -- handle error out:write(string.format("can't open serial port '%s', error: '%s'\n", port_name, rs232.error_tostring(e))) return end -- Настраиваем порт, вызывая соотвествующие методы. Методы описаны в luars232.c assert(p:set_baud_rate(rs232.RS232_BAUD_115200) == rs232.RS232_ERR_NOERROR) assert(p:set_data_bits(rs232.RS232_DATA_8) == rs232.RS232_ERR_NOERROR) assert(p:set_parity(rs232.RS232_PARITY_NONE) == rs232.RS232_ERR_NOERROR) assert(p:set_stop_bits(rs232.RS232_STOP_1) == rs232.RS232_ERR_NOERROR) assert(p:set_flow_control(rs232.RS232_FLOW_OFF) == rs232.RS232_ERR_NOERROR) out:write(string.format("OK, port open with values '%s'\n", tostring(p))) --[[ Порт настроен. Можно осуществлять обмен данными. Алгоритм управления сервой следующий: 1. Мастер посылает пакет, в котором указывается номер сервы и то состояние, в которое она должна перейти. 2. Серва отвечает мастеру, что пакет получен и команда исполнена Протокол общения: Запрос: |Длина|Кому|№команды|Тело|Кс| Ответ: |Длина|ОтКого|№команды+1|Тело|Кс| ]] --[[ при вызове скрипта были переданы параметры. Параметр 1 - номер сервы. Параметр 2 - положение сервы. ]] input_args = {} input_args[1] = arg [1] input_args[2] = arg [2] --[[ сформируем буфер для отправки Запрос: |Длина|Кому|№команды|Тело|Кс| ]] local tx_buf={5} table.insert (tx_buf,input_args[1]) table.insert (tx_buf,SET_NEW_STATE) table.insert (tx_buf,input_args[2]) table.insert (tx_buf,(tx_buf[1]+tx_buf[2]+tx_buf[3]+tx_buf[4])%0xff) -- Посылаем пакет err, len_written = p:write(tx_buf) assert(e == rs232.RS232_ERR_NOERROR) -- выведем на экран то, что послали out:write (string.format(">Передан пакет %d байт:",len_written)) for i=1,len_written,1 do out:write(string.format(" %d",tx_buf[i])) end out:write(string.format("\n")) -- Ждем ответа -- предположим, что максимальный пакет не может быть больше 100 байт local read_len = 100 -- ждать ответа будем 100мс local timeout = 100 -- запускаем функцию чтения данных из порта local err, data_read, size = p:read(read_len, timeout,1) assert(e == rs232.RS232_ERR_NOERROR) -- выведем на экран то, что получили через уарт out:write(string.format("<Принят пакет %d байт:",size)) for i=1,size,1 do out:write(string.format(" %d",string.byte(data_read,i))) end out:write(string.format("\n")) -- close assert(p:close() == rs232.RS232_ERR_NOERROR) |
<%+header%> и <%+footer%> - это файлы header.htm и footer.htm, которые отвечают за начало и конец html файла. Допустим, в файл header.htm можно было бы добавить рисунок для шапки сайта. А в файл footer.htm рисунок, который бы располагался внизу на всех страницах сайта.
Библиотеки javascript подключаются стандартным способом. При этом <%=resource%> - это вот такой путь luci-0.10/applications/luci-servo-manager/htdocs/luci-static/resources. Поэтому нужно создать эту папку и поместить туда все файлы, которые используются.
Ну и последнее. Нужно добавить наш модуль в состав openwrt. Для этого в файл /trunk/feeds/luci/luci/Makefile нужно добавить вот такую строчку:
$(eval $(call application,servo-manager,servo-manager - control servo from web)) |
Всё. Заходим в make menuconfig и отмечаем наш модуль.
LuCI :
Applications:
<M>luci-app-servo-manager - servo-manager - control servo from web
Устанавливем:
root@OpenWrt:/# opkg install luci-app-servo-manager Installing luci-app-servo-manager (0.10+svn6982-1) to root... Downloading ftp://ftp:ftp@192.168.0.9/luci-app-servo-manager_0.10+svn6982-1_brcm47xx.ipk. Configuring luci-app-servo-manager. |
Открываем веб-интерфейс и проверяем.
Все необходимые файлы можно взять в разделе «скачать».
Купить адаптер для серво-машинки у нас: