惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
IT之家
IT之家
V
V2EX
Jina AI
Jina AI
V
Visual Studio Blog
有赞技术团队
有赞技术团队
博客园 - 司徒正美
博客园 - 叶小钗
The Cloudflare Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
腾讯CDC
Google Online Security Blog
Google Online Security Blog
博客园 - 【当耐特】
Apple Machine Learning Research
Apple Machine Learning Research
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
News and Events Feed by Topic
N
News and Events Feed by Topic
The Last Watchdog
The Last Watchdog
W
WeLiveSecurity
月光博客
月光博客
Security Archives - TechRepublic
Security Archives - TechRepublic
Webroot Blog
Webroot Blog
SecWiki News
SecWiki News
博客园_首页
罗磊的独立博客
量子位
Latest news
Latest news
I
Intezer
V
Vulnerabilities – Threatpost
A
Arctic Wolf
Last Week in AI
Last Week in AI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
S
SegmentFault 最新的问题
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Palo Alto Networks Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News | PayPal Newsroom

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Создаем клиентскую библиотеку ROS2. «Hello ROS»
stanislav_mi · 2026-05-04 · via Все публикации подряд на Хабре

Время на прочтение10 мин

Охват и читатели1.1K

Туториал

Пару лет назад у меня возникла мысль, почему бы не написать полноценную клиентскую библиотеку ROS2 для языка Lua?.. Увы, результат оказался невостребованным, зато сама разработка позволила лучше понять, как устроен этот фреймворк, а также с интересом провести время, разгадывая логические головоломки.

Клиентские библиотеки

Создатели ROS2 вынесли базовый функционал в C библиотеку  rcl (ROS Client Libraries). В теории, достаточно создать обертку на каком-либо языке программирования и можно пользоваться. Между тем, сторонних клиентских библиотек не так уж много. На мой взгляд, можно выделить следующие причины:

  • мой дед программировал роботов на C++/Python, и я буду отсутствие востребованности

  • документация не покрывает весь функционал, нет единой инструкции

  • разработчику библиотеки необходимо помимо целевого языка знать C, Python, CMake и иметь очень много свободного времени

Если вы, несмотря ни на что, решили создать свою версию клиентской библиотеки, или хотите лучше понять, как устроены rclcpp/rclpy, добро пожаловать. В этой серии заметок я постараюсь описать те шаги, которые нужно выполнить, чтобы портировать ROS2 на свой любимый язык программирования. В первой части мы рассмотрим создание простого исполняемого файла и его запуск в окружении ROS2. Во второй речь пойдет о генерации сообщений. В третьей части рассмотрим особенности реализации функционала ROS ноды. В качестве примеров я буду использовать свой код из ros2_lua, но постараюсь абстрагироваться от особенностей Lua насколько это возможно.

А нужно ли?

ROS2 содержит богатый набор CLI команд. Если в вашем языке программирования есть форматирование строк и передача команд операционной системе (функция типа execute()), этого может быть достаточно. Плюсы:

  • быстро

  • просто

  • переносимо между версиями ROS2

Минусы:

  • неэффективно, т.к. используется промежуточная интерпретация в Python

  • ограниченная функциональность

Этот способ подойдет для публикации данных, но организовать обработку входящих сообщений, особенно для нескольких топиков, может быть сложно или невозможно.

Документация

Начать знакомство со структурой кода можно на сайте ROS2. Однако наилучшая документация, которую мне удалось найти, это описание функций в самой библиотеке rcl. Также полезно рассмотреть реализацию официальных клиентских библиотек rclcpprclpyrclc, однако они перегружены дополнительным функционалом, разобраться в котором может быть непросто. Мне очень помогли сторонние библиотеки типа ros2_dotnetros2_rust, так как в них проще найти "скелет" системы.

MVP

В качестве минимального примера реализуем следующий функционал:

  • программу на целевом языке программирования, которая выполняет по таймеру вывод сообщения в консоль

  • настройку окружения через colcon

  • запуск через ros2 run

Функции клиентской библиотеки

Типичный подход заключается в том, чтобы создать динамическую библиотеку (назовем её rclbind), которая содержит вызовы к низкоуровневым командам rcl, следит за жизненным циклом объектов и т.п., а также собственно клиентскую библиотеку, упрощающую работу с ROS2 для конечного пользователя.

Каждый объект rcl имеет конструктор по умолчанию (_get_zero_initialized), функцию инициализации (_init) и освобождения ресуросов (_fini). Задача клиентской библиотеки заключается, в том числе, в обеспечении правильного порядка их вызовов. Каждый вызов библиотечной функции возвращает код завершения, который должен быть обработан. Для сокращения записи эта часть далее будет опущена.

Состояние исполняемого ROS2 файла хранится в объекте rcl_context_t, который должен быть создан первым и последним освобожден. Библиотеки rclcpp и rclpy допускают наличие нескольких контекстов и создают их динамически. Если вы считаете, что одного контекста более чем достаточно, можно пойти по пути rcldontnet и создать глобальную статическую переменную.

В функции rcl_lua_context_init мы инициализируем объект с учетом опций, полученных из аргументов командной строки. Можно расширить реализацию и считывать настройки из объекта rcl_init_options_t, который будет настраиваться до иницализации контекста. Также при запуске мы будем инициализировать логирование, поскольку трудно представить пример практического использования, когда ROS2 нода не пишет логи. Проверка статуса и освобождение ресурсов тривиальны и сводятся к вызову соответствующих функций.

#include <rcl/allocator.h>
#include <rcl/init.h>
#include <rcl/init_options.h>
#include <rcl/logging.h>

// здесь храним состояние окружения ROS2
static rcl_context_t context_;

// инициализация
// (функция rclbind.context_init)
static int rcl_lua_context_init (lua_State* L)
{
  // инициализация пустого объекта
  context_ = rcl_get_zero_initialized_context();

  // опции запуска по умолчанию
  rcl_allocator_t allocator = rcl_get_default_allocator();
  rcl_init_options_t init_options = rcl_get_zero_initialized_init_options();
  rcl_ret_t ret = rcl_init_options_init(&init_options, allocator);

  // заполнение полей контекста
  ret = rcl_init(argc, argv, &init_options, &context_);

  // инициализация логирования
  rcutils_ret_t rc_ret = rcutils_logging_initialize();
}

// проверка текущего состояния контекста
// (функция rclbind.context_ok)
static int rcl_lua_context_ok (lua_State* L)
{
  bool ok = rcl_context_is_valid(&context_);
}

// освобождение ресурсов
// (функция rclbind.context_shutdown)
static int rcl_lua_context_shutdown (lua_State* L)
{
  // контекст
  rcl_ret_t ret = rcl_shutdown(&context_);
  // логирование
  rcutils_ret_t rcret = rcutils_logging_shutdown();
}

Логирование мы инициализировали, теперь нужно добавить функцию, которая будет выводить сообщение пользователя. Для этого используем rcutils_log, первым аргументом здесь является структура rcutils_log_location_t, содержащая дополнительную информацию о файле, функции и строке кода, но можно обойтись без нее.

#include <rcutils/logging.h>

// упрощенная форма вывода сообщения в лог
// (функция rclbind.simp_log)
static int rcl_lua_logger_log_simp (lua_State* L)
{
  rcutils_log(NULL, severity, name, "%s", message);
}

Для того чтобы публиковать сообщения по таймеру, нам нужны два объекта: собственно таймер и часы, к которым он будет привязан (системные или реализованные в ROS2). Начнем с часов.

#include <rcl/time.h>

// конструктор часов
// (функция rclbind.new_clock)
static int rcl_lua_clock_init (lua_State* L)
{
  rcl_clock_t* clock = lua_newuserdata(L, sizeof(rcl_clock_t));
  rcl_allocator_t allocator = rcl_get_default_allocator();
  rcl_ret_t ret = rcl_clock_init(clock_type, clock, &allocator);
}

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

#include <rcl/timer.h>

// конструктор таймера
// (функция rclbind.new_timer)
static int rcl_lua_timer_init (lua_State* L)
{
  rcl_allocator_t allocator = rcl_get_default_allocator();
  rcl_timer_t* timer = lua_newuserdata(L, sizeof(rcl_timer_t));
  *timer = rcl_get_zero_initialized_timer();

  rcl_ret_t ret = rcl_timer_init(
    timer, clock, context_, period_nsec, NULL, allocator);
}

// запуск таймера
// (метод timer.call)
static int rcl_lua_timer_call (lua_State* L)
{
  rcl_ret_t ret = rcl_timer_call(timer);
}

Основной функционал ROS2 ноды обычно реализуется в коллбэках. За работу с ними отвечает объект rcl_wait_set_t, который запускается в цикле. На каждой итерации происходит заполнение данного объекта ссылками на такие элементы как подписчики, таймеры и прочие, с которыми могут быть связаны callback функции. Затем Wait Set переходит в блокирующий режим ожидания. Когда случается ожидаемое событие, объект разблокируется и позволяет обработчику извлечь информацию об этом событии. Правда, для этого придется пройтись по каждой из сущностей, добавленных в Wait Set, и проверить, связано ли событие конкретно с ней.

#include <rcl/wait.h>

// конструктор Wait Set
// (функция rclbind.new_wait_set)
static int rcl_lua_wait_set_init (lua_State* L)
{
  // инициализация для работы с заданным числом объектов
  rcl_wait_set_t* wait_set = lua_newuserdata(L, sizeof(rcl_wait_set_t));
  *wait_set = rcl_get_zero_initialized_wait_set();
  rcl_ret_t ret = rcl_wait_set_init(
    wait_set,
    (size_t) num_sub,     // число подписчиков
    (size_t) num_guard,   // число защитников
    (size_t) num_timers,  // число таймеров
    (size_t) num_cli,     // число клиентов
    (size_t) num_srv,     // число сервисов
    (size_t) num_ev,      // число событий
    context_,
    rcl_get_default_allocator());
}

// очистка Wait Set, обычно вызывается в начале цикла
// (метод waitset.clear)
static int rcl_lua_wait_set_clear (lua_State* L)
{
  rcl_ret_t ret = rcl_wait_set_clear(wait_set);
}

// добавление таймера в Wait Set
// (метод waitset.add_timer)
static int rcl_lua_wait_set_add_timer (lua_State* L)
{
  size_t index = 0;
  rcl_ret_t ret = rcl_wait_set_add_timer(wait_set, timer, &index);
}

// блокирующее ожидание события
// (метод waitset.wait)
static int rcl_lua_wait_set_wait (lua_State* L)
{
  rcl_ret_t ret = rcl_wait(wait_set, timeout);
  switch (ret) {
    case RCL_RET_OK:
    case RCL_RET_TIMEOUT:
      // успешно
    default:
      // ошибка
  }
}

// проверка сработавших таймеров
// можно вернуть список таймеров или сразу добавить связанные с ними функции
// (метод waitset.ready_timers)
static int rcl_lua_wait_set_ready_timers (lua_State* L)
{
  for (size_t i = 0; i < wait_set->size_of_timers; i++) {
    if (wait_set->timers[i]))
    {
      // помещяем связанную функцию в список
    }
  }
}

В этом примере используются таймеры, однако для других объектов (подписчиков, клиентов и пр.) методы создания, добавления в Wait Set и проверки состояния аналогичны.

После того как библиотека скомпилирована, можно перейти к написанию программы. В случае Lua код может быть следующим.

-- подключение функций rcl
local rclbind = require("rcllua.rclbind")

-- callback для таймера
local function timer_cb ()
  rclbind.simp_log(rclbind.LogLevel.INFO, "rcllua", "Hello ROS!")
end

-- инициализация контекста
rclbind.context_init(arg)

-- создание необходимых объектов
local clock = rclbind.new_clock(rclbind.ClockType.STEADY_TIME)
local period_s = 0.5
local timer = rclbind.new_timer(clock, period_s, timer_cb)
local wait_set = rclbind.new_wait_set(0, 0, 1, 0, 0, 0)

while rclbind.context_ok() do
  -- запускаем таймер
  timer:call()
  -- добавляем таймер в список ожидания
  wait_set:clear()
  wait_set:add_timer(timer)
  -- ждем ...
  local timeout = -1
  wait_set:wait(timeout)

  -- здесь должно быть извлечение таймера и
  -- функции через wait_set:ready_timers()
  -- по поскольку других объектов в списке ожидания не было
  -- вызовем связанную функцию
  timer_cb()
end

-- освобождение контекста
rclbind.context_shutdown()

В этой программе мы использовали "сырые" вызовы функций из rcl, что, очевидно, неудобно для конечного пользователя, хотя и эффективно с точки зрения вычислений. Повышение уровня абстракции для удобства применения - задача следующего уровня клиентской библиотеки.

Настройка окружения в colcon

Команда ros2 run выполняет поиск и запуск исполняемого файла, т.е. можно обойтись без нее, достаточно напрямую вызвать написанную программу. Однако, если мы хотим полноценно пользоваться инструментами ROS2, нужно как минимум настроить сборку через colcon build и запуск через ros2 run.

В Lua нет стандартной системы сборки, во всяком случая я с такой не сталкивался. Поэтому использовал "стандартные" для ROS2 инструкции в CMake и bash.

Пакет можно создать командой pkg create, тогда что бы ни было внутри, colcon его попытается собрать.

ros2 pkg create --build-type ament_cmake hello_ros

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

Создадим шаблон env_hook_lua.sh.in со сценарием bash для обновления путей поиска исполняемых файлов. Определим здесь функцию для формирования списка путей без дубликатов и пустых строк, которая потом применяется к текущему пути. Данный код на 95% процентов совпадает с автогенерируемым кодом в пакете ROS2, отличие заключается только в использовании разделителя ";", который принят в Lua. Если ваш язык программирования использует ":", такой файл можно не добавлять.

ament_append_unique_value() {
  # аргументы
  _listname=$1
  _value=$2

  # проверка наличия переменной
  eval _values=\$$_listname
  _duplicate=
  _ament_append_unique_value_IFS=$IFS
  IFS=";"
  if [ "$AMENT_SHELL" = "zsh" ]; then
    ament_zsh_to_array _values
  fi
  for _item in $_values; do
    # пустые строки игнорируются
    if [ -z "$_item" ]; then
      continue
    fi
    if [ $_item = $_value ]; then
      _duplicate=1
    fi
  done
  unset _item

  # дубликаты игнорируются
  if [ -z "$_duplicate" ]; then
    # устранение начального разделителя
    if [ -z "$_values" ]; then
      eval $_listname=\"$_value\"
    else
      # конкатенация
      unset IFS
      eval $_listname=\"\$$_listname\;$_value\"
    fi
  fi
  IFS=$_ament_append_unique_value_IFS
  unset _ament_append_unique_value_IFS
  unset _duplicate
  unset _values
  unset _value
  unset _listname
}

# создаём пути
if [ -z "$LUA_PATH" ]; then
  export LUA_PATH=";;$COLCON_CURRENT_PREFIX/lib/lua/?.lua"
else
  ament_append_unique_value LUA_PATH "$COLCON_CURRENT_PREFIX/lib/lua/?.lua"
fi

Чтобы шаблон был "подхвачен" при сборке, нужно создать переменную, имя которой оканчивается на ENVIRONMENT_HOOK_REGISTERED. Для этого определим макрос в CMake, который переместит исполняемые файлы в директорию проекта install/имя_пакета/lib/lua, а также настроит обновление окружения при запуске install/setup.bash.

macro(rcllua_cmake_install_lib src_name)
  install(
    DIRECTORY ${src_name}
    DESTINATION lib/lua
  )

  if(NOT DEFINED _AMENT_CMAKE_LUA_ENVIRONMENT_HOOK_REGISTERED)
    set(_AMENT_CMAKE_LUA_ENVIRONMENT_HOOK_REGISTERED TRUE)

    find_package(ament_cmake_core QUIET REQUIRED)

    ament_environment_hooks(
      "${rcllua_cmake_DIR}/templates/env_hook_lua.sh.in")
  endif()
endmacro()

Запуск через ros2 run

Для того чтобы выполнялась команда ros2 run необходим исполяемый файл. Можно обязать пользователя явно указывать shebang в своем скрипте, но если он забудет это сделать, работоспособность нарушится. Пойдём по пути rclpy и будем генерировать новый файл, который при запуске вызывает пользовательскую программу. Для этого служит следующий макрос. Он создает файл с указанием пути к вызываемой программе, помещает его в install/имя_пакета/lib/имя_пакета и делает исполняемым.

# путь к интерпретатору
set(RCLLUA_EXEC /usr/local/bin/lua)

macro(rcllua_cmake_executable src_name exec_name)
  set(_out_dir ${CMAKE_INSTALL_PREFIX}/lib/${PROJECT_NAME})
  file(MAKE_DIRECTORY "${_out_dir}")
  # создаем файл
  file(WRITE "${_out_dir}/${exec_name}"
    "#!${RCLLUA_EXEC}\n"
    "local script = loadfile('${CMAKE_SOURCE_DIR}/${src_name}')\n"
    "script()"
  )
  # настраиваем права доступа
  file(CHMOD "${_out_dir}/${exec_name}"
    PERMISSIONS
      OWNER_READ OWNER_EXECUTE
      GROUP_READ GROUP_EXECUTE
      WORLD_READ WORLD_EXECUTE
  )
endmacro()

Теперь пользователь может добавить в CMakeLists.txt следующий код.

# зависимости
find_package(rcllua REQUIRED)
find_package(rcllua_cmake REQUIRED)

# создание исполяемого файла hello
rcllua_cmake_executable(${PROJECT_NAME}/hello.lua hello)

# для доступа из других пакетов и файлов
# можно настроить пути к текущему пакету 
rcllua_cmake_install_lib(${PROJECT_NAME})

В процессе сборки настройки путей будут добавлены в install/setup.bash, а файлы будут доступны для вызова через ros2 run.

Заключение

Здесь мы рассмотрели основные элементы исполняемого файла ROS2: инициализацию контекста, создание объектов(часов и таймера), обработку callback вызовов с помощью Wait Set. Также описали настройку окружения и сборки файлов. С точки зрения реализации, практически все элементы ROS ноды похожи друг на друга, они включают в себя функци для создания, вызова и взаимодействия с Wait Set. Однако, для работы с оставшимися объектами нам пока не хватает реализации сообщений. Об этом и поговорим в следующий раз.