Подключение серво-машинок в OpenWrt

Ключевые слова для этой статьи (для тех, кому они не знакомы, статья будет, видимо, не интересна): аналоговая и цифровая серво-машинка, управление сервами, хексапод, дроид, авиамодель, автомодель, робот, в общем всё, что связано с роботостроением.

Все предыдущие статьи имели вводный характер и раскрывали скрытые возможности использования альтернативных функций роутера. Теперь предлагаю перейти к практической части.

Когда я начал увлекаться роботостроением, то одним из постоянных вопросов был — «это чем же управлять сервами?». Для этого нужно сделать специальную плату управления, на каком-то процессоре, проц естественно охота взять помощнее, потому что изначально вовсе и не знаешь, какие задачи будут на его возложены. Поэтому последовательность слов: серво-машинка — роутер — OpenWrt звучит очень заманчиво, хотя некоторое время назад мне бы и в голову это не пришло )))

Так вот, давайте попробуем реализовать следующую структуру, которая представлена на рисунке:

Управлять одной сервой, конечно, хорошо, но нам это не так интересно. Гораздо важнее научиться строить сеть из серво-приводов. В нашем случае в качестве мастера сети серво-машинок будет выступать роутер. Все сервы будут связаны с мастером по Uart. Я понимаю, что I2C является стандартом де-факто, но мне почему-то Uart кажется удобнее, гибче и роднее )))

Для построения сети из серв нам понадобится вот такой адаптер из Usb в Uart (мы его уже использовали в разделе Debug порт):

И на каждую серву будем ставить маленькую платку на mega8 для приёма команд и формирования управляющих импульсов серве вот такого типа:

С аппаратной частью, вроде, закончили. Как же управлять такой сетью серв из OpenWrt? Вариантов реализации, конечно, очень много. Опять же с не давних пор мне стал нравиться скриптовый язык Lua. Знаний его на уровне HelloWorld вполне достаточно для решения нашей задачи.

Алгоритм управления.

1. Мастер (роутер) посылает пакет в серво-сеть (по Uart). Пакет имеет следующую стандартную структуру:
|Длина|Кому|НомерКоманды|ТелоКоманды|Контрольная сумма|
Пример:Передан пакет 5 байт: 5 1 10 179 195

2. Серва, которой предназначен этот пакет, принимает его и отвечает следующим пакетом:
|Длина|ОтКого|НомерКоманды+1|ТелоКоманды|Контрольная сумма|
Принят пакет 5 байт: 5 1 11 0 17
Всё предельно просто.

Программная реализация.

О прошивке для mega8 не буду ничего расписывать, вы можете её скачать с сайта и самостоятельно разобраться. Она очень простая.

Подробно рассмотрим скрипт на луа. Скрипт должен делать следующее:

1. Открыть порт /dev/ttyUSBx

2. Попросить пользователя ввести номер сервы и новое состояние для неё

3. Сформировать пакет с новыми данными и отправить его.

4. Дождаться ответа от сервы.

5. Перейти в режим ожидания ввода новых данных либо выйти из программы, закрыв порт /dev/ttyUSBx

На луа эта задача реализуется очень просто. Собственно поэтому мне и захотелось попробовать использовать его для этой цели.

Единственным неприятным местом в этом процессе является отсутствие в OpenWrt библиотеки луа для работы c последовательным портом. Саму библиотеку можно скачать вот отсюда. Давайте попробуем добавить её в состав OpenWrt.

Как добавить новый пакет в состав OpenWrt, мы рассматривали ранее, но думаю, можно еще раз повторить, чтобы мне не забыть тоже )))

1. Создаем в директории /trunk/package директорию luars232. В ней создаем makefile, взяв за основу любой makefile из директории /trunk/package. Наш makefile выглядит следующим образом:

#
# Copyright (C) 2007-2009 OpenWrt.org
#
# This is free software, licensed under the GNU General Public License v2.
# See /LICENSE for more information.
#

include $(TOPDIR)/rules.mk

PKG_NAME:=luars232
PKG_VERSION:=1.0.1

PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_FIXUP:=libtool

include $(INCLUDE_DIR)/package.mk

PKG_INSTALL=1

define Package/luars232
  SECTION:=libs
  CATEGORY:=Libraries
  TITLE:=Lua lib for serial communication over rs-232
endef

define Package/luars232/description
	Lua lib for serial communication over rs-232
endef

TARGET_CFLAGS += -DLUA_USE_LINUX $(FPIC)

define Build/Configure
        $(call Build/Configure/Default, \
        CFLAGS="$(TARGET_CFLAGS)" \
        )
endef

define Build/InstallDev
	$(INSTALL_DIR) $(1)/usr/include
	$(CP) $(PKG_INSTALL_DIR)/usr/include/librs232 $(1)/usr/include/
	$(INSTALL_DIR) $(1)/usr/lib/lua
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/librs232.{a,so*} $(1)/usr/lib/lua/
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/luars232.{a,so*} $(1)/usr/lib/lua/	
	$(INSTALL_DIR) $(1)/usr/lib/pkgconfig
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/pkgconfig/librs232.pc $(1)/usr/lib/pkgconfig/
endef

define Package/luars232/install
	$(INSTALL_DIR) $(1)/usr/lib/lua
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/librs232.so* $(1)/usr/lib/
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/luars232.so* $(1)/usr/lib/	
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/librs232.so* $(1)/usr/lib/lua
	$(CP) $(PKG_INSTALL_DIR)/usr/lib/luars232.so* $(1)/usr/lib/lua	
endef

$(eval $(call BuildPackage,luars232))

2. Зайдем в make menuconfig, отметим нужные модули и затем запустим make V=99.
LuCI :
Collections:
<M> luci
Libraries:
<M> luars232 (Lua lib for serial communication over rs-232)

Вроде бы всё собралось, но библиотека в luars232/bindings/lua/.libs/luars232.so не собралась. Смотрим лог сборки в момент выполнения configure:

checking whether stripping libraries is possible... yes 
checking if libtool supports shared libraries... yes 
checking whether to build shared libraries... yes 
checking whether to build static libraries... yes 
checking for pkg-config... pkg-config 
checking for Lua headers and librairies with pkg-config... 
checking for Lua compiling and linking... no 
configure: WARNING: 
*** Lua (>=5.0) headers and/or librairies couldn't be found in your system. 
*** Try to install liblua, liblualib and liblua-dev with your software package manager. 
*** librs232 will be built without Lua bindings library. 
./configure: line 11780: 1: command not found 
configure: creating ./config.status 
config.status: creating Makefile 
config.status: creating src/Makefile 
config.status: creating bindings/Makefile

Как видим, configure не нашел луа headers and/or librairies. Для начала проверим их наличие. Если подняться чуть выше по логу, то можно увидеть путь, где они лежат.
headers:"-I/trunk/staging_dir/target-mipsel_uClibc-0.9.32/usr/include"
там должны быть файлы: lua.h, luaconf.h, lualib.h
librairies:"-L/trunk/staging_dir/target-mipsel_uClibc-0.9.32/usr/lib"
там должны быть файлы:liblua.a, liblua.so, liblua.so.5.1.4, liblualib.so.

У меня эти файлы есть, но тем не менее configure их не увидел.

Придется разбираться, почему configure не находит их, хотя они есть и пути все прописываются. Поскольку configure генерится из configure.in, попробуем поискать в нём причину ошибки....

В общем, нашел следующую строчку в файле configure.in:

LUA_TEST=`LT=luatest.c ; echo "#include........
if test "x$LUA_TEST" != "x0" ; then
        AC_MSG_RESULT(no)
        AC_MSG_WARN([

Честно говоря, мне трудно понять, что тут происходит )))... Но вроде, если в двух словах, то для проверки создается некий файл и затем он пытается собраться и исполниться. И типа, если он собрался и исполнился, то это означает наличие необходимых хедеров и библиотек. Поэтому мне не очень понятно, как файл может исполниться на компьютере, если библиотека луа собрана кросскомпилятором под mips. Хотя, наверняка, я и ошибаюсь ))). Но, тем не менее, предлагаю сделать инверсную операцию для этого if.

Выглядеть должно так:
if !(test "x$LUA_TEST" != "x0") ; then

Снова запускаем make V=99 и смотрим лог сборки:

checking for Lua headers and librairies with pkg-config... 
checking for Lua compiling and linking... yes 
./configure: line 11780: 1: command not found 
configure: creating ./config.status 
config.status: creating Makefile 
config.status: creating src/Makefile 
config.status: creating bindings/Makefile 
config.status: creating bindings/lua/Makefile 
config.status: creating include/Makefile 
config.status: creating include/librs232/Makefile 
config.status: creating src/librs232.pc 
config.status: executing depfiles commands 
config.status: executing libtool commands 
configure: WARNING: unrecognized options: --disable-nls 

 librs232 version 0.0.1 configured successfully.

Как видим, теперь всё Ок и библиотека собралась. Это мой рабочий архив (все остальные файлы можно взять в разделе «скачать»).

Перед написанием скрипта для большего запудривания мозгов придется сделать еще пару важных дел. ))

Давайте посмотрим на файл примера (doc/example.lua) из библиотеки. А именно, вот на эти строки:

-- write without timeout 
err, len_written = p:write("test") 
assert(e == rs232.RS232_ERR_NOERROR)

Как видим, в функцию write передается строка. И мне не очень понятно, как передать в эту функцию протокол, который был заложен на этапе проектирования. Наверняка это можно как-то реализовать, но, к сожалению, я не знаю, как! ((( В принципе, можно изменить протокол и работать со строками, но это вроде какой-то изврат. Хотя может и нет.... В общем, появилась проблема там, где я просто не ожидал!

Для меня самым простым решением оказалось изменение в библиотеке функции write, чтобы вместо строк она принимала массив данных.

Как это сделать? В файле bindings/lua/luars232.c находится функция static int lua_port_write(lua_State *L), которая отрабатывает метод write из lua. Внесем следующие изменения в неё:

/*
 * error, written_len = port:write(data [, timeout_ms])
 */
static int lua_port_write(lua_State *L)
{
	int ret = 0;
	int argc = 0;
	//unsigned int timeout = 0;
	unsigned int wlen = 0;
	//size_t len = 0;
	//const char *data;
	struct rs232_port_t *p = NULL;

	p = (struct rs232_port_t*) luaL_checkudata(L, 1, MODULE_NAMESPACE);
	lua_remove(L, 1);

	if (p == NULL || !rs232_port_open(p)) {
		lua_pushinteger(L, RS232_ERR_PORT_CLOSED);
		lua_pushinteger(L, 0);
		return 2;
	}

	argc = lua_gettop(L);
	
	/* моя правка. Из луа должна передаваться таблица, а не строка.
	 * Здесь происходит разбор элементов таблицы */
	switch (argc) {
		case 1:
			{
				/* заведем две переменные. длина массива и сам массив, куда
				 * будем складывать элементы таблицы из луа */
				unsigned char array_len=0, lua_array[20]={0};
				
				lua_pushnil(L);
				while (lua_next(L, argc) != 0)
				{ 
					lua_array [array_len] = (int)(lua_tonumber(L,-1));
					//printf ("value=%d\n",lua_array [array_len]);					
					array_len++;
					lua_pop(L, 1); 
				}
				ret = rs232_write(p, lua_array, array_len, &wlen);	
			}
		break;
					
		default:
			lua_pushinteger(L, RS232_ERR_CONFIG);
			lua_pushinteger(L, 0);
		return 2;		
	}

	lua_pushinteger(L, ret);
	lua_pushinteger(L, wlen);
	return 2;
}

Оказалось, всё не так страшно. Опять же, спасибо автору библиотеки.

Ну и еще одно небольшое отступление, в большей степени делаю для себя, чтобы не забыть... Собрав библиотеку, бывает полезно протестировать её, т.е. проверить программный интерфейс динамической библиотеки:

1. Проверим реакцию dlopen с помощью маленькой программки:

#include 
#include 

int main (int argc, char **argv){
		void *handle;
		char *error;

		printf ("str=%s\n",argv[1]);
		handle = dlopen (argv[1], RTLD_LAZY);
		if (!handle) {
				fputs (dlerror(), stderr);
				printf ("\n");
				return (1);
		}
		printf ("handle=%p\n",handle);
		dlclose (handle);
}

root@OpenWrt:/# /home/test_dlopen /usr/lib/luars232.so 
str=/usr/lib/luars232.so 
handle=0x9dc170 

Вывод — библиотека подгрузилась.

2. Проверим зависимости:

$ ldd luars232.so 
	linux-gate.so.1 =>  (0x00d58000) 
	librs232.so.0 => /usr/local/lib/librs232.so.0 (0x00ed9000) 
	liblua5.1.so.0 => /usr/lib/liblua5.1.so.0 (0x00a34000) 
	libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x00260000) 
	libm.so.6 => /lib/tls/i686/cmov/libm.so.6 (0x00960000) 
	libdl.so.2 => /lib/tls/i686/cmov/libdl.so.2 (0x00110000) 
	/lib/ld-linux.so.2 (0x0071f000) 

	$ ldd librs232.so 
	linux-gate.so.1 =>  (0x00375000) 
	libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x008f1000) 
	/lib/ld-linux.so.2 (0x00f76000) 

Вывод — зависимости удовлетворены.

Всё!!! Теперь смело можем писать луа скрипт для управления нашей серво-сетью.

Прошло некоторое время, и скрипт готов ))). Собственно он очень простой и с комментариями практически в каждой строке:

--[[ Подключаем библиотеку для работы с com портом. Можно было бы
			написать просто require("luars232"), то тогда обращение бы шло
			по имени модуля luars232.RS232_BAUD_115200 ]]
rs232 = require("luars232")

local SET_NEW_STATE					=		10
local SET_NEW_STATE_ANSWER	=		11

-- Определяем имя порта
port_name = "/dev/ttyUSB1"

-- определим канал для вывода сообщений
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|Тело|Кс| ]]

-- бесконечный цикл		
while true do		
	local input_error
	
	repeat
		out:write (string.format("Введите номер сервы(0-10) и положение(0-180) или q для выхода\n"))
		out:write (string.format("Пример:0 90 (выставить серву №0 в положение 90 градусов)\n"))

		-- Считаем данные, которые ввел пользователь
		local keyboard_input = user_in:read()

		-- Разберем данные, которые ввел пользователь
		input_args = {}
		local cnt=0
		for next_word in string.gmatch(keyboard_input, "%w+") do
			-- поместим в таблицу введеный символ
			table.insert (input_args,next_word)
			cnt = cnt + 1

			if (cnt >= 2) then
				break
			end
		end
	
		-- проверим корректность введеных данных
		if cnt < 2 or tonumber(input_args[1]) == nil or tonumber(input_args[2]) == nil then
			if input_args[1] == "q" then
				input_error = 0
				exit_flag   = 1;	-- выставим флаг, что пользователь хочет выйти из проги
			else
				out:write (string.format("Ошибка ввода - нужно повторить ввод!\n"))
				input_error = 1
			end
		else
			exit_flag   = 0
			input_error = 0
		end
		
	until input_error == 0
	
	if (exit_flag == 1) then
		out:write (string.format("Выходим из программы...\n"))
		break;
	end
	
	--[[ сформируем буфер для отправки
		Запрос:                                                
		|Длина|Кому|№команды|Тело|Кс| ]]
		
	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"))

end

-- close
assert(p:close() == rs232.RS232_ERR_NOERROR)

Загружаемся и устанавливаем пакеты.

root@OpenWrt:/# opkg install luci luars232 
Installing luci (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci_0.10+svn6982-1_brcm47xx.ipk. 
Installing uhttpd (22) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/uhttpd_22_brcm47xx.ipk. 
Installing luci-mod-admin-full (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-mod-admin-full_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-mod-admin-core (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-mod-admin-core_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-web (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-web_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-core (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-core_0.10+svn6982-1_brcm47xx.ipk. 
Installing lua (5.1.4-7) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/lua_5.1.4-7_brcm47xx.ipk. 
Installing liblua (5.1.4-7) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/liblua_5.1.4-7_brcm47xx.ipk. 
Installing libuci-lua (2011-03-27.2-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/libuci-lua_2011-03-27.2-1_brcm47xx.ipk. 
Installing luci-lib-sys (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-sys_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-nixio (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-nixio_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-sgi-cgi (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-sgi-cgi_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-lmo (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-lmo_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-i18n-english (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-i18n-english_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-lib-ipkg (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-lib-ipkg_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-theme-openwrt (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-theme-openwrt_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-theme-base (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-theme-base_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-app-firewall (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-app-firewall_0.10+svn6982-1_brcm47xx.ipk. 
Installing luci-app-initmgr (0.10+svn6982-1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luci-app-initmgr_0.10+svn6982-1_brcm47xx.ipk. 
Installing libiwinfo (13) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/libiwinfo_13_brcm47xx.ipk. 
Installing luars232 (1.0.1) to root... 
Downloading ftp://ftp:ftp@192.168.0.9/luars232_1.0.1_brcm47xx.ipk. 
Configuring luci-lib-sys. 
Configuring liblua. 
Configuring libuci-lua. 
Configuring lua. 
Configuring luci-lib-core. 
Configuring luci-lib-nixio. 
Configuring luci-sgi-cgi. 
Configuring luci-lib-lmo. 
Configuring luci-lib-web. 
Configuring luci-i18n-english. 
Configuring luci-mod-admin-core. 
Configuring libiwinfo. 
Configuring luci-theme-base. 
Configuring luci-theme-openwrt. 
Configuring luci-app-firewall. 
Configuring luci-lib-ipkg. 
Configuring luci-mod-admin-full. 
Configuring luars232. 
Configuring uhttpd. 
Configuring luci-app-initmgr. 
Configuring luci. 

Попробуем протестировать. Запускаем его:

root@OpenWrt:/# lua /home/ServoManager/ServoManager.lua 
OK, port open with values 'device: /dev/ttyUSB0, baud: 115200, data bits: 8, parity: none, stop bits: 1, flow control: off' 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
1 179 
>Передан пакет 5 байт: 5 1 10 179 195 
<Принят пакет 5 байт: 5 1 11 0 17 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
^[[A 
Ошибка ввода - нужно повторить ввод! 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
1 10 
>Передан пакет 5 байт: 5 1 10 10 26 
<Принят пакет 5 байт: 5 1 11 0 17 
Введите номер сервы(0-10) и положение(0-180) или q для выхода 
Пример:0 90 (выставить серву №0 в положение 90 градусов) 
q 
Выходим из программы... 
root@OpenWrt:/# 

Пашет и это круто!



Купить сервы у нас:

Сервы