Lua в робототехнике

lua_logo

 
 
 

Сегодня хотелось бы поговорить о том, как можно полезно использовать язык Lua при создании роботов. Lua - интерпретируемый язык, который легко интегрируется с кодом на С/С++, позволяющий из себя вызывать функции из библиотеки на С. Язык содержит кучу полезных штучек, прост в освоении и давно используется разработчиками компьютерных игр в их нелегком ремесле, исходные коды интерпретатора и библиотека языка доступны по лицензии MIT, легко скачать скомпилированный интерпретатор и библиотеку. Начнем с постановки проблем:

 
1) необходимо удобно хранить настройки
2) сокращение времени отладки действий робота


 
 

По первому пункту. Настройки можно хранить в текстовом файле со своим собственным форматом; плюсы: файл - это просто, минусы: сложно расширяется и вообще дело это неблагородное свои форматы, в лучшем случае получиться большой ненадежный велосипед, лучше не тратить время на его изобретение. Можно также хранить настройки в XML-документе; из плюсов: XML - это удобно, хорошо расширяется; в противовес плюсам хочется противопоставить необходимость разработки парсера XML-документа, пусть даже на базе готовых библиотек, будь то Qt-XML или что-то другое. XML больше подходит для хранения больших документов устоявшегося формата. Используя Lua, можно удобно хранить настройки и забыть про головную боль. Хочется добавить, что когда-то автор статьи хранил настройки прямо в коде программы в .h файлах и тратил кучу времени на перекомпиляцию после изменения одного несчастного коэффициента. Мораль истории такова, что настройки нужно хранить в таком месте, где их можно было легко менять без потери времени на компиляцию основной программы и быстро доставать из приложения.

По второму пункту. Во время участия в соревнованиях Eurobot или при другой задаче появляется проблема отладить определенную последовательность действий робота или программу, по которой он работает - это может быть траектория или подпрограмма для выполнения составного действия. Если после каждого исправления нескольких строчек в коде функции, отвечающей за основные действия робота, вам приходится ждать полминуты или больше на перекомпиляцию и перелинковку проекта, то пора сказать:"ХВАТИТ ЭТО ТЕРПЕТЬ"!!! Из Lua скрипта можно вызывать подготовленные функции из библиотеки на языке С - это позволяет писать логику работы так же, как и в коде на С/С++, но без потери времени на пересборку после редактирования часто меняющейся логики.

Далее поговорим о том, как использовать это маленькое чудо Lua. Начнем с сохранения файлов настроек. Опишем файл config.lua, содержащий настройки:

my_name = "evgeny"
my_os   = "linux"
my_arc  = "amd64"

Чтобы из нашей С/С++ программы вытащить эти настройки, необходимо выполнить следующие действия:

void get_config()
{
    lua_State * L;
    L = luaL_newstate();
    luaL_openlibs(L);
 
    load_lua_file(L, (PARENT_DIRECTORY_STRING + "config.lua").c_str());
    check_execution(L);
 
    lua_getglobal(L, "my_arc");
    lua_getglobal(L, "my_os");
    lua_getglobal(L, "my_name");
 
    std::cout << "my_name: " << lua_tostring(L, -1) << std::endl;
    std::cout << "my_os  : " << lua_tostring(L, -2) << std::endl;
    std::cout << "my_arc : " << lua_tostring(L, -3) << std::endl;
 
    lua_close(L);  
}

lua_getglobal() достает запрашиваемое значение из глобальных переменных скрипта и кладет его в Lua стек; lua_tostring() вынимает из стека значение в виде строки;
load_lua_file() и check_execution() - служебные функции загрузки скрипта и его выполнения, которые будут описаны ниже.

Результат выполнения функции get_config():

my_name: evgeny
my_os  : linux
my_arc : amd64

Перейдем вызову библиотечного С-кода из скрипта. Чтобы из Lua кода вызвать С-функции, необходимо выполнить следующие действия. В файле call_c.lua запишем:

cap = require 'libcapabilities'
 
cap.fun()                                       -- example of a function call with no parameters
cap.fun_fix('I love Lua!', 100, "la-la-la")     -- example of a function call with no parameters
cap.fun_var(true, 300, "web", 'bob', "robot")   -- example of a function call with a variable number of parameters
 
print()
 
print("call cap.fun_return_num(): ", cap.fun_return_num())          -- example of a function call returns the number
print("call cap.fun_return_string(): ", cap.fun_return_string())    -- example of a function call returns the string
print("call cap.fun_return_set(): ", cap.fun_return_set())          -- example of a function call returns a set of values

Вызываемые внутри Lua скрипта функции: fun(), fun_fix(), fun_var(), fun_return_num(), fun_return_string(), fun_return_set() подгружаются из библиотеки libcapabilities командой require 'libcapabilities'. Содержимое файла lib.cpp этой библиотеки:

#include <string.h>
#include <iostream>
 
#include "lua.hpp"
 
#define LIB_EXPORT extern "C" __attribute__((visibility("default")))
 
static int fun(lua_State* L)
{
    (void)L;
    std::cout << "* fun *" << std::endl;
    return 0;
}
 
static int fun_fix(lua_State* L)
{
    const char* text1 = lua_tostring(L, 1);
    const int num     = lua_tonumber(L, 2);
    const char* text2 = lua_tostring(L, 3);
 
    std::cout << "* fun_fix *" << std::endl;
    std::cout << "text1  -  " << text1 << std::endl;
    std::cout << "num    -  " << num << std::endl;
    std::cout << "text2  -  " << text2 << std::endl;
 
    return 0;
}
 
static int fun_var(lua_State* L)
{
    const int count = lua_gettop(L);
    std::cout << "* fun_var *" <<  std::endl << "arguments count: " << count << std::endl;
    std::cout.setf(std::ios_base::left, std::ios_base::adjustfield);
    for(int i = 1; count >= i; ++i) {
        std::cout << "fun_var argument is " << i << " | ";
        int type = lua_type(L, i);
        std::cout << "type : ";
        std::cout.width(10);
        std::cout << lua_typename(L, type) << "  | value:  ";
        std::cout.width(10);
        switch(type) {
        case 1:
            std::cout << lua_toboolean(L, i);
            break;
        case 3:
            std::cout << lua_tonumber(L, i);
            break;
        case 4:
            std::cout << lua_tostring(L, i);
            break;
        default:
            std::cout << "invalid argument type";
            break;
        }
        std::cout << std::endl;
    }
    return 0;
}
 
static int fun_return_num(lua_State* L)
{
    lua_pushnumber(L, 777);
    return 1;
}
 
static int fun_return_string(lua_State* L)
{
    lua_pushstring(L, "hello, my name C");
    return 1;
}
 
static int fun_return_set(lua_State* L)
{
    lua_pushstring(L, "start");
    for(int i = 100; i < 103; ++i) {
        lua_pushnumber(L, i);
    }
    lua_pushstring(L, "end");
    return 5;
}
 
static const luaL_Reg lua_possible[] = 
{
    {"fun", fun},
    {"fun_fix", fun_fix},
    {"fun_var", fun_var},
    {"fun_return_num", fun_return_num},
    {"fun_return_string", fun_return_string},
    {"fun_return_set", fun_return_set},
    {0, 0}
};
 
LIB_EXPORT int luaopen_libcapabilities(lua_State* L)
{
    luaL_register(L, "libcapabilities", lua_possible);
    return 0;
}

Чтобы из С вызвать наш Lua скрипт, использующий методы библиотеки libcapabilities, выполним следующие действия:

void call_function_from_c()
{
    lua_State * L;
    L = luaL_newstate();
    luaL_openlibs(L);
 
    load_lua_file(L, (PARENT_DIRECTORY_STRING + "call_c.lua").c_str());
    check_execution(L);
 
    lua_close(L);  
}

Результат выполнения функции call_function_from_c():

* fun *
* fun_fix *
text1  -  I love Lua!
num    -  100
text2  -  la-la-la
* fun_var *
arguments count: 5
fun_var argument is 1 | type : boolean     | value:  1         
fun_var argument is 2 | type : number      | value:  300       
fun_var argument is 3 | type : string      | value:  web       
fun_var argument is 4 | type : string      | value:  bob       
fun_var argument is 5 | type : string      | value:  robot     
 
call cap.fun_return_num():      777
call cap.fun_return_string():   hello, my name C
call cap.fun_return_set():      start   100     101     102     end

Также хотелось обсудить возможность перезагрузки Lua скрипта в процессе работы программы, т.е. перезагрузка настроек или подпрограммы на Lua. Короткий пример перезагрузки Lua скрипта. Содержимое файла cycle.lua:

print("lua", 1)
print("lua", 2)
print("lua", 3)

Пример программы на С/С++, перезагружающей наш cycle.lua. Чтобы убедится, что все работает как нужно, измените несколько раз cycle.lua и сделайте reload, не закрывая программу (введите 1, когда программа попросит число).

void reload()
{
    lua_State * L;
    L = luaL_newstate();
    luaL_openlibs(L);
 
    int command = 1;
    do {
        if(command) {
            load_lua_file(L, (PARENT_DIRECTORY_STRING + "cycle.lua").c_str());
            check_execution(L);
        }
        else {
            break;
        }
        std::cout << "Enter control key 1 (to reload cycle.lua), or 0 (to exit): ";
        std::cin >> command;
    }
    while(command);
    lua_close(L); 
}

Результат выполнения функции reload() (перед второй загрузкой скрипта я изменил цифры в cycle.lua) :

lua     1
lua     2
lua     3
Enter control key 1 (to reload cycle.lua), or 0 (to exit): 1
lua     6
lua     5
lua     4
Enter control key 1 (to reload cycle.lua), or 0 (to exit): 0

Описание служебных функций load_lua_file() и check_execution():

void load_lua_file(lua_State *L, const char* name)
{
    int result = luaL_loadfile(L, name);
    if (result) {
        std::cout << "Returned result: " << result << std::endl;
        std::cout << "Loading error: " << lua_tostring(L, -1) << std::endl;
        exit(TERMINATION_ERROR); 
    } 
}
 
void check_execution(lua_State *L)
{
    int result = lua_pcall(L, 0, LUA_MULTRET, 0); 
    if (result) {
        std::cout << "Returned result: " << result << std::endl;
        std::cout << "Runtime error: " << lua_tostring(L, -1) << std::endl;
        exit(TERMINATION_ERROR); 
    }
}

Функция load_lua_file() загружает .lua скрипт в lua_State из указанного ей файла и производит проверку, удалось ли открыть файл; в случае ошибки она завершает работу программы и выводит соответствующие сообщения.
Функция check_execution() выполняет загруженный .lua скрипт и выводит ошибку, если она возникла.

Скачать весь пример к статье zip архивом.
Самую свежую версию моих экспериментов можно скачать со страницы моих экспериментов на github.

Leave a Reply

You must be logged in to post a comment.