В статье подробно рассматривается опыт интеграции высокоточных тактильных перчаток в VR‑окружение при помощи ROS 2. Автор делится практическими наблюдениями, описывает архитектуру системы, принципы синхронизации данных и пример реализации на C++ и Python. Материал будет интересен тем, кто хочет заглянуть «под капот» реального прототипа мультисенсорного взаимодействия и избежать типичных ловушек в организации низкоуровневой передачи тактильных сигналов.

Введение
Погружение в виртуальную реальность давно вышло за рамки простых визуальных ощущений. Тактильные перчатки позволяют «потрогать» виртуальные объекты и получить мгновенную обратную связь — от легкого давления до вибраций переменной частоты. Однако нелинейность аппаратного тракта и требования к детерминированности требуют продуманного подхода к обмену сообщениями и времени выполнения. В этой статье исследуется, как применить ROS 2 для подключения и синхронизации таких перчаток, преодолевая задержки и рассогласования.
Немного контекста и личный опыт
В первый прототип автор внедрял перчатки “XGlove” без какой‑либо middleware — получалось слишком много «подергиваний» и артефактов. Переключение на ROS 2 с DDS‑бэкендом сразу дало стабильность и гибкость перенастройки QoS в полёте. К тому же, появилось ощущение единой «системной магистрали», куда легко подключать новые сенсоры: от IMU до камер.
Архитектура системы
Аппаратный уровень
Гаптические перчатки (актуаторы, сенсоры силы)
Контроллеры (STM32 с USB или UART)
Прослойка связи
ROS 2 (Foxy / Humble)
DDS (Fast-RTPS / CycloneDDS)
VR‑движок
Unity с ROS 2 Bridge или Unreal Engine + Custom Plugin
Узел синхронизации времени
ros2 topic pub /clock
и режимuse_sim_time=true
Весь обмен происходит через ROS‑топики: сенсорные данные (GloveState
), команды к актуаторам (HapticCommand
), а также временные метки для синхронизации.
Подключение тактильных перчаток к ROS 2

Драйвер для контроллера
На контроллере STM32 реализован USB‑CDC интерфейс, передающий сэмплы силы и положения пальцев. По умолчанию данные идут пакетом по 40 байт с частотой 1 кГц.
ROS 2‑нод на C++: публикация сырых данных
// glove_publisher.cpp
#include <rclcpp/rclcpp.hpp>
#include <std_msgs/msg/float32_multi_array.hpp>
#include <boost/asio.hpp>
class GlovePublisher : public rclcpp::Node {
public:
GlovePublisher() : Node("glove_publisher"), serial(io, "/dev/ttyUSB0") {
pub = this->create_publisher<std_msgs::msg::Float32MultiArray>("glove_state", 10);
timer = this->create_wall_timer(
std::chrono::microseconds(1000),
std::bind(&GlovePublisher::readAndPublish, this));
}
private:
void readAndPublish() {
GloveRaw raw;
boost::asio::read(serial, boost::asio::buffer(&raw, sizeof(raw)));
auto msg = std_msgs::msg::Float32MultiArray();
msg.data.insert(msg.data.end(), raw.pressure, raw.pressure + 5);
msg.data.insert(msg.data.end(), raw.bend, raw.bend + 5);
msg.layout.dim.resize(2);
pub->publish(msg);
}
rclcpp::Publisher<std_msgs::msg::Float32MultiArray>::SharedPtr pub;
rclcpp::TimerBase::SharedPtr timer;
boost::asio::io_service io;
boost::asio::serial_port serial;
};
int main(int argc, char** argv) {
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<GlovePublisher>());
rclcpp::shutdown();
return 0;
}
Синхронизация данных через ROS 2
Чтобы визуальная и тактильная части не «расходились во времени», важно единообразно распространить метки времени. Здесь помогает режим use_sim_time
и собственный брокер времени:
# Запуск ноды симуляции времени
ros2 run ros2_control_node clock_publisher --ros-args -p use_sim_time:=true
В каждом сообщении к тактильным актуаторам указывается метка header.stamp
, совпадающая с меткой визуального рендера.
// Пример: HapticCommand.msg
std_msgs/Header header
float32[5] amplitudes
А в подписчике:
// haptic_actuator.cpp
void callback(const HapticCommand::SharedPtr msg) {
auto now = this->get_clock()->now();
auto delay = msg->header.stamp - now;
if (delay > rclcpp::Duration(0)) {
// Ждем до нужного момента
std::this_thread::sleep_for(delay.nanoseconds());
}
driveActuators(msg->amplitudes);
}
Интеграция с VR‑движком
Unity + ROS 2 Bridge
Автор использовал пакет ROS‑TCP-Connector. Настройка сводится к созданию C#-скрипта:
using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Std;
public class VRGloveReceiver : MonoBehaviour {
ROSConnection ros;
void Start() {
ros = ROSConnection.GetOrCreateInstance();
ros.Subscribe<Float32MultiArrayMsg>("glove_state", OnGloveStateReceived);
}
void OnGloveStateReceived(Float32MultiArrayMsg msg) {
// Привязка усилий к физике пальцев модели
for (int i = 0; i < 5; i++) {
float pressure = msg.data[i];
HandSimulator.SetFingerForce(i, pressure);
}
}
}
Unreal Engine + Custom Plugin
В UE пришлось писать C++‑плагин, заворачивающий rclcpp::Subscription
. Понадобилось учитывать особенности рендер‑треда и многопоточности движка.
Обработка и фильтрация тактильных сигналов
Сырые данные от перчаток могут содержать шум. Используются простейшие фильтры Скользящего среднего:
# smoothing.py
import numpy as np
class MovingAverage:
def __init__(self, window_size=5):
self.window = []
self.k = window_size
def filter(self, value):
self.window.append(value)
if len(self.window) > self.k:
self.window.pop(0)
return np.mean(self.window)
# ROS‑subscriber в Python
import rclpy
from std_msgs.msg import Float32MultiArray
filterers = [MovingAverage(10) for _ in range(5)]
def callback(msg):
raw = msg.data[:5]
smooth = [filterers[i].filter(raw[i]) for i in range(5)]
apply_haptics(smooth)
Тестирование и отладка
ros2 topic echo и rviz2 для визуальной инспекции меток времени.
Инструменты DDS (например, rtps_deploy) для мониторинга сетевых задержек.
Логирование профилей в реальном времени: автор ловил «подвисания» при большой частоте через ros2 trace.
Выводы и рекомендации
ROS 2 + DDS — отличная связка для VR‑систем с жёсткими требованиями к латентности.
Всегда проверяйте QoS‑профили: лучший вариант для haptics — Reliable + Deadline.
Не бойтесь гибридных решений: часть логики можно вынести на Python для быстрой правки, а часть на C++ для производительности.
Полезно заводить «виртуальные часы» через
use_sim_time
, тогда латентность отладки и воспроизведения воспоминаний станет более предсказуемой.