Подключение серво-машинок к роутеру. Часть 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
|
{"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%>
|
Между <%+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)
|
Библиотеки 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))
|
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.
|
Все необходимые файлы можно взять в разделе «скачать».
Купить адаптер для серво-машинки у нас:
Адаптер для серво-машинки
Александр, копаюсь в Luci, хочу сделать свое меню.
ОтветитьУдалитьНо у меня при вызове make menuconfig не появляется возможность выбора в меню luci-app-servo-manager. Подскажите с чем это связано, может предварительно требуется подача какой-либо команды?