Anschließen von Servoantrieben an einen Router. Teil II

Im 2. Teil des Artikels schlage ich vor uns noch mit etwas Interessantes bekannt zu machen — mit Luci.

Die Steuerung von Servoantrieben aus der Befehlszeile ist natürlich cool, aber anscheinend möchte man etwas mehr Grafisches. Daher schlage ich vor die Steuerung unseres Servo-Netzwerkes über Web-Interface zu realisieren.

Wie bekannt ist eine Luci für das Web-Interface zuständig. Im Allgemeinen ist Luci ein gutes Beispiel wie man eine Kombination der Sprachen - JavaScript, Ajax, Lua, Html, Css und Сu - in einem Projekt schön, kompetent und rational zu verwenden. Derjenige, der die Luci geschrieben hat, ist anscheinend sehr cool)))

Nun, was haben wir zu machen? Wie brauchen nicht Die Luci von Anfang an zu schreiben ). Es ist notwendig nur einen kleinen eigenen Modul für Luci zu erstellen. Wie das zu machen ist, ist gut auf der ofiziellen Webseite von Luci beschrieben. Versuchen wir dasselbe zu machen.

Erstellen wir das Verzeichnis luci-servo-manager im /trunk/build_dir/target-mipsel_uClibc-0.9.32/luci-0.10/applications.

Erstellen wir darin das Verzeichnis luasrc. Im luasrc erstellen wir die Verzeichnisse view und controller.

Hier sind ein paar Kommentare. Wenn man in make menuconfig geht, kann man sehen, dass Luci eine Modulstruktur hat. Jeder Modul wird zur Luci hinzugefügt, sobald er mit einem <*> vermerkt wird. Wir haben Luci schon einmal mit dem Modul samba in einem der vorherigen Abschnitten zusammengebaut. Also, alle Module haben eine voll identische Struktur, welche die Autoren von Luci festgelegt hatten. Daher um unseren Modul hinzuzufügen, haben wir diese Struktur zu erstellen, was wir gerade machen.

Im Verzeichnis controller erstellen wir die Datei servo-manager.lua mit dem folgenden Inhalt:

module("luci.controller.servo-manager", package.seeall) 

function index() 
        entry({"admin", "ServoManager"}, template("servo-manager/sm_cfg"), "ServoManager", 50).dependent=false 
end

Diese Datei hat eine einzige Funktion mit dem Namen index, welche die Struktur von Luci mit unserem Modul ergänzt.
{"admin", "ServoManager"} — das ist der Pfad in der Adressenzeile bis zur Seite, welche von uns erstellt wird.

template("servo-manager/sm_cfg") - das heißt, dass die Datei sm_cfg.htm für die Darstellung unserer Seite verwendet wird. Diese Datei befindet sich im Sub-Verzeichnis mit dem Namen «servo-manager».

"ServoManager" — das ist der Tabname im Web-Interface.

50 — diese Ziffer wird dafür gebraucht, dass die Menüelemente im Webinterface in einer bestimmten Reihe angeordnet werden können.

module("luci.controller.servo-manager", package.seeall) - erstellt unseren Modul mit dem Namen servo-manager.

Jetzt haben wir eine html-Seite im Verzeichnis /luci-0.10/applications/luci-servo-manager/luasrc/view/servo-manager zu erstellen, welche uns die Möglichkeit gibt einen Servoantrieb zu steuern.

Wie wird der Servoantrieb aus dem Web-Interface gesteuert? Ich schlage vor ein Element mit dem Namen TrackBar dafür zu verwenden. Das ist ein Slider mit einer diskreten Schritt. Es gibt kein Element "TrackBar" in der html-Standardsprache, daher werden wir ein Widget aus der Bibliothek jquery verwenden. In jquery hat dieses Widget den Namen slider.

Es sollte wie folgt aussehen:

Und das ist die Seite selbst in der 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%>

Auf den ersten Blick ist die Pagecode sehr kompliziert. Und man kann damit einverstenden sein, weil es die Sprachen lua, html, css, javascript, ajax verwendet werden. Andererseits ist alles sehr einfach.

Zwischen <%+header%> und <%+footer%> wird die Seite selbst erstellt. Es wird eine Tabelle aus 3 Spalten erstellt. Die 3. Spalte ist die Position für den Slider, welcher in der Sprache javascript geschrieben ist. Die Funktion SetNewState ist auch in der javascript geschrieben. Diese Funktion wird jedes mal aufgerufen, wenn der Slider seinen Wert ändert. Es wird in dieser Funktion einen ajax-Anruf zum Server mittels der Bibliothek xhr.js. erfolgt. Beim ajax-Anruf erfolgt eine Parameterübergabe: es wird der Flagstatus = 1 übergeben, sowie auch die Nummer des Servoantriebes und seinen neuen Zustand.

Die Kode in der Lua, welche sich in der Datei oben befindet,arbeitet den ajax-Anruf ab und wird auf dem Server erfüllt. Es wird der Parameterwert status geprüft. Und wenn dieser gleich 1 ist, erfolgt der Scriptanruf WebServoManager.lua mit den Parametern.

Der Script WebServoManager.lua ist ein Script wie ServoManager.lua, aber bisschen geändert. Er sieht wie folgt aus:

--[[ Подключаем библиотеку для работы с 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%> und <%+footer%> - das sind die Dateien header.htm und footer.htm, welche für den Anfang und das Ende der html-Datei verantwortlich sind. Nehmen wir an, dass man die Datei header.htm mit einem Bild für den Webseitenkopf ergänzen könnte. Und die Datei footer.htm - mit einem Bild, welches unten auf allen Webseiten gesetzt würde.

Die Bibliotheken der javascript werden wie üblich geschrieben. Wobei <%=resource%> - ist solch ein Pfad luci-0.10/applications/luci-servo-manager/htdocs/luci-static/resources. Daher soll dieser Folder erstellt werden, worin alle verwendbaren Dateien zu plazieren sind.

Und das Letzte. OpenWrt ist mit unserem Modul zu ergänzen. Daher ist die folgende Zeile in die Datei /trunk/feeds/luci/luci/Makefile einzufügen:

$(eval $(call application,servo-manager,servo-manager - control servo from web))

Erledigt. Gehen wir in das make menuconfig und wählen unseren Modul aus.

LuCI :
Applications:
<M>luci-app-servo-manager - servo-manager - control servo from web

Beginnen wir mit der Installation:

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. 

Öffnen wir das Web-Interface und prüfen.

Alle notwendigen Dateien sind dem Abschnitt «Herunterladen» zu entnehmen.



Bestellen Sie einen Adapter für einen Servoantrieb bei uns:

Adapter für einen Servoantrieb