Задача хоть интересная, но по объему получилась не маленькая. Разбита на 9 частей, 6 из которых уже готовы и упакованы в 3 статьи. В каждой статье будет много принтскринов и кусков кода(все убрано под спойлеры). Если пропущу какие-то детали – заранее извиняюсь, пишите в личку или комментарии, некоторые особенности мог просто опустить как само собой разумеющееся или просто вылетело из головы. Части и статусы на момент публикации:  Подготовительная (реализована). Сигнализация Websocket (реализована). Настройка WebRTC Connection + DataChannel (реализована). Настройка WebRTC Media streaming (реализована). Настройка управления камерой (реализована). Настройка управления манипулятором (реализована). Перенос на ROS (в процессе). Работа через Интернет (в процессе). Настройка передвижения (не взята). А вот то, что мы получим к концу 6 части: Disclaimer 1. Некоторые участки кода и решения в этой серии статей могут вызвать неоднозначные чувства у читателей, прошу отнестись к этим особенностям с пониманием, так как:  1. Основная цель – это демонстрация, реализация Proof of Concept, некоторые элементы заведомо не реализовывались, дабы сократить объем работы и материала для публикации. 2. Unity/C#/Python у меня на начальном уровне, а с некоторыми вещами, как с корутинами в python и c#, вообще столкнулся впервые на этапе приготовления этого блюда. 3. Переключаться между 4-мя ЯП достаточно тяжко для моей скороварки, мне и самому плакать хотелось от того, что я наделал, но с какого-то момента я уже просто не мог остановиться, простите. Проведя быстрый аудит компонентов, которые у меня были в наличии, я остановился на решении с роботизированной рукой-манипулятором подключенной к RPI3B+, и решил выстроить весь процесс вокруг идеи управления этим манипулятором из VR-приложения, с возможностью быстро доработать это решение управлением не только из локальной сети, но и через Интернет. Это можно сделать реализовав клиент-серверную архитектуру для взаимодействия компонентов, но тогда получается минус в использовании сервера как единой точки консолидации трафика и будет дополнительная задержка между VR и Манипулятором, поэтому решено использовать WebRTC в качестве решения для P2P трафика, и Websocket для сигнализации WebRTC, еще один плюс WebRTC – элегантный механизм прохода за NAT с помощью stun/turn-серверов в будущем. В WebRTC для обмена данными существует DataChannel, который можно использовать для передачи данных управления на сервоприводы и MediaStreams для передачи видео/аудио контента, что мы и будем использовать для трансляции видео из USB-камеры в приложение. Websocket отлично подходит для реализации сигнализации между WebRTC-пирами, можно, конечно, использовать и REST для обмена сигнализацией, но минус REST – периодичность опроса конечной точки при инициации подключения. Т.е. когда мы захотим инициировать новое подключение к манипулятору, нам потребуется ждать очередного периода опроса для обмена контекстом WebRTC. Таким образом у нас получается комплект из 3-х основных компонентов – Сервер сигнализации для обмена контекстом WebRTC, Исполнительный компонент (RPI-хост) который будет принимать управляющие сигналы для сервоприводов и транслировать видеопоток, и Управляющий компонент(VR-приложение), в котором мы принимаем видеопоток, и из которого мы будем управлять манипулятором отправляя сообщения на RPI-хост. Так же, мы реализуем дублирующий управляющий компонент с помощью HTML/JS, он нам поможет в поэтапной реализации и отладке. Сервер сигнализации реализуем с помощью SpringBoot, на стороне исполнительного компонента на RPI будем использовать приложение на Python, управляющий компонент сделаем с помощью Unity XR, так же на стороне сервера сигнализации продублируем управляющий компонент с помощью стандартными средствами веб – html-bootstrap-js-jquery. Disclaimer 2 Вообще, все что касается робототехники нужно делать с использованием ROS на стороне исполнительного компонента, но мне было интересно посмотреть на альтернативу попроще, а уже потом перенести на ROS. Как говорится – все познается в сравнении. Используемые компоненты: VR-гарнитура с контроллерами, я буду использовать Oculus Quest 2. Кабель USB Type-C 1-2м, который будем использовать для подключения VR-гарнитуры к Unity для тестирования и отладки. Raspberry Pi 3B+ с БП. Плата PCA9685, это плата расширения, она соединяется I2C интерфейсом с RPI и 	позволяет управлять 16-ю ШИМ сервоприводами. Кронштейн-подвес для SG90 и 2 сервопривода SG90, для вращения подвеса камеры. USB-веб-камера, будет использоваться для трансляции видео в VR. Манипулятор(6Dof Arm MG996R/YF-6125MG у меня такой), собственно тот манипулятор, которым мы будем управлять из VR. Комплект соединительных проводов Мама-Папа(40см.). 	У моих сервоприводов стоковые кабели коротки и их не дотянуть до платы PCA9685. Блок питания 220V – 5V. Для запитывания серво. Основание для фиксации компонентов (манипулятор, RPI, БП, подвес для камеры), у меня это лишняя часть пластикового стеллажа, куда смонтированы все компоненты. (желательно) Роутер на OWRT, если будете проводить тесты по RTT/Jitter. (желательно) Более-менее прямые руки, но и с "не очень" получится с нескольких попыток. Требуемая экспертиза: Начальные знания по Linux. Начальные знания по Python. Начальные знания по Java и SpringBoot. Начальные знания по Unity и C#. Начальные знания по HTML/Bootstrap/JS/JQuery. Начальные знания по Websocket и WebRTC. Основные операции будут проводиться на рабочей станции под Win10, периодически переключаясь на Ubuntu WSL для работы с RPI. На Win10 установлены: Eclipse IDE, для SpringBoot. Unity + MS Visual Studio, для VR части. Oculus Client, нужен для Oculus Link.(Возможно потребуются драйверы ADB для Oculus, но у меня они уже были установлены ранее). Для начала собираем минимальный исполнительный комплект – подвес камеры с SG90 и соединяем его с платой PCA9685(я разобрал свою USB-камеру, т.к. корпус был большой и неудобный). В плату PCA9685 сервы SG90 устанавливаем в порты 0 и 1. Они будут отвечать за ротацию камеры по осям(поворот головы в VR-гарнитуре). Далее делаем установку камеры на кронштейн на основе за местом, где планируется манипулятор. Кабели от разъемов привода удлиняем с помощью дополнительных кабелей «Папа-Мама».  Делаем соединение платы PCA9685 и RPI3B+: Питание для серво должно идти через отдельную клемму на PCA9685, но до 6 части мы будем использовать только 2 серво SG90 и брать питание с RPI можем напрямую.   Так же рекомендую использовать обычный светодиод подключенный к RPI для проведения маленького наглядного тестирования работы RPI и скриптов (Pin 39-40) (как же можно обойтись без мигания светодиодом). Далее, подготавливаем RPI, заливаем ОС с помощью RPI Imager(RPI OS Light x64). После успешной заливки делаем стандартные апдейт/апгрейд apt и личные предпочтения по настройке системы(ssh-ключи, samba и т.д.).  Для работы с интерфейсом I2C в RPI нам нужен пакет пакет с i2c-tools:  sudo apt install i2c-tools -y Заходим в raspi конфиг и включаем поддержку I2C:  sudo raspi-config  Interface Options → <Enable I2C>  reboot Проверяем I2C: i2cdetect -y 1  Это значит все норм и плата обнаружена. Далее, для работы с серво через PCA9685 из Python нам понадобится pip3-пакеты, поэтому ставим pip3: sudo apt-get install python3-pip  И ставим сами пакеты: pip3 install adafruit-circuitpython-pca9685 pip3 install adafruit-circuitpython-motorkit Установка пакетов глобально это конечно плохой тон, но тут мы срежем углы, т. к. все равно все будет на ROS. Теперь можем проверить работу маленьким скриптом: nano /home/pi3/shared/testpi3b/part0.py PS: Работа с серво по ШИМ имеет свои особенности, поэтому рекомендую сначала потратить часик на чтение материала по теме. Если все сделали правильно - то светодиод моргнет 3 раза и сервомотор в 0-порту PCA9685 3 раза сделает поворот 0-180 градусов. По деталям работы из Python с PCA9685 можно почитать тут, с Rpi.GPIO тут. На этом подготовительная часть с RPI-хостом закончена. Следующим пунктом будет подготовка и установка SSL сертификатов для работы websocket-over-ssl (можно конечно обойтись без SSL, но мне хотелось сразу решить этот вопрос и получить возможность тестировать видео в браузере). Генерируем новый самоподписанный сертификат(я это делаю из под WSL Ubuntu 20): sudo openssl req -x509 -nodes -days 1825 -newkey rsa:2048 -keyout ./ssl/ssl-selfsigned.key -out ./ssl/ssl-selfsigned.crt  ... отвечаем на все вопросы, указываем IP-хоста на котором будет крутиться сервер. И сразу же переводим его в формат PKS#12: openssl pkcs12 -export -in ./ssl/ssl-selfsigned.crt -inkey ./ssl/ssl-selfsigned.key -out ./ssl/ssl-selfsigned.p12 Файлы сертификатов копируем на Win10, проводим установку сертификата на Win10. Теперь сделаем заготовку для сервера сигнализации. Делаем новый SpringBoot проект через Spring Initializr для сервера сигнализации, нам потребуются зависимости:  Делаем базовую настройку работы на порту 9000 с нашим сертификатом:  Подавляем идентификацию/аутентификацию с использованием SecurityFilterChain:  Делаем простейший контроллер, страницу для HTML и JS-скрипт:    Файл robo.js - пока оставим пустой, он нам потребуется дальше.  Такая настройка позволит работать через HTTPS и не требует ввода логина/пароля при соединении(нам это пока не требуется). Теперь запускаем в BootDashboard и проверяем в браузере(https://<ip>:9000/main), что страница открывается успешно через https. И последний подготовительный этап - сделаем базовое VR-приложение. Создаем новый проект Unity. Для начала у нас должен быть установлен Unity Hub, Oculus Client, и IDE для C#. Через Unity Hub устанавливаем Editor (2021.3.19f1). Создаем новый проект (3D Core). После загрузки проекта установим XR Plugin Managment и сделаем стартовые настройки для XR:  Давайте дальше добавим еще один компонент - XR Interaction Toolkit, который отвечает за взаимодействие с элементами сцены с помощью шлема и контроллеров. После установки импортируем Starter Assets, этот ассет поможет в освоении базового взаимодействия с элементами сцены, там есть хорошие префабы и DemoScene для экспериментов. Теперь добавим готовые пресеты из XR Interaction Toolkit в наш проект, для этого зайдем в директорию Samples →  XR Interaction Toolkit → 2.2.0 → Starter Assets и увидим там 8 файлов пресетов для разного типа взаимодействия Scene, добавим их все.  Теперь нам нужно сделать минимальную сцену-окружение для дальнейших тестов. Добавляем простой Panel Ground. В него добавляем Teleportation Area, и простой материал с цветом на наш пол. Создаем 2 Empty объекта контейнера для префабов левого и правого контроллера, которые разворачиваем на 180 по «Y», и в каждый из них добавляем префаб модели контроллера из Samples.  Подключаем Quest2 по USB к рабочей станции и подключаемся к Oculus Link. Теперь если запустить в Unity проект (Play) по идее вы попадаете в нашу дефолтную сцену, можете крутить головой в окулусе, телепортироваться по ground, видеть контроллеры с красными лазерами. Если что-то не будет получаться с Unity XR, смотрим видео тут(ссылка). Подготовка закончена, по окончании этого этапа у вас есть: Собранный стенд с подвесом камеры. Заготовка сервера сигнализации и управления из веб. Заготовка для исполнительного компонента (RPI-хоста). Заготовка для управляющего компонента (VR-приложения). Теперь можем приступить к связыванию компонентов с помощью сервера сигнализации. Основные сущности для сигнализации:  userId/toUserId – идентификаторы пользователей(RPI-хост, VR-клиент, браузерный клиент).   тип сообщения исходя из описания WebRTC→ OFFER, ANSWER, ICE   наши типы процесса установления соединения → LOGIN, NEWMEMBER data – доп. поле для служебной информации установления UUID. payload – собственно сами offer/answer/ice из WebRTC в виде json. Для работы по Websocket с WebRTC нам нужно реализовать модель сообщения:   сделать хендлер конфигурации:  и хендлер самих сообщений сигнализации: PS – в  хендлер сообщений сигнализации лучше добавить логирование всех сообщений в websocket, это упростит трейс обмена сообщениями. Для 	браузерной части – дополняем наш main.html новыми элементами: соединение с websocket-сервером, отправка запроса на UUID, и NEWMEMBER Info:  Формируем скрипт robo.js для обработки соединения:  В итоге, мы можем зайти на страницу https://<ip>:9000/main c разных браузеров и проверить:  В Python для работы с вебсокетами используем библиотеку websockets, pip3 install websockets а так же нам понадобиться asynco, ssl и json для работы с сообщениями:  Теперь мы можем попробовать подсоединиться и из браузера и из RPI и оправить NEWMEMBER инфо между браузером и Python, проверяем результат в консоли браузера и логе сервера:  Для работы с вебсокетом из Unity используем библиотеку WebSocketSharp, для этого сначала поставим NuGetForUnity, а потом оттуда делаем установку WebSocketSharp.  Далее реализуем небольшой UI: cоздаем Empty Object с названием Connection Panel, становим его параметры Rect Transform и наполняем его компонентами:  После этого размещаем в Connection Panel несколько элементов:  Теперь создаем новый C# скрипт, который будет ядром наших соединений websocket и webrtc:  Готовый скрипт аттачим на объект Connection handler, а его в свою очередь на каждый Button, и выбираем соответствующий метод для этой кнопки:  По нажатию на кнопку вызывается метод из нашего скрипта, происходит соединение, отправка/прием сообщений(+ смотрим на Debug console в Unity)! Тестируем Unity сначала с браузером, потом и с Python скриптом.  Видим на всех 3х сторонах, что обмен сообщениями происходит успешно, а значит мы завершили Часть 2. Продолжение в следующей статье – Управляем роботами из VR. Продолжение 1