Connection of servo-drives to the router. Part 2

In the second part I propose to consider one more interesting thing called - Luci.
The control of servo-drives from the command line is cool, but probably one would like something more graphic. Therefore I propose to control our servo-drives network through the Web interface.
As you know the Luci is responsible for the Web interface in the OpenWrt. Generally luci is a very good example how we can use the whole combination of languages - JavaScript, Ajax, Lua, Html, Css, and Cu - in one project beautifully, intelligently and rationally. The person who wrote the Luci is probably very cool)))

What we need to do? We have not to write the Luci from scratch). We just need to write our smaaaaal module for Luci. How to do it is well written on the official website of Luci. Let's try to do the same.
Let’s create a directory luci-servo-manager in / trunk/build_dir/target-mipsel_uClibc-0. 9. 32/luci-0. 10/applications.
Let’s create a directory named luasrc in it. In luasrc we create the directories view and controller.
Some comments. If you go to “make menuconfig” you can see that Luci has a modular structure. Each module is added to Luci as soon as we select it with <*>. If you remember we've already built Lucy with the module “samba” in one of the earlier sections. So, we have to create this structure to add our module that we are doing now.
In the directory “controller” we create a file named servo-manager.lua with the following content:

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

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

This file has the only one function "index" which adds our module to the structure of the Luci.
{"admin", "ServoManager"} — this is the path in the address line to the page which we are creating.

template("servo-manager/sm_cfg") - it means that for our page display the file sm_cfg.htm is used, this file is in the sub-directory «servo-manager».

"ServoManager" — this is the name of the page in the web interface

50 — this figure is needed to make it possible to place the menu items in a certain order in the web-interface.

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

Now we have to create a html page in the directory /luci-0.10/applications/luci-servo-manager/luasrc/view/servo-manager. This page gives us the possibility to control the servo-drive.

How can we control the servo-drive from the web-interface? I propose to use the element TrackBar for this. This element is a slider box with the fixed-step-size. There is no TrackBar in the common html, therefore we will use a widget from the library "jquery". In jquery this widget is named "slider".

It should look like this:

And here is the page itself on the 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%>

One the one hand the page code is very complicated at first sight. And we can agree, because it uses lua, html, css, javascript, ajax. On the other hand, it is very simple.
The page itself is being created between <%+header%> and <%+footer%>. A schedule is being created composed of 3 columns. In the third column we put the slider, which is implemented in javascript. In the same place the function SetNewState is described, which will be activated whenever the slider will change its value. In this function the ajax makes reference to the server by using of the library xhr. js. By the ajax requesting there is a transfer of parameters: the status flag is passed = 1, and the number of its new position.
The code in Lua located at the top of the file processes the ajax request and is executed on the server. It checks the value of “status”, and if it is equal to 1, then the script WebServoManager.lua is called with its settings.

The script WebServoManager. lua - this the script ServoManager. lua, but with a slight change. It looks like this:

--[[ Подключаем библиотеку для работы с 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%> and <%+footer%> are the files header.htm and footer.htm, which are responsible for the beginning and the end of html file. For example, in the file header.htm we could add a picture for the site header. And in the file footer.htm we could add a picture that would be located at the bottom of all site pages.

Javascript libraries are connected in a common way. In this case <%=resource%> - it's a way like this luci-0. 10/applications/luci-servo-manager/htdocs/luci-static/resources. Therefore, we have to create this folder and put there all the files which are used.

Well, the last thing. We have to add our module to the openwrt. To do this we have to add the following line to the file /trunk/feeds/luci/luci/Makefile:

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

That is all. Let's go into "make menuconfig" and select our module.

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

Let's install:

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. 

Let's open the web-interface and check.

All the necessary files you can find in the section «Download».



To buy an adapter for a servo-drive from us:

Adapter for a servo drive