В статье подробно рассматривается опыт интеграции высокоточных тактильных перчаток в 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, тогда латентность отладки и воспроизведения воспоминаний станет более предсказуемой.

Комментарии (0)