init
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.venv/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Актуальные заметки по соревнованию
|
||||||
|
|
||||||
|
## Рабочие допущения
|
||||||
|
|
||||||
|
- Будет отдельный день настройки оборудования.
|
||||||
|
- Основной ожидаемый формат — физические РМК, но Webots остаётся запасным и учебным вариантом.
|
||||||
|
- На физических РМК ожидается тот же ROS 2 API. Если площадка выдаёт другое окружение без документации и времени адаптации, это нужно сразу поднимать как проблему инфраструктуры.
|
||||||
|
- Интернет и документация разрешены.
|
||||||
|
- По телефонному разъяснению эксперта LLM разрешены, формат близок к хакатону. Это устное актуальное разъяснение; его стоит повторно зафиксировать на инструктаже.
|
||||||
|
- Целевой ArUco ID сообщает эксперт.
|
||||||
|
- Для модуля В, вероятно, понадобится новый датасет с выданной камеры и физических деталей либо готовый датасет организаторов.
|
||||||
|
- Для модуля Г важнее работоспособный интерфейс по критериям, чем сложный дизайн.
|
||||||
|
|
||||||
|
## Ограничение числа скриптов
|
||||||
|
|
||||||
|
По полному финальному КЗ:
|
||||||
|
|
||||||
|
| Модуль | Максимум взаимодействий/скриптов на зачётном ПК |
|
||||||
|
| --- | ---: |
|
||||||
|
| Б | 2 |
|
||||||
|
| В | 1 |
|
||||||
|
| Д | 4 |
|
||||||
|
|
||||||
|
Текущий `module5/module5.py`, запускаемый одним процессом, не нарушает ограничение модуля Д. Он не использует собственный UDP-протокол: один ROS 2 node на зачётном ПК публикует команды в пространства `/RMC1/*` и `/RMC2/*` и получает телеметрию обоих роботов через DDS.
|
||||||
|
|
||||||
|
Это работает, если оба физических РМК доступны в одном согласованном ROS-домене. Если площадка разведёт роботов по разным доменам или компьютерам, архитектуру нужно будет адаптировать в день настройки.
|
||||||
|
|
||||||
|
Официальный сценарий модуля Д допускает последовательные или параллельные операции. Текущее решение выполняет их последовательно — медленнее, но проще и безопаснее.
|
||||||
|
|
||||||
|
## Предлагаемые изменения 30%
|
||||||
|
|
||||||
|
### Модуль Б — навигация
|
||||||
|
|
||||||
|
- заменить ID стартового и целевого ArUco;
|
||||||
|
- переставить несколько закрытых ячеек;
|
||||||
|
- изменить место появления динамического препятствия;
|
||||||
|
- передавать старт, финиш и препятствия через конфиг.
|
||||||
|
|
||||||
|
Это меняет входные данные, но сохраняет планировщик, маршрут, возврат, объезд и оценку времени.
|
||||||
|
|
||||||
|
### Модуль В — техническое зрение
|
||||||
|
|
||||||
|
- выбирать другой целевой класс из заранее подготовленных классов;
|
||||||
|
- менять расположение детали перед камерой;
|
||||||
|
- менять цвет или номер целевой детали;
|
||||||
|
- не вводить неизвестную категорию и новую архитектуру непосредственно на площадке.
|
||||||
|
|
||||||
|
Если физические детали отличаются от симулятора, новый датасет всё равно потребуется. Классы и формат разметки лучше сохранить прежними.
|
||||||
|
|
||||||
|
### Модуль Г — GUI/FMS
|
||||||
|
|
||||||
|
- переключатель РМК-1/РМК-2;
|
||||||
|
- состояния `ONLINE/OFFLINE`, `AUTO/MANUAL/STOP`;
|
||||||
|
- перестановка блоков интерфейса;
|
||||||
|
- цвет свободных и занятых ячеек;
|
||||||
|
- окно последних событий;
|
||||||
|
- неизменные ROS-топики и сервисы.
|
||||||
|
|
||||||
|
### Модуль Д — складской сценарий
|
||||||
|
|
||||||
|
- менять ячейки хранения, комплектации и выдачи;
|
||||||
|
- выбирать другую известную деталь;
|
||||||
|
- менять порядок существующих операций;
|
||||||
|
- сохранять РМК, ARM95 и ROS-интерфейсы;
|
||||||
|
- хранить точки и класс детали в YAML/JSON или аргументах запуска.
|
||||||
|
|
||||||
|
### Модуль А — диагностика
|
||||||
|
|
||||||
|
- заменить диагностируемую плату;
|
||||||
|
- изменить набор заранее внесённых неисправностей;
|
||||||
|
- использовать единый шаблон отчёта;
|
||||||
|
- ограничить дефекты теми, которые обнаруживаются осмотром и мультиметром без пайки.
|
||||||
|
|
||||||
|
Этот вариант безопасен только при наличии схемы или документации на плату и заранее определённого перечня допустимых неисправностей.
|
||||||
@@ -0,0 +1,248 @@
|
|||||||
|
# ЧВТ-2026: документы и памятка по подготовке
|
||||||
|
|
||||||
|
Этот приватный репозиторий — офлайн-набор для подготовки: исходные материалы организаторов, порядок установки окружения, ссылки на документацию и короткие ROS-примеры. Рабочие решения модулей лежат отдельно в [`mavxa/mvch`](https://github.com/mavxa/mvch).
|
||||||
|
|
||||||
|
> Конкурсное задание, критерии и указания экспертов на площадке имеют приоритет над этой памяткой и старыми презентациями. Не меняйте мир Webots, расположение объектов и штатные управляющие пакеты, если это прямо не разрешено экспертами.
|
||||||
|
|
||||||
|
## Что лежит в репозитории
|
||||||
|
|
||||||
|
- [`materials/presentations`](materials/presentations) — пять учебных презентаций (введение, документация, Python/ROS, симулятор, развитие компетенции).
|
||||||
|
- [`materials/pdf`](materials/pdf) — шестая презентация про датасет и модели, расписание.
|
||||||
|
- [`materials/final`](materials/final) — полный очный финал: инфраструктурный лист, КЗ модулей А–Д, критерии и приложения.
|
||||||
|
- [`materials/tasks`](materials/tasks) — отдельный ранний/отборочный комплект по навигации; не путать с полным финалом.
|
||||||
|
- [`basic`](basic) — примеры для реального ROS 2: движение к ArUco с odometry/TF/lidar, ручной `cmd_vel`, остановка, лифт, мониторинг и BFS-маршрут.
|
||||||
|
- [`setup/FROM_ZERO.md`](setup/FROM_ZERO.md) — подготовка полностью чистого Windows/Ubuntu и день настройки оборудования.
|
||||||
|
- [`setup/COMMANDS.md`](setup/COMMANDS.md) — только команды для копирования в терминал.
|
||||||
|
- [`setup/check_environment.sh`](setup/check_environment.sh) — безопасная read-only проверка окружения.
|
||||||
|
- [`COMPETITION_NOTES.md`](COMPETITION_NOTES.md) — актуальные устные уточнения, лимиты скриптов и предложения 30% изменений.
|
||||||
|
|
||||||
|
Материалы собраны из выданного набора ЧВТ. Репозиторий оставлен приватным: права на исходные презентации и задание принадлежат их авторам/организаторам.
|
||||||
|
Актуальную опубликованную редакцию сверяйте с [официальным комплектом финала 2026](https://esim.firpo.ru/d/f/2026), раздел «Оператор-техник складских роботов (AGV/AMR)».
|
||||||
|
|
||||||
|
## Порядок чтения
|
||||||
|
|
||||||
|
1. `materials/final/04-final-task.docx` и `materials/final/05-final-criteria.xlsx` — сначала понять конечный результат и баллы очного финала.
|
||||||
|
2. `1. Введение...` и `2. Документация...` — термины, РМК и структура компетенции.
|
||||||
|
3. `3. Разбор...` — Python, ROS 2, топики и сервисы.
|
||||||
|
4. `4. Практика... (симулятор)` — установка и работа с Webots.
|
||||||
|
5. `5. Практика... (датасет + модели)` — разметка и обучение детектора.
|
||||||
|
6. `setup/FROM_ZERO.md` — подготовка ПК и оборудования.
|
||||||
|
7. README каждого модуля в `mvch` — точные команды текущего решения.
|
||||||
|
|
||||||
|
## Быстрый старт на подготовленной VM
|
||||||
|
|
||||||
|
Нужны SSH-ключ с доступом к приватным GitHub-репозиториям и уже установленное окружение ROS/Webots.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/scripts
|
||||||
|
git clone git@github.com:mavxa/mvch-docs.git ~/scripts/mvch-docs
|
||||||
|
git clone git@github.com:mavxa/mvch.git ~/scripts/mvch
|
||||||
|
|
||||||
|
cd ~/scripts/mvch-docs
|
||||||
|
./setup/check_environment.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
В каждом новом ROS-терминале:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Перед поездкой обновлять только fast-forward:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch
|
||||||
|
git switch main
|
||||||
|
git pull --ff-only
|
||||||
|
```
|
||||||
|
|
||||||
|
## Установка чистого окружения
|
||||||
|
|
||||||
|
Официальная целевая база материалов — **Ubuntu 24.04 LTS + ROS 2 Jazzy + Webots + Fast DDS + colcon**. На площадке лучше использовать выданный и заранее проверенный образ, если он будет предоставлен.
|
||||||
|
|
||||||
|
Полный путь от чистой Windows/Ubuntu, включая VS Code, расширения, Git/SSH, ROS, Webots, Python/YOLO, Bun, Node.js и OpenCode: [`setup/FROM_ZERO.md`](setup/FROM_ZERO.md). Краткий файл только с командами: [`setup/COMMANDS.md`](setup/COMMANDS.md).
|
||||||
|
|
||||||
|
### 1. ROS 2 Jazzy
|
||||||
|
|
||||||
|
Устанавливайте Jazzy по [официальной инструкции ROS 2 для Ubuntu](https://docs.ros.org/en/jazzy/Installation/Ubuntu-Install-Debs.html), включая настройку locale и репозитория ROS. Минимальный набор после подключения репозитория:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install ros-jazzy-desktop ros-dev-tools python3-colcon-common-extensions \
|
||||||
|
python3-rosdep python3-venv git
|
||||||
|
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
`rosdep init` выполняется один раз на систему:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rosdep init
|
||||||
|
rosdep update
|
||||||
|
```
|
||||||
|
|
||||||
|
Если команда сообщает, что sources list уже существует, повторный `rosdep init` не нужен.
|
||||||
|
|
||||||
|
### 2. Webots
|
||||||
|
|
||||||
|
Сверяйте установку с [официальной инструкцией Cyberbotics](https://cyberbotics.com/doc/guide/installing-webots). Версия симулятора должна совпадать с версией, под которую собран выданный ROS-пакет; самовольно обновлять её прямо перед соревнованием не стоит.
|
||||||
|
|
||||||
|
### 3. Пакеты симулятора ЧВТ
|
||||||
|
|
||||||
|
Команды из выданных материалов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/ros2_ws/src
|
||||||
|
cd ~/ros2_ws/src
|
||||||
|
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_webots_fms_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_arm95_moveit_config.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_aruco_detect_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_dual_lidar_merge_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_nav_ros2.git
|
||||||
|
|
||||||
|
cd ~/ros2_ws
|
||||||
|
rosdep install --from-paths src -y --ignore-src
|
||||||
|
colcon build --symlink-install
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
GitLab может потребовать выданный организаторами аккаунт/токен. Если clone не работает, не ищите случайные зеркала: запросите доступ у эксперта.
|
||||||
|
|
||||||
|
### 4. Python-окружение YOLO и ROS
|
||||||
|
|
||||||
|
ROS-библиотеки установлены системно, поэтому venv для модуля В создаётся с `--system-site-packages`. Это также предотвращает несовместимость отдельного Python с бинарными пакетами ROS; подробнее — в [официальной памятке ROS 2 по Python-пакетам](https://docs.ros.org/en/jazzy/How-To-Guides/Using-Python-Packages.html).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch/module3
|
||||||
|
python3 -m venv --system-site-packages .venv
|
||||||
|
.venv/bin/python -m pip install -U pip
|
||||||
|
.venv/bin/pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
.venv/bin/pip install -r requirements.txt tqdm ultralytics-thop
|
||||||
|
```
|
||||||
|
|
||||||
|
В текущем образе `cv_bridge` требует NumPy 1.x:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv/bin/pip install --force-reinstall "numpy<2" "opencv-python<4.12" lark transforms3d
|
||||||
|
```
|
||||||
|
|
||||||
|
Для NVIDIA/ROCm сначала установите подходящую сборку PyTorch по [официальному селектору PyTorch](https://pytorch.org/get-started/locally/), затем остальные зависимости. Для CPU готовая команда выше достаточна.
|
||||||
|
|
||||||
|
### 5. Веб-интерфейс модуля Г
|
||||||
|
|
||||||
|
Установите [Bun](https://bun.sh/docs/installation), затем:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch/module4
|
||||||
|
bun install
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Быстрая проверка перед работой
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch-docs
|
||||||
|
./setup/check_environment.sh
|
||||||
|
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
ros2 topic list
|
||||||
|
ros2 pkg prefix ar_webots_fms_ros2
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверьте только один launch-файл за раз. После его запуска отдельно убедитесь, что приходят сообщения:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 topic hz /RMC1/odometry
|
||||||
|
ros2 topic echo /RMC1/odometry --once
|
||||||
|
ros2 topic echo /RMC2/scan_front --once
|
||||||
|
```
|
||||||
|
|
||||||
|
Точный список топиков зависит от активного launch-файла. Если `scan_front` отсутствует, сначала посмотрите `ros2 topic list | sort`, а не меняйте код вслепую.
|
||||||
|
|
||||||
|
## Запуск модулей
|
||||||
|
|
||||||
|
| Модуль | Решение | Основной запуск |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Б | навигация RMC2 по ArUco | `python3 module2/module_b.py --target 14 --sim` |
|
||||||
|
| В | YOLO + ARM95 | `module3/.venv/bin/python module3/module3.py --target hammer` |
|
||||||
|
| Г | веб-FMS двух роверов | `cd module4 && bun run start` |
|
||||||
|
| Д | общий сценарий RMC1 + RMC2 | `module3/.venv/bin/python module5/module5.py --target hammer` |
|
||||||
|
|
||||||
|
Запускайте команды из `~/scripts/mvch`. До движения используйте dry-run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 module2/module_b.py --target 14 --dry-run
|
||||||
|
module3/.venv/bin/python module3/module3.py --target hammer --dry-run --no-window
|
||||||
|
python3 module5/module5.py --target hammer --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
Для модуля Д сначала проверяйте платформы и лифт без руки: `python3 module5/module5.py --target hammer --skip-arm`.
|
||||||
|
|
||||||
|
Лимит полного финального КЗ: модуль Б — максимум 2 скрипта, В — 1, Д — 4. Текущий модуль Д использует один центральный ROS 2 процесс для обоих пространств имён и укладывается в лимит; отдельного UDP-протокола нет.
|
||||||
|
|
||||||
|
## Модель для симулятора
|
||||||
|
|
||||||
|
Текущая YOLO11n лежит в `mvch/module3/models/latest.pt`. Классы и их написание:
|
||||||
|
|
||||||
|
```text
|
||||||
|
hammer
|
||||||
|
pliers
|
||||||
|
wrench
|
||||||
|
```
|
||||||
|
|
||||||
|
Текущий симуляторный датасет очень маленький: 28 train, 8 validation и 4 test изображения. Он годится для проверки пайплайна, но не доказывает работу на реальном стенде. Для реального оборудования нужен отдельный набор кадров с его камеры, светом, фоном, ракурсами и реальными карточками/деталями. Инструкции обучения — в `mvch/module3/README.md` и [Ultralytics Train mode](https://docs.ultralytics.com/modes/train/).
|
||||||
|
|
||||||
|
## План подготовки
|
||||||
|
|
||||||
|
1. Сохранить рабочую VM и проверить clone обоих репозиториев без старых локальных файлов.
|
||||||
|
2. Для каждого launch-файла записать реальные `ros2 topic list`, стартовые ID и положения объектов.
|
||||||
|
3. Пройти модуль Б на нескольких целях и с препятствием у стеллажа; проверить остановку при потере ArUco/лидара.
|
||||||
|
4. В модуле В сначала подтвердить dry-run координат, затем pick/place на каждом классе.
|
||||||
|
5. В модуле Г проверить ручной STOP, телеметрию обоих роверов и fallback без Nav2.
|
||||||
|
6. В модуле Д пройти `--skip-arm`, затем полный последовательный сценарий.
|
||||||
|
7. Сделать чистый контрольный прогон строго по процедуре эксперта и сохранить логи.
|
||||||
|
8. На площадке сначала снять фактические топики, TF, размеры, тормозной путь и геометрию ARM95; только потом менять параметры.
|
||||||
|
|
||||||
|
## Что уточнить у главного эксперта
|
||||||
|
|
||||||
|
- Финал проходит полностью в Webots, на физических РМК или в смешанном формате?
|
||||||
|
- Какая редакция конкурсного задания и критериев считается действующей?
|
||||||
|
- Будет ли готовая VM, доступ к закрытому GitLab и разрешено ли принести свой Git-репозиторий?
|
||||||
|
- Разрешены ли интернет, локальная документация, поисковики и LLM; как технически фиксируются нарушения?
|
||||||
|
- Какие действия участника допустимы между этапами: Enter, stop/resume, выбор цели, ручное подтверждение маршрута?
|
||||||
|
- Какие точки/ID сообщаются перед попыткой, а какие должны определяться автоматически?
|
||||||
|
- Что именно входит в разрешённые 30% изменений задания?
|
||||||
|
|
||||||
|
Ответы лучше получить письменно или зафиксировать в протоколе инструктажа.
|
||||||
|
|
||||||
|
## Документация и форумы
|
||||||
|
|
||||||
|
- [ROS 2 Jazzy documentation](https://docs.ros.org/en/jazzy/)
|
||||||
|
- [ROS 2 concepts](https://docs.ros.org/en/jazzy/Concepts.html)
|
||||||
|
- [ROS 2 tf2 tutorials](https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Tf2-Main.html)
|
||||||
|
- [webots_ros2_driver API](https://docs.ros.org/en/jazzy/p/webots_ros2_driver/)
|
||||||
|
- [Webots User Guide](https://cyberbotics.com/doc/guide/index)
|
||||||
|
- [Webots Discussions](https://github.com/cyberbotics/webots/discussions)
|
||||||
|
- [MoveIt 2 documentation](https://moveit.picknik.ai/main/index.html)
|
||||||
|
- [MoveIt 2 issues](https://github.com/moveit/moveit2/issues)
|
||||||
|
- [Ultralytics documentation](https://docs.ultralytics.com/)
|
||||||
|
- [Roboflow Annotate documentation](https://docs.roboflow.com/annotate)
|
||||||
|
- [Robotics Stack Exchange: ROS 2](https://robotics.stackexchange.com/questions/tagged/ros2) — конкретные технические вопросы с логами и минимальным примером.
|
||||||
|
- [Open Robotics Discourse](https://discourse.ros.org/) — новости и общие обсуждения экосистемы; не основная площадка техподдержки.
|
||||||
|
|
||||||
|
Публичного официального форума именно по этому заданию ЧВТ в выданных материалах не указано. Вопросы о правилах, критериях, доступе к репозиториям и оборудованию адресуйте главному эксперту, а не стороннему форуму.
|
||||||
|
|
||||||
|
## Git на соревновании
|
||||||
|
|
||||||
|
Для критериев обычно достаточно локальной истории: `main`, понятные коммиты и при необходимости ветки модулей. GitHub удобен как резервная копия, но не рассчитывайте на интернет на площадке.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status
|
||||||
|
git log --oneline --decorate --graph --all -20
|
||||||
|
git switch main
|
||||||
|
```
|
||||||
|
|
||||||
|
Не выполняйте `git reset --hard`, `git clean` и force-push перед попыткой. Скопируйте оба репозитория и веса модели на внешний накопитель или в снапшот VM.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Базовые примеры для реального ROS 2
|
||||||
|
|
||||||
|
Это короткие учебные скрипты, которые работают через штатные ROS 2 интерфейсы
|
||||||
|
роверов: `cmd_vel`, `odometry`, `scan_front`, `aruco_id`, TF и `lift`. В них нет
|
||||||
|
прямого управления моторами и нет подмены симулятора. Один и тот же ROS API можно
|
||||||
|
проверять в Webots, а затем перенести на физический РМК после сверки имён топиков,
|
||||||
|
TF, размеров поля и безопасных скоростей.
|
||||||
|
|
||||||
|
Это не готовые конкурсные решения и не замена аппаратному аварийному STOP.
|
||||||
|
|
||||||
|
Перед запуском:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
Сначала проверьте реальные интерфейсы:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ros2 topic list | sort
|
||||||
|
ros2 topic info /RMC2/cmd_vel -v
|
||||||
|
ros2 topic info /RMC2/odometry -v
|
||||||
|
ros2 topic info /RMC2/scan_front -v
|
||||||
|
ros2 topic echo /RMC2/aruco_id
|
||||||
|
ros2 run tf2_ros tf2_echo RMC2/odom aruco_0
|
||||||
|
```
|
||||||
|
|
||||||
|
Практические примеры:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Проехать RMC2 от текущей метки до ArUco 14 на физическом ROS
|
||||||
|
python3 basic/drive_to_aruco.py 14
|
||||||
|
|
||||||
|
# То же в Webots; только здесь нужен --sim
|
||||||
|
python3 basic/drive_to_aruco.py 14 --sim
|
||||||
|
|
||||||
|
# Закрытые клетки и уменьшенная реальная скорость
|
||||||
|
python3 basic/drive_to_aruco.py 14 --blocked 8 9 --max-speed 0.08
|
||||||
|
|
||||||
|
# Короткая проверка движения; lidar обязателен, после 1 с будет stop
|
||||||
|
python3 basic/move_rmc.py --robot RMC1 --x 0.15 --seconds 1
|
||||||
|
|
||||||
|
# Поворот RMC2 на месте
|
||||||
|
python3 basic/move_rmc.py --robot RMC2 --yaw 0.3 --seconds 1
|
||||||
|
|
||||||
|
# Статус положения и лидара
|
||||||
|
python3 basic/watch_robot.py --robot RMC1
|
||||||
|
|
||||||
|
# Управление лифтом RMC2
|
||||||
|
python3 basic/lift_rmc2.py up
|
||||||
|
python3 basic/lift_rmc2.py down
|
||||||
|
|
||||||
|
# Остановить оба ровера
|
||||||
|
python3 basic/stop_all.py
|
||||||
|
```
|
||||||
|
|
||||||
|
`drive_to_aruco.py` рассчитан на регулярную сетку 6×6: `0=(0,0)`,
|
||||||
|
`1=(0,+1 м)`, `6=(-1 м,0)`. Ровер должен стартовать центром над видимой меткой.
|
||||||
|
Скрипт сам определяет старт, строит BFS-маршрут, печатает его до движения, затем
|
||||||
|
едет по odometry и уточняет положение через TF каждой ArUco. Без свежих odometry,
|
||||||
|
лидара, метки на финише или при препятствии ближе `--stop-distance` он отправляет
|
||||||
|
нулевой `Twist` и завершает работу.
|
||||||
|
|
||||||
|
На реальном поле сначала измерьте шаг сетки и при необходимости задайте
|
||||||
|
`--spacing`. Если организаторы используют другой топик лидара, передайте
|
||||||
|
`--scan-topic /нужный/топик`. Не используйте `--yes` в первом физическом заезде.
|
||||||
|
|
||||||
|
У RMC1 доступно боковое движение (`linear.y`), у RMC2 оно принудительно запрещено
|
||||||
|
в `move_rmc.py`. Режим `--no-lidar` предназначен только для вывешенного стенда,
|
||||||
|
где колёса не могут переместить робот.
|
||||||
|
|
||||||
|
`grid_route.py` оставлен как офлайн-проверка графа. Он ничего не публикует в ROS.
|
||||||
Executable
+346
@@ -0,0 +1,346 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Безопасно проехать на RMC по сетке до указанной ArUco-метки.
|
||||||
|
|
||||||
|
Скрипт использует штатные ROS 2 топики и TF. По умолчанию рассчитан на
|
||||||
|
физический робот; для Webots явно передайте --sim.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from geometry_msgs.msg import Twist
|
||||||
|
from nav_msgs.msg import Odometry
|
||||||
|
from rclpy.node import Node
|
||||||
|
from rclpy.parameter import Parameter
|
||||||
|
from rclpy.qos import qos_profile_sensor_data
|
||||||
|
from rclpy.time import Time
|
||||||
|
from sensor_msgs.msg import LaserScan
|
||||||
|
from std_msgs.msg import String
|
||||||
|
from tf2_ros import Buffer, TransformException, TransformListener
|
||||||
|
|
||||||
|
|
||||||
|
def clamp(value, low, high):
|
||||||
|
return max(low, min(high, value))
|
||||||
|
|
||||||
|
|
||||||
|
def wrap(angle):
|
||||||
|
return math.atan2(math.sin(angle), math.cos(angle))
|
||||||
|
|
||||||
|
|
||||||
|
def yaw(quaternion):
|
||||||
|
return math.atan2(
|
||||||
|
2 * (quaternion.w * quaternion.z + quaternion.x * quaternion.y),
|
||||||
|
1 - 2 * (quaternion.y**2 + quaternion.z**2),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compose(a, b):
|
||||||
|
"""Перенести позу b из системы a в родительскую систему a."""
|
||||||
|
cosine, sine = math.cos(a[2]), math.sin(a[2])
|
||||||
|
return (
|
||||||
|
a[0] + cosine * b[0] - sine * b[1],
|
||||||
|
a[1] + sine * b[0] + cosine * b[1],
|
||||||
|
wrap(a[2] + b[2]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def inverse(pose):
|
||||||
|
cosine, sine = math.cos(pose[2]), math.sin(pose[2])
|
||||||
|
return (
|
||||||
|
-cosine * pose[0] - sine * pose[1],
|
||||||
|
sine * pose[0] - cosine * pose[1],
|
||||||
|
-pose[2],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def marker_pose(marker, columns, spacing):
|
||||||
|
"""Штатная схема ЧВТ: 0=(0,0), 1=(0,+S), COLS=(-S,0)."""
|
||||||
|
row, column = divmod(marker, columns)
|
||||||
|
return -row * spacing, column * spacing, 0.0
|
||||||
|
|
||||||
|
|
||||||
|
def build_route(start, target, rows, columns, blocked):
|
||||||
|
queue = deque([start])
|
||||||
|
previous = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == target:
|
||||||
|
break
|
||||||
|
row, column = divmod(current, columns)
|
||||||
|
for next_row, next_column in (
|
||||||
|
(row - 1, column),
|
||||||
|
(row + 1, column),
|
||||||
|
(row, column - 1),
|
||||||
|
(row, column + 1),
|
||||||
|
):
|
||||||
|
candidate = next_row * columns + next_column
|
||||||
|
if not (0 <= next_row < rows and 0 <= next_column < columns):
|
||||||
|
continue
|
||||||
|
if candidate in blocked or candidate in previous:
|
||||||
|
continue
|
||||||
|
previous[candidate] = current
|
||||||
|
queue.append(candidate)
|
||||||
|
|
||||||
|
if target not in previous:
|
||||||
|
raise RuntimeError(f"Нет маршрута {start} -> {target}")
|
||||||
|
route = []
|
||||||
|
current = target
|
||||||
|
while current is not None:
|
||||||
|
route.append(current)
|
||||||
|
current = previous[current]
|
||||||
|
return list(reversed(route))
|
||||||
|
|
||||||
|
|
||||||
|
class ArucoDrive(Node):
|
||||||
|
def __init__(self, args):
|
||||||
|
super().__init__("mvch_basic_aruco_drive")
|
||||||
|
self.args = args
|
||||||
|
self.robot = args.robot.strip("/")
|
||||||
|
if args.sim:
|
||||||
|
self.set_parameters([Parameter("use_sim_time", value=True)])
|
||||||
|
|
||||||
|
self.odom = None
|
||||||
|
self.pose = None
|
||||||
|
self.map_from_odom = None
|
||||||
|
self.marker = None
|
||||||
|
self.marker_error = math.inf
|
||||||
|
self.front = None
|
||||||
|
self.odom_wall = 0.0
|
||||||
|
self.marker_wall = 0.0
|
||||||
|
self.scan_wall = 0.0
|
||||||
|
|
||||||
|
self.odom_frame = f"{self.robot}/odom"
|
||||||
|
self.tf_buffer = Buffer()
|
||||||
|
self.tf_listener = TransformListener(self.tf_buffer, self)
|
||||||
|
self.publisher = self.create_publisher(
|
||||||
|
Twist, f"/{self.robot}/cmd_vel", 10
|
||||||
|
)
|
||||||
|
self.create_subscription(
|
||||||
|
Odometry,
|
||||||
|
f"/{self.robot}/odometry",
|
||||||
|
self.on_odometry,
|
||||||
|
qos_profile_sensor_data,
|
||||||
|
)
|
||||||
|
self.create_subscription(
|
||||||
|
LaserScan,
|
||||||
|
args.scan_topic or f"/{self.robot}/scan_front",
|
||||||
|
self.on_scan,
|
||||||
|
qos_profile_sensor_data,
|
||||||
|
)
|
||||||
|
self.create_subscription(
|
||||||
|
String, f"/{self.robot}/aruco_id", self.on_marker, 10
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_odometry(self, message):
|
||||||
|
pose = message.pose.pose
|
||||||
|
values = (pose.position.x, pose.position.y, yaw(pose.orientation))
|
||||||
|
if all(math.isfinite(value) for value in values):
|
||||||
|
self.odom = values
|
||||||
|
self.odom_wall = time.monotonic()
|
||||||
|
|
||||||
|
def on_marker(self, message):
|
||||||
|
match = re.fullmatch(r"(?:aruco_)?(\d+)", message.data.strip())
|
||||||
|
if match:
|
||||||
|
marker = int(match.group(1))
|
||||||
|
if 0 <= marker < self.args.rows * self.args.columns:
|
||||||
|
self.marker = marker
|
||||||
|
self.marker_wall = time.monotonic()
|
||||||
|
|
||||||
|
def on_scan(self, message):
|
||||||
|
if not message.ranges or message.angle_increment == 0:
|
||||||
|
return
|
||||||
|
distances = []
|
||||||
|
for index, distance in enumerate(message.ranges):
|
||||||
|
angle = message.angle_min + index * message.angle_increment
|
||||||
|
if abs(angle) > math.radians(25):
|
||||||
|
continue
|
||||||
|
if math.isfinite(distance) and message.range_min <= distance <= message.range_max:
|
||||||
|
distances.append(distance)
|
||||||
|
# Пустое пространство lidar часто кодирует как inf. Это свежий скан,
|
||||||
|
# просто без препятствий в выбранном секторе.
|
||||||
|
self.front = min(distances, default=math.inf)
|
||||||
|
self.scan_wall = time.monotonic()
|
||||||
|
|
||||||
|
def localize(self):
|
||||||
|
"""Привязать odometry к сетке по последней видимой метке."""
|
||||||
|
if self.odom is None or self.marker is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
transform = self.tf_buffer.lookup_transform(
|
||||||
|
self.odom_frame, f"aruco_{self.marker}", Time()
|
||||||
|
).transform
|
||||||
|
except TransformException:
|
||||||
|
return
|
||||||
|
|
||||||
|
odom_marker = (
|
||||||
|
transform.translation.x,
|
||||||
|
transform.translation.y,
|
||||||
|
yaw(transform.rotation),
|
||||||
|
)
|
||||||
|
correction = compose(
|
||||||
|
marker_pose(self.marker, self.args.columns, self.args.spacing),
|
||||||
|
inverse(odom_marker),
|
||||||
|
)
|
||||||
|
new_pose = compose(correction, self.odom)
|
||||||
|
if self.pose is not None:
|
||||||
|
jump = math.hypot(new_pose[0] - self.pose[0], new_pose[1] - self.pose[1])
|
||||||
|
if jump > 0.35:
|
||||||
|
return
|
||||||
|
self.map_from_odom = correction
|
||||||
|
self.pose = new_pose
|
||||||
|
self.marker_error = math.hypot(
|
||||||
|
odom_marker[0] - self.odom[0], odom_marker[1] - self.odom[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
rclpy.spin_once(self, timeout_sec=0.05)
|
||||||
|
self.localize()
|
||||||
|
if self.map_from_odom is not None and self.odom is not None:
|
||||||
|
self.pose = compose(self.map_from_odom, self.odom)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.publisher.publish(Twist())
|
||||||
|
|
||||||
|
def wait_until_ready(self):
|
||||||
|
print("Жду odometry, scan_front, aruco_id и TF метки...")
|
||||||
|
deadline = time.monotonic() + self.args.ready_timeout
|
||||||
|
while rclpy.ok() and time.monotonic() < deadline:
|
||||||
|
self.update()
|
||||||
|
self.stop()
|
||||||
|
now = time.monotonic()
|
||||||
|
if (
|
||||||
|
self.pose is not None
|
||||||
|
and self.front is not None
|
||||||
|
and now - self.odom_wall < 1.0
|
||||||
|
and now - self.scan_wall < 1.0
|
||||||
|
and now - self.marker_wall < 1.0
|
||||||
|
):
|
||||||
|
return
|
||||||
|
raise RuntimeError(
|
||||||
|
"Датчики не готовы. Проверьте ros2 topic list, aruco_id и TF aruco_<ID>."
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_safety(self):
|
||||||
|
now = time.monotonic()
|
||||||
|
if now - self.odom_wall > 1.0:
|
||||||
|
raise RuntimeError("Потеря odometry")
|
||||||
|
if now - self.scan_wall > 1.0:
|
||||||
|
raise RuntimeError("Потеря scan_front")
|
||||||
|
if self.front < self.args.stop_distance:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Препятствие спереди: {self.front:.2f} м "
|
||||||
|
f"(< {self.args.stop_distance:.2f} м)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def drive_to(self, target):
|
||||||
|
target_pose = marker_pose(target, self.args.columns, self.args.spacing)
|
||||||
|
deadline = time.monotonic() + self.args.waypoint_timeout
|
||||||
|
|
||||||
|
while rclpy.ok() and time.monotonic() < deadline:
|
||||||
|
self.update()
|
||||||
|
self.check_safety()
|
||||||
|
dx = target_pose[0] - self.pose[0]
|
||||||
|
dy = target_pose[1] - self.pose[1]
|
||||||
|
distance = math.hypot(dx, dy)
|
||||||
|
marker_is_fresh = (
|
||||||
|
self.marker == target
|
||||||
|
and time.monotonic() - self.marker_wall < 1.0
|
||||||
|
and self.marker_error <= self.args.tolerance
|
||||||
|
)
|
||||||
|
if distance <= self.args.tolerance and marker_is_fresh:
|
||||||
|
self.stop()
|
||||||
|
print(f"Метка {target} достигнута, ошибка {self.marker_error:.3f} м")
|
||||||
|
return
|
||||||
|
|
||||||
|
angle_error = wrap(math.atan2(dy, dx) - self.pose[2])
|
||||||
|
command = Twist()
|
||||||
|
if abs(angle_error) > 0.15:
|
||||||
|
command.angular.z = clamp(
|
||||||
|
1.4 * angle_error,
|
||||||
|
-self.args.max_angular,
|
||||||
|
self.args.max_angular,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
command.linear.x = min(self.args.max_speed, 0.5 * distance)
|
||||||
|
command.angular.z = clamp(
|
||||||
|
1.2 * angle_error,
|
||||||
|
-self.args.max_angular,
|
||||||
|
self.args.max_angular,
|
||||||
|
)
|
||||||
|
self.publisher.publish(command)
|
||||||
|
|
||||||
|
raise RuntimeError(f"Таймаут движения к метке {target}")
|
||||||
|
|
||||||
|
|
||||||
|
def arguments():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("target", type=int, help="ID целевой ArUco")
|
||||||
|
parser.add_argument("--robot", default="RMC2", help="RMC1 или RMC2")
|
||||||
|
parser.add_argument("--rows", type=int, default=6)
|
||||||
|
parser.add_argument("--columns", type=int, default=6)
|
||||||
|
parser.add_argument("--spacing", type=float, default=1.0, help="Шаг сетки, м")
|
||||||
|
parser.add_argument("--blocked", type=int, nargs="*", default=[])
|
||||||
|
parser.add_argument("--max-speed", type=float, default=0.12, help="м/с")
|
||||||
|
parser.add_argument("--max-angular", type=float, default=0.30, help="рад/с")
|
||||||
|
parser.add_argument("--stop-distance", type=float, default=0.55, help="м")
|
||||||
|
parser.add_argument("--tolerance", type=float, default=0.06, help="м")
|
||||||
|
parser.add_argument("--ready-timeout", type=float, default=15.0, help="с")
|
||||||
|
parser.add_argument("--waypoint-timeout", type=float, default=45.0, help="с")
|
||||||
|
parser.add_argument("--scan-topic", help="Например /RMC2/scan_front")
|
||||||
|
parser.add_argument("--yes", action="store_true", help="Не ждать Enter")
|
||||||
|
parser.add_argument("--sim", action="store_true", help="Использовать /clock Webots")
|
||||||
|
args = parser.parse_args()
|
||||||
|
count = args.rows * args.columns
|
||||||
|
if args.rows <= 0 or args.columns <= 0 or not 0 <= args.target < count:
|
||||||
|
parser.error(f"target должен быть в диапазоне 0..{count - 1}")
|
||||||
|
if args.spacing <= 0 or args.max_speed <= 0 or args.max_angular <= 0:
|
||||||
|
parser.error("spacing и скорости должны быть больше нуля")
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = arguments()
|
||||||
|
rclpy.init()
|
||||||
|
node = ArucoDrive(args)
|
||||||
|
exit_code = 0
|
||||||
|
try:
|
||||||
|
node.wait_until_ready()
|
||||||
|
start = node.marker
|
||||||
|
blocked = set(args.blocked) - {start, args.target}
|
||||||
|
route = build_route(
|
||||||
|
start, args.target, args.rows, args.columns, blocked
|
||||||
|
)
|
||||||
|
print(f"Стартовая метка: {start}")
|
||||||
|
print("Маршрут:", " -> ".join(map(str, route)))
|
||||||
|
print(
|
||||||
|
f"Топики: /{node.robot}/cmd_vel, /{node.robot}/odometry, "
|
||||||
|
f"/{node.robot}/aruco_id, {args.scan_topic or f'/{node.robot}/scan_front'}"
|
||||||
|
)
|
||||||
|
if not args.yes:
|
||||||
|
input("Проверьте поле и держите аппаратный STOP. Enter — начать: ")
|
||||||
|
# Во время input ROS не обрабатывал сообщения: перед движением снова
|
||||||
|
# получить свежие odometry/lidar/ArUco.
|
||||||
|
node.wait_until_ready()
|
||||||
|
for marker in route[1:]:
|
||||||
|
node.drive_to(marker)
|
||||||
|
node.stop()
|
||||||
|
print(f"FINISHED: ровер стоит над ArUco {args.target}")
|
||||||
|
except (KeyboardInterrupt, RuntimeError) as error:
|
||||||
|
exit_code = 1
|
||||||
|
print(f"STOP: {error}")
|
||||||
|
finally:
|
||||||
|
for _ in range(10):
|
||||||
|
node.stop()
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.02)
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
raise SystemExit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Строит кратчайший маршрут по сетке маркеров 6x6 без ROS."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
|
SIZE = 6
|
||||||
|
|
||||||
|
|
||||||
|
def neighbors(marker: int):
|
||||||
|
row, column = divmod(marker, SIZE)
|
||||||
|
for dr, dc in ((-1, 0), (1, 0), (0, -1), (0, 1)):
|
||||||
|
next_row, next_column = row + dr, column + dc
|
||||||
|
if 0 <= next_row < SIZE and 0 <= next_column < SIZE:
|
||||||
|
yield next_row * SIZE + next_column
|
||||||
|
|
||||||
|
|
||||||
|
def build_route(start: int, target: int, blocked: set[int]):
|
||||||
|
queue = deque([start])
|
||||||
|
previous = {start: None}
|
||||||
|
|
||||||
|
while queue:
|
||||||
|
current = queue.popleft()
|
||||||
|
if current == target:
|
||||||
|
break
|
||||||
|
for candidate in neighbors(current):
|
||||||
|
if candidate not in blocked and candidate not in previous:
|
||||||
|
previous[candidate] = current
|
||||||
|
queue.append(candidate)
|
||||||
|
|
||||||
|
if target not in previous:
|
||||||
|
return None
|
||||||
|
|
||||||
|
route = []
|
||||||
|
current = target
|
||||||
|
while current is not None:
|
||||||
|
route.append(current)
|
||||||
|
current = previous[current]
|
||||||
|
return list(reversed(route))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("start", type=int, help="Стартовый маркер 0..35")
|
||||||
|
parser.add_argument("target", type=int, help="Целевой маркер 0..35")
|
||||||
|
parser.add_argument(
|
||||||
|
"--blocked",
|
||||||
|
type=int,
|
||||||
|
nargs="*",
|
||||||
|
default=[],
|
||||||
|
help="Маркеры, через которые нельзя ехать",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not 0 <= args.start < SIZE * SIZE or not 0 <= args.target < SIZE * SIZE:
|
||||||
|
parser.error("start и target должны быть от 0 до 35")
|
||||||
|
|
||||||
|
blocked = set(args.blocked)
|
||||||
|
blocked.discard(args.start)
|
||||||
|
blocked.discard(args.target)
|
||||||
|
route = build_route(args.start, args.target, blocked)
|
||||||
|
if route is None:
|
||||||
|
raise SystemExit("Маршрут не найден")
|
||||||
|
print(" -> ".join(map(str, route)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Поднимает или опускает лифт RMC2 через штатный ROS-топик."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from rclpy.node import Node
|
||||||
|
from std_msgs.msg import Float64, String
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("state", choices=("up", "down"))
|
||||||
|
parser.add_argument("--wait", type=float, default=6.0, help="Сколько ждать статус, с")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
rclpy.init()
|
||||||
|
node = Node("mvch_basic_lift")
|
||||||
|
publisher = node.create_publisher(Float64, "/RMC2/lift", 10)
|
||||||
|
statuses = []
|
||||||
|
node.create_subscription(String, "/RMC2/lift_status", lambda msg: statuses.append(msg.data), 10)
|
||||||
|
|
||||||
|
height = 0.1 if args.state == "up" else 0.0
|
||||||
|
print(f"RMC2 lift: {args.state}, команда {height}")
|
||||||
|
for _ in range(5):
|
||||||
|
publisher.publish(Float64(data=height))
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.1)
|
||||||
|
|
||||||
|
deadline = time.monotonic() + args.wait
|
||||||
|
while time.monotonic() < deadline and not statuses:
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.1)
|
||||||
|
|
||||||
|
if statuses:
|
||||||
|
print(f"Статус: {statuses[-1]}")
|
||||||
|
else:
|
||||||
|
print("Нового lift_status нет. Это возможно, если лифт уже был в нужном положении.")
|
||||||
|
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Подаёт ограниченную по времени ROS-команду скорости и затем останавливает RMC."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from geometry_msgs.msg import Twist
|
||||||
|
from rclpy.node import Node
|
||||||
|
from rclpy.qos import qos_profile_sensor_data
|
||||||
|
from sensor_msgs.msg import LaserScan
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--robot", choices=("RMC1", "RMC2"), required=True)
|
||||||
|
parser.add_argument("--x", type=float, default=0.0, help="Скорость вперёд, м/с")
|
||||||
|
parser.add_argument("--y", type=float, default=0.0, help="Боковая скорость RMC1, м/с")
|
||||||
|
parser.add_argument("--yaw", type=float, default=0.0, help="Угловая скорость, рад/с")
|
||||||
|
parser.add_argument("--seconds", type=float, default=1.0)
|
||||||
|
parser.add_argument("--stop-distance", type=float, default=0.55, help="Стоп по lidar, м")
|
||||||
|
parser.add_argument("--no-lidar", action="store_true", help="Только стенд без препятствий")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.seconds <= 0:
|
||||||
|
parser.error("--seconds должен быть больше нуля")
|
||||||
|
if args.robot == "RMC2" and args.y != 0:
|
||||||
|
parser.error("RMC2 не поддерживает боковое движение: используйте --y 0")
|
||||||
|
|
||||||
|
rclpy.init()
|
||||||
|
node = Node("mvch_basic_move")
|
||||||
|
publisher = node.create_publisher(Twist, f"/{args.robot}/cmd_vel", 10)
|
||||||
|
front_distance = [None]
|
||||||
|
|
||||||
|
def on_scan(message):
|
||||||
|
if not message.ranges or message.angle_increment == 0:
|
||||||
|
return
|
||||||
|
front = []
|
||||||
|
for index, distance in enumerate(message.ranges):
|
||||||
|
angle = message.angle_min + index * message.angle_increment
|
||||||
|
if abs(angle) <= 0.44 and math.isfinite(distance):
|
||||||
|
if message.range_min <= distance <= message.range_max:
|
||||||
|
front.append(distance)
|
||||||
|
front_distance[0] = min(front, default=math.inf)
|
||||||
|
|
||||||
|
if not args.no_lidar:
|
||||||
|
node.create_subscription(
|
||||||
|
LaserScan,
|
||||||
|
f"/{args.robot}/scan_front",
|
||||||
|
on_scan,
|
||||||
|
qos_profile_sensor_data,
|
||||||
|
)
|
||||||
|
command = Twist()
|
||||||
|
command.linear.x = args.x
|
||||||
|
command.linear.y = args.y
|
||||||
|
command.angular.z = args.yaw
|
||||||
|
|
||||||
|
print(f"{args.robot}: cmd_vel x={args.x}, y={args.y}, yaw={args.yaw}")
|
||||||
|
if not args.no_lidar:
|
||||||
|
print(f"Жду /{args.robot}/scan_front...")
|
||||||
|
deadline = time.monotonic() + 3.0
|
||||||
|
while front_distance[0] is None and time.monotonic() < deadline:
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.1)
|
||||||
|
if front_distance[0] is None:
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
raise SystemExit("Нет scan_front: движение запрещено. Для стенда: --no-lidar")
|
||||||
|
started = time.monotonic()
|
||||||
|
exit_code = 0
|
||||||
|
try:
|
||||||
|
while time.monotonic() - started < args.seconds:
|
||||||
|
if front_distance[0] is not None and front_distance[0] < args.stop_distance:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Препятствие {front_distance[0]:.2f} м — аварийный стоп"
|
||||||
|
)
|
||||||
|
publisher.publish(command)
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.05)
|
||||||
|
except RuntimeError as error:
|
||||||
|
exit_code = 1
|
||||||
|
print(f"STOP: {error}")
|
||||||
|
finally:
|
||||||
|
stop = Twist()
|
||||||
|
for _ in range(5):
|
||||||
|
publisher.publish(stop)
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.05)
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
raise SystemExit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Публикует нулевую скорость обоим роверам."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from geometry_msgs.msg import Twist
|
||||||
|
from rclpy.node import Node
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
rclpy.init()
|
||||||
|
node = Node("mvch_basic_stop_all")
|
||||||
|
publishers = [
|
||||||
|
node.create_publisher(Twist, "/RMC1/cmd_vel", 10),
|
||||||
|
node.create_publisher(Twist, "/RMC2/cmd_vel", 10),
|
||||||
|
]
|
||||||
|
stop = Twist()
|
||||||
|
for _ in range(10):
|
||||||
|
for publisher in publishers:
|
||||||
|
publisher.publish(stop)
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.05)
|
||||||
|
time.sleep(0.02)
|
||||||
|
print("Нулевая скорость отправлена RMC1 и RMC2")
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Показывает одометрию и минимальную дистанцию перед ровером."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import time
|
||||||
|
|
||||||
|
import rclpy
|
||||||
|
from nav_msgs.msg import Odometry
|
||||||
|
from rclpy.node import Node
|
||||||
|
from rclpy.qos import qos_profile_sensor_data
|
||||||
|
from sensor_msgs.msg import LaserScan
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--robot", choices=("RMC1", "RMC2"), required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
rclpy.init()
|
||||||
|
node = Node("mvch_basic_watch")
|
||||||
|
state = {"pose": None, "front": None}
|
||||||
|
|
||||||
|
def on_odom(message):
|
||||||
|
position = message.pose.pose.position
|
||||||
|
orientation = message.pose.pose.orientation
|
||||||
|
siny = 2.0 * (orientation.w * orientation.z + orientation.x * orientation.y)
|
||||||
|
cosy = 1.0 - 2.0 * (orientation.y**2 + orientation.z**2)
|
||||||
|
state["pose"] = (position.x, position.y, math.atan2(siny, cosy))
|
||||||
|
|
||||||
|
def on_scan(message):
|
||||||
|
front = []
|
||||||
|
for index, distance in enumerate(message.ranges):
|
||||||
|
angle = message.angle_min + index * message.angle_increment
|
||||||
|
if abs(angle) <= math.radians(20) and math.isfinite(distance):
|
||||||
|
if message.range_min <= distance <= message.range_max:
|
||||||
|
front.append(distance)
|
||||||
|
state["front"] = min(front, default=math.inf)
|
||||||
|
|
||||||
|
node.create_subscription(
|
||||||
|
Odometry, f"/{args.robot}/odometry", on_odom, qos_profile_sensor_data
|
||||||
|
)
|
||||||
|
node.create_subscription(
|
||||||
|
LaserScan, f"/{args.robot}/scan_front", on_scan, qos_profile_sensor_data
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Слушаю {args.robot}. Ctrl+C — выход.")
|
||||||
|
last_print = 0.0
|
||||||
|
try:
|
||||||
|
while rclpy.ok():
|
||||||
|
rclpy.spin_once(node, timeout_sec=0.1)
|
||||||
|
if time.monotonic() - last_print >= 0.5:
|
||||||
|
pose = state["pose"]
|
||||||
|
front = state["front"]
|
||||||
|
pose_text = "нет odometry" if pose is None else (
|
||||||
|
f"x={pose[0]:.2f}, y={pose[1]:.2f}, yaw={math.degrees(pose[2]):.1f}°"
|
||||||
|
)
|
||||||
|
front_text = "нет scan_front" if front is None else f"{front:.2f} м"
|
||||||
|
print(f"{pose_text}; спереди: {front_text}")
|
||||||
|
last_print = time.monotonic()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
node.destroy_node()
|
||||||
|
rclpy.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,132 @@
|
|||||||
|
ПРОГРАММА ПРОВЕДЕНИЯ
|
||||||
|
соревнований по компетенции
|
||||||
|
Оператор-техник складских роботов (AGV/AMR)
|
||||||
|
Финала Чемпионата высоких технологий в 2026 г
|
||||||
|
Великий Новгород
|
||||||
|
Общая информация
|
||||||
|
|
||||||
|
Период проведения 13-18 сентября
|
||||||
|
|
||||||
|
Место проведения и адрес площадки Великий Новгород
|
||||||
|
|
||||||
|
ФИО Главного эксперта Даукаев Руслан Ильнурович
|
||||||
|
|
||||||
|
Контакты Главного эксперта 79856178554
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Д-2 / «13» сентября 2026 г.
|
||||||
|
|
||||||
|
09:00 – 09:30 Регистрация экспертов на площадке: сверка паспортов, подписание протокола.
|
||||||
|
09:30 – 10:00 Проведение инструктажа по охране труда, подписание протокола.
|
||||||
|
10:00 – 12:00 Ознакомление экспертов с нормативной документацией.
|
||||||
|
12:00 – 13:00 Обед
|
||||||
|
Ознакомление экспертов с конкурсной документацией.
|
||||||
|
Внесение 30% изменений в конкурсное задание.
|
||||||
|
13:00 – 15:00
|
||||||
|
Обсуждение критериев оценки, загрузка схемы оценки в ЦСО, блокировка.
|
||||||
|
Подписание протоколов.
|
||||||
|
Проведение специальной подготовки экспертов для обеспечения высокого
|
||||||
|
15:00 – 16:00 качества, профессионализма и соответствия процесса оценки правилам и
|
||||||
|
процедурам.
|
||||||
|
Распределение ролей главным экспертом на чемпионате: оценивающие эксперты;
|
||||||
|
эксперт ответственный за охрану труда на площадке проведения чемпионатного
|
||||||
|
16:00 – 16:30 мероприятия по компетенции; эксперт, ответственный за взаимодействие с
|
||||||
|
посетителями и представителями СМИ и др.
|
||||||
|
Подписание протоколов.
|
||||||
|
Формирование оценочных групп в ЦСО.
|
||||||
|
16:30 – 18:00 Подготовка ведомостей оценок.
|
||||||
|
Подписание протоколов.
|
||||||
|
18:00 – 19:00 Ужин
|
||||||
|
Д-1 / «14» сентября 2026 г.
|
||||||
|
|
||||||
|
09:00 – 09:30 Регистрация конкурсантов на площадке: сверка паспортов, подписание протокола.
|
||||||
|
|
||||||
|
09:30 – 10:00 Проведение инструктажа по охране труда, подписание протокола.
|
||||||
|
10:00 – 10:30 Ознакомление конкурсантов с нормативной документацией.
|
||||||
|
Ознакомление конкурсантов с актуализированной конкурсной документацией.
|
||||||
|
10:30 – 11:00
|
||||||
|
Подписание протоколов.
|
||||||
|
Жеребьевка рабочих мест.
|
||||||
|
11:00 – 12:00
|
||||||
|
Ознакомление конкурсантов с рабочими местами, тестирование оборудования.
|
||||||
|
12:00 – 13:00 Обед
|
||||||
|
Ознакомление конкурсантов с рабочими местами, тестирование оборудования.
|
||||||
|
13:00 – 17:00
|
||||||
|
Подписание протоколов.
|
||||||
|
17:00 – 19:00 Церемония открытия
|
||||||
|
19:30 – 20:30 Ужин
|
||||||
|
Д1 / «15» сентября 2026 г.
|
||||||
|
08:00 – 08:10 Прибытие на площадку экспертов и конкурсантов.
|
||||||
|
08:10 – 08:30 Проведение инструктажа по охране труда, подписание протокола.
|
||||||
|
Ознакомление конкурсантов с конкурсным заданием, подписание протокола.
|
||||||
|
08:30 – 09:00
|
||||||
|
Брифинг с Главным экспертом.
|
||||||
|
Выполнение конкурсантами задания, модуль А. Диагностика и отладка
|
||||||
|
09:00 – 10:00
|
||||||
|
электронных компонентов
|
||||||
|
Выполнение конкурсантами задания, модуль Б. Разработка и настройка
|
||||||
|
10:00 – 13:00
|
||||||
|
навигационных алгоритмов для РМК
|
||||||
|
13:00 – 14:00 Обед
|
||||||
|
Выполнение конкурсантами задания, модуль Б. Разработка и настройка
|
||||||
|
14:00 – 17:00
|
||||||
|
навигационных алгоритмов для РМК
|
||||||
|
|
||||||
|
Проведение экспертами оценки работ конкурсантов.
|
||||||
|
17:00 – 18:00
|
||||||
|
Занесение оценок в ЦСО.
|
||||||
|
|
||||||
|
18:00 – 19:00 Ужин
|
||||||
|
Д2 / «16» сентября 2026 г.
|
||||||
|
08:00 – 08:10 Прибытие на площадку экспертов и конкурсантов.
|
||||||
|
08:10 – 08:30 Проведение инструктажа по охране труда, подписание протокола.
|
||||||
|
Ознакомление конкурсантов с конкурсным заданием, подписание протокола.
|
||||||
|
08:30 – 09:00
|
||||||
|
Брифинг с Главным экспертом.
|
||||||
|
Выполнение конкурсантами задания, модуль В. Системы технического зрения с
|
||||||
|
09:00 – 14:00
|
||||||
|
использованием инструментов искусственного интеллекта
|
||||||
|
14:00 – 15:00 Обед
|
||||||
|
15:00 – 17:00 Выполнение конкурсантами задания, модуль Г. Разработка и тестирование
|
||||||
|
|
||||||
|
1
|
||||||
|
графического интерфейса (FMS-системы) для управления и визуализации работы
|
||||||
|
РМК и их группы
|
||||||
|
Проведение экспертами оценки работ конкурсантов.
|
||||||
|
17:00 – 18:00
|
||||||
|
Занесение оценок в ЦСО.
|
||||||
|
18:00 – 19:00 Ужин
|
||||||
|
Д3 / «17» сентября 2026 г.
|
||||||
|
08:00 – 08:10 Прибытие на площадку экспертов и конкурсантов.
|
||||||
|
08:10 – 08:30 Проведение инструктажа по охране труда, подписание протокола.
|
||||||
|
Ознакомление конкурсантов с конкурсным заданием, подписание протокола.
|
||||||
|
08:30 – 09:00
|
||||||
|
Брифинг с Главным экспертом.
|
||||||
|
Выполнение конкурсантами задания, модуль Г. Разработка и тестирование
|
||||||
|
09:00 – 11:00 графического интерфейса (FMS-системы) для управления и визуализации работы
|
||||||
|
РМК и их группы
|
||||||
|
Выполнение конкурсантами задания, модуль Д. Решение складского кейса
|
||||||
|
11:00 – 13:00
|
||||||
|
гетерогенной группой РМК
|
||||||
|
13:00 – 14:00 Обед
|
||||||
|
Выполнение конкурсантами задания, модуль Д. Решение складского кейса
|
||||||
|
14:00 – 18:00
|
||||||
|
гетерогенной группой РМК
|
||||||
|
18:00 – 19:00 Ужин
|
||||||
|
Проведение экспертами оценки работ конкурсантов.
|
||||||
|
Занесение оценок в ЦСО. Блокировка оценок.
|
||||||
|
19:00 – 21:00
|
||||||
|
Сверка ведомостей экспертами-наставниками.
|
||||||
|
Подведение итогов.
|
||||||
|
Д+1 / «18» сентября 2026 г.
|
||||||
|
09:00 – 14:00 Активности на площадке
|
||||||
|
14:00 – 16:00 Демонтаж площадки
|
||||||
|
16:00 – 19:00 Церемония награждения
|
||||||
|
19:00 – 20:00 Ужин
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,215 @@
|
|||||||
|
# Команды установки
|
||||||
|
|
||||||
|
Краткая шпаргалка без пояснений. Подробности и ограничения: [`FROM_ZERO.md`](FROM_ZERO.md).
|
||||||
|
|
||||||
|
## Windows PowerShell (администратор)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
winget install --id Microsoft.WindowsTerminal -e
|
||||||
|
winget install --id Microsoft.PowerShell -e
|
||||||
|
winget install --id Git.Git -e
|
||||||
|
winget install --id Microsoft.VisualStudioCode -e
|
||||||
|
winget install --id OpenJS.NodeJS.LTS -e
|
||||||
|
|
||||||
|
git --version
|
||||||
|
code --version
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
|
||||||
|
wsl --install -d Ubuntu-24.04
|
||||||
|
```
|
||||||
|
|
||||||
|
## VS Code extensions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
code --install-extension ms-python.python
|
||||||
|
code --install-extension ms-python.vscode-pylance
|
||||||
|
code --install-extension ms-iot.vscode-ros
|
||||||
|
code --install-extension ms-vscode.cpptools
|
||||||
|
code --install-extension ms-vscode.cmake-tools
|
||||||
|
code --install-extension redhat.vscode-yaml
|
||||||
|
code --install-extension bradlc.vscode-tailwindcss
|
||||||
|
code --install-extension dbaeumer.vscode-eslint
|
||||||
|
code --install-extension esbenp.prettier-vscode
|
||||||
|
code --install-extension ms-vscode-remote.remote-wsl
|
||||||
|
code --install-extension ms-vscode-remote.remote-ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ubuntu 24.04: база и VS Code
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y \
|
||||||
|
git curl wget unzip ca-certificates gnupg locales software-properties-common \
|
||||||
|
build-essential python3 python3-pip python3-venv python3-dev \
|
||||||
|
mesa-utils libxcb-cursor0 libxcb-xinerama0
|
||||||
|
sudo snap install --classic code
|
||||||
|
|
||||||
|
git --version
|
||||||
|
python3 --version
|
||||||
|
code --version
|
||||||
|
glxinfo -B
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git и SSH
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --global user.name "Nikita Shilov"
|
||||||
|
git config --global user.email "YOUR_EMAIL"
|
||||||
|
git config --global init.defaultBranch main
|
||||||
|
ssh-keygen -t ed25519 -C "chvt-2026"
|
||||||
|
eval "$(ssh-agent -s)"
|
||||||
|
ssh-add ~/.ssh/id_ed25519
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
|
||||||
|
mkdir -p ~/scripts
|
||||||
|
git clone git@github.com:mavxa/mvch.git ~/scripts/mvch
|
||||||
|
git clone git@github.com:mavxa/mvch-docs.git ~/scripts/mvch-docs
|
||||||
|
|
||||||
|
git -C ~/scripts/mvch remote add gitea ssh://git@GITEA_HOST/USER/mvch.git
|
||||||
|
git -C ~/scripts/mvch push gitea --all
|
||||||
|
git -C ~/scripts/mvch push gitea --tags
|
||||||
|
git -C ~/scripts/mvch-docs remote add gitea ssh://git@GITEA_HOST/USER/mvch-docs.git
|
||||||
|
git -C ~/scripts/mvch-docs push gitea --all
|
||||||
|
git -C ~/scripts/mvch-docs push gitea --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
## ROS 2 Jazzy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo locale-gen en_US en_US.UTF-8
|
||||||
|
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
|
||||||
|
sudo add-apt-repository universe
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
export ROS_APT_SOURCE_VERSION="$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F 'tag_name' | awk -F\" '{print $4}')"
|
||||||
|
curl -L -o /tmp/ros2-apt-source.deb \
|
||||||
|
"https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo ${UBUNTU_CODENAME:-${VERSION_CODENAME}})_all.deb"
|
||||||
|
sudo dpkg -i /tmp/ros2-apt-source.deb
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y ros-jazzy-desktop ros-dev-tools \
|
||||||
|
ros-jazzy-rmw-fastrtps-cpp python3-colcon-common-extensions python3-rosdep
|
||||||
|
|
||||||
|
sudo rosdep init
|
||||||
|
rosdep update
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Webots
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
sudo wget -q https://cyberbotics.com/Cyberbotics.asc \
|
||||||
|
-O /etc/apt/keyrings/Cyberbotics.asc
|
||||||
|
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/Cyberbotics.asc] https://cyberbotics.com/debian binary-amd64/" \
|
||||||
|
| sudo tee /etc/apt/sources.list.d/Cyberbotics.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y webots
|
||||||
|
which webots
|
||||||
|
readlink -f "$(command -v webots)"
|
||||||
|
webots --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## ROS-пакеты ЧВТ
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/ros2_ws/src
|
||||||
|
cd ~/ros2_ws/src
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_webots_fms_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_arm95_moveit_config.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_aruco_detect_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_dual_lidar_merge_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_nav_ros2.git
|
||||||
|
|
||||||
|
cd ~/ros2_ws
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
rosdep install --from-paths src -y --ignore-src --rosdistro jazzy
|
||||||
|
colcon build --symlink-install
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Python/YOLO
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch/module3
|
||||||
|
python3 -m venv --system-site-packages .venv
|
||||||
|
.venv/bin/python -m pip install -U pip
|
||||||
|
.venv/bin/pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
.venv/bin/pip install -r requirements.txt tqdm ultralytics-thop
|
||||||
|
.venv/bin/pip install --force-reinstall \
|
||||||
|
"numpy<2" "opencv-python<4.12" lark transforms3d
|
||||||
|
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
.venv/bin/python -c \
|
||||||
|
"import rclpy, cv_bridge, numpy, torch, ultralytics; print('Python/ROS/YOLO OK')"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bun, module4 и Node.js
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://bun.com/install | bash
|
||||||
|
export BUN_INSTALL="$HOME/.bun"
|
||||||
|
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||||
|
bun --version
|
||||||
|
|
||||||
|
cd ~/scripts/mvch/module4
|
||||||
|
bun install
|
||||||
|
bun run check
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||||
|
nvm install --lts
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## OpenCode (необязательно)
|
||||||
|
|
||||||
|
Ubuntu/Linux:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://opencode.ai/install | bash
|
||||||
|
source ~/.bashrc
|
||||||
|
opencode --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install -g opencode-ai
|
||||||
|
opencode --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Проверка окружения и топиков
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch-docs
|
||||||
|
./setup/check_environment.sh
|
||||||
|
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
|
||||||
|
|
||||||
|
ros2 node list
|
||||||
|
ros2 topic list | sort
|
||||||
|
ros2 topic echo /RMC1/odometry --once
|
||||||
|
ros2 topic echo /RMC2/odometry --once
|
||||||
|
ros2 topic echo /RMC2/scan_front --once
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git bundle на флешку
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/chvt-backup
|
||||||
|
git -C ~/scripts/mvch bundle create ~/chvt-backup/mvch.bundle --all
|
||||||
|
git -C ~/scripts/mvch-docs bundle create ~/chvt-backup/mvch-docs.bundle --all
|
||||||
|
git bundle verify ~/chvt-backup/mvch.bundle
|
||||||
|
git bundle verify ~/chvt-backup/mvch-docs.bundle
|
||||||
|
```
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
# Подготовка рабочего места с нуля
|
||||||
|
|
||||||
|
Цель — за день настройки получить воспроизводимое рабочее место для физических РМК и Webots. Основная среда проекта: **Ubuntu 24.04 x86-64, ROS 2 Jazzy, Webots, Fast DDS, Python 3 и Git**.
|
||||||
|
|
||||||
|
Все команды без объяснений продублированы в [`COMMANDS.md`](COMMANDS.md).
|
||||||
|
|
||||||
|
## Сначала уточнить у эксперта
|
||||||
|
|
||||||
|
До установки и обновлений зафиксируйте:
|
||||||
|
|
||||||
|
1. Какая ОС и что уже установлено.
|
||||||
|
2. Нужно ли использовать готовый образ организаторов.
|
||||||
|
3. Какую версию Webots требует выданный пакет.
|
||||||
|
4. Адреса GitLab/локального Git, логин и доступ к интернету.
|
||||||
|
5. `ROS_DOMAIN_ID`, RMW и схему сети физических РМК.
|
||||||
|
6. Разрешённые программы, сайты и LLM.
|
||||||
|
|
||||||
|
Не обновляйте целиком уже работающее окружение непосредственно перед попыткой.
|
||||||
|
|
||||||
|
## Если выдали Windows
|
||||||
|
|
||||||
|
Windows используем как хост для редактора, Git и резервного доступа. Конкурсный ROS-стек из материалов рассчитан на Ubuntu 24.04. Нативную сборку всех пакетов под Windows нельзя считать равноценной без подтверждения организаторов.
|
||||||
|
|
||||||
|
Откройте PowerShell от администратора:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
winget install --id Microsoft.WindowsTerminal -e
|
||||||
|
winget install --id Microsoft.PowerShell -e
|
||||||
|
winget install --id Git.Git -e
|
||||||
|
winget install --id Microsoft.VisualStudioCode -e
|
||||||
|
winget install --id OpenJS.NodeJS.LTS -e
|
||||||
|
```
|
||||||
|
|
||||||
|
После установки закройте и снова откройте терминал:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git --version
|
||||||
|
code --version
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Для вспомогательной Linux-среды можно установить Ubuntu 24.04 в WSL:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
wsl --install -d Ubuntu-24.04
|
||||||
|
```
|
||||||
|
|
||||||
|
После команды требуется перезапуск Windows. WSL подходит для Git, Python, Bun и редактирования, но DDS discovery, multicast, USB-камера и графический Webots могут отличаться от обычного Ubuntu. Для физического заезда предпочтительны нативный Ubuntu либо выданная Ubuntu VM с bridged-сетью и проверенным OpenGL.
|
||||||
|
|
||||||
|
Если используется Ubuntu VM, выделите не менее 4 CPU, 8 ГБ RAM и 50 ГБ диска. Включайте 3D-ускорение только после проверки стабильности конкретного гипервизора.
|
||||||
|
|
||||||
|
## Если выдали Ubuntu 24.04
|
||||||
|
|
||||||
|
### Базовые программы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y \
|
||||||
|
git curl wget unzip ca-certificates gnupg locales software-properties-common \
|
||||||
|
build-essential python3 python3-pip python3-venv python3-dev \
|
||||||
|
mesa-utils libxcb-cursor0 libxcb-xinerama0
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка GPU:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
glxinfo -B
|
||||||
|
```
|
||||||
|
|
||||||
|
В `OpenGL renderer` не должно быть `llvmpipe`, если Webots должен работать с аппаратным ускорением.
|
||||||
|
|
||||||
|
### Visual Studio Code
|
||||||
|
|
||||||
|
Короткий официальный вариант для Ubuntu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo snap install --classic code
|
||||||
|
code --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Snap здесь используется только для VS Code. Webots устанавливайте через `.deb`/APT: Snap-версия Webots уже создавала проблемы с ROS и Qt.
|
||||||
|
|
||||||
|
Минимальные расширения:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
code --install-extension ms-python.python
|
||||||
|
code --install-extension ms-python.vscode-pylance
|
||||||
|
code --install-extension ms-iot.vscode-ros
|
||||||
|
code --install-extension ms-vscode.cpptools
|
||||||
|
code --install-extension ms-vscode.cmake-tools
|
||||||
|
code --install-extension redhat.vscode-yaml
|
||||||
|
code --install-extension bradlc.vscode-tailwindcss
|
||||||
|
code --install-extension dbaeumer.vscode-eslint
|
||||||
|
code --install-extension esbenp.prettier-vscode
|
||||||
|
```
|
||||||
|
|
||||||
|
Для Windows/WSL дополнительно:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
code --install-extension ms-vscode-remote.remote-wsl
|
||||||
|
code --install-extension ms-vscode-remote.remote-ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git и доступ к репозиториям
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config --global user.name "Nikita Shilov"
|
||||||
|
git config --global user.email "YOUR_EMAIL"
|
||||||
|
git config --global init.defaultBranch main
|
||||||
|
|
||||||
|
ssh-keygen -t ed25519 -C "chvt-2026"
|
||||||
|
eval "$(ssh-agent -s)"
|
||||||
|
ssh-add ~/.ssh/id_ed25519
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
Добавьте **публичный** ключ в GitHub/Gitea. Приватный `~/.ssh/id_ed25519` никуда не отправляйте и не коммитьте.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/scripts
|
||||||
|
git clone git@github.com:mavxa/mvch.git ~/scripts/mvch
|
||||||
|
git clone git@github.com:mavxa/mvch-docs.git ~/scripts/mvch-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
Если GitHub недоступен, замените URL на зеркало Gitea. После клонирования код полностью локален; интернет для `git status`, веток и коммитов не нужен.
|
||||||
|
|
||||||
|
Добавление зеркала к уже клонированному репозиторию:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote add gitea ssh://git@GITEA_HOST/USER/mvch.git
|
||||||
|
git push gitea --all
|
||||||
|
git push gitea --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
Для `mvch-docs` повторите команды в его каталоге с URL репозитория `mvch-docs`.
|
||||||
|
|
||||||
|
### ROS 2 Jazzy
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo locale-gen en_US en_US.UTF-8
|
||||||
|
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
|
||||||
|
export LANG=en_US.UTF-8
|
||||||
|
|
||||||
|
sudo add-apt-repository universe
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
export ROS_APT_SOURCE_VERSION="$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F 'tag_name' | awk -F\" '{print $4}')"
|
||||||
|
curl -L -o /tmp/ros2-apt-source.deb \
|
||||||
|
"https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo ${UBUNTU_CODENAME:-${VERSION_CODENAME}})_all.deb"
|
||||||
|
sudo dpkg -i /tmp/ros2-apt-source.deb
|
||||||
|
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y ros-jazzy-desktop ros-dev-tools \
|
||||||
|
ros-jazzy-rmw-fastrtps-cpp python3-colcon-common-extensions python3-rosdep
|
||||||
|
```
|
||||||
|
|
||||||
|
Инициализация `rosdep` выполняется один раз:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rosdep init
|
||||||
|
rosdep update
|
||||||
|
```
|
||||||
|
|
||||||
|
Если `rosdep init` пишет, что файл источников уже существует, повторять команду не нужно.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
|
||||||
|
ros2 --help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Webots
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /etc/apt/keyrings
|
||||||
|
sudo wget -q https://cyberbotics.com/Cyberbotics.asc \
|
||||||
|
-O /etc/apt/keyrings/Cyberbotics.asc
|
||||||
|
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/Cyberbotics.asc] https://cyberbotics.com/debian binary-amd64/" \
|
||||||
|
| sudo tee /etc/apt/sources.list.d/Cyberbotics.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y webots
|
||||||
|
|
||||||
|
which webots
|
||||||
|
readlink -f "$(command -v webots)"
|
||||||
|
webots --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Не задавайте `WEBOTS_HOME=/snap/webots/...` для APT-версии. Сначала проверьте реальный путь.
|
||||||
|
|
||||||
|
### Пакеты симулятора и управления
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/ros2_ws/src
|
||||||
|
cd ~/ros2_ws/src
|
||||||
|
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_webots_fms_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_arm95_moveit_config.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_aruco_detect_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_dual_lidar_merge_ros2.git
|
||||||
|
git clone https://gitlab.mobird.dev/fms_group/ar_nav_ros2.git
|
||||||
|
|
||||||
|
cd ~/ros2_ws
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
rosdep install --from-paths src -y --ignore-src --rosdistro jazzy
|
||||||
|
colcon build --symlink-install
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Если закрытый GitLab не открывается, это вопрос доступа организаторов. Не подменяйте пакеты случайными форками.
|
||||||
|
|
||||||
|
Добавить окружение в Bash можно после успешной ручной проверки:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
printf '%s\n' \
|
||||||
|
'source /opt/ros/jazzy/setup.bash' \
|
||||||
|
'source ~/ros2_ws/install/setup.bash' \
|
||||||
|
'export RMW_IMPLEMENTATION=rmw_fastrtps_cpp' \
|
||||||
|
>> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Python, YOLO и ROS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/scripts/mvch/module3
|
||||||
|
python3 -m venv --system-site-packages .venv
|
||||||
|
.venv/bin/python -m pip install -U pip
|
||||||
|
.venv/bin/pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
|
||||||
|
.venv/bin/pip install -r requirements.txt tqdm ultralytics-thop
|
||||||
|
.venv/bin/pip install --force-reinstall \
|
||||||
|
"numpy<2" "opencv-python<4.12" lark transforms3d
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
source ~/ros2_ws/install/setup.bash
|
||||||
|
~/scripts/mvch/module3/.venv/bin/python -c \
|
||||||
|
"import rclpy, cv_bridge, numpy, torch, ultralytics; print('Python/ROS/YOLO OK')"
|
||||||
|
```
|
||||||
|
|
||||||
|
CPU-сборки достаточно для запуска и короткой проверки. CUDA/ROCm устанавливайте только под фактическую видеокарту по официальному селектору PyTorch.
|
||||||
|
|
||||||
|
### Bun и Node.js
|
||||||
|
|
||||||
|
Для `module4` нужен Bun:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install -y unzip
|
||||||
|
curl -fsSL https://bun.com/install | bash
|
||||||
|
export BUN_INSTALL="$HOME/.bun"
|
||||||
|
export PATH="$BUN_INSTALL/bin:$PATH"
|
||||||
|
bun --version
|
||||||
|
|
||||||
|
cd ~/scripts/mvch/module4
|
||||||
|
bun install
|
||||||
|
bun run check
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Node.js для этого проекта не обязателен, но полезен как запасной runtime и для npm-инструментов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.7/install.sh | bash
|
||||||
|
export NVM_DIR="$HOME/.nvm"
|
||||||
|
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||||
|
nvm install --lts
|
||||||
|
node --version
|
||||||
|
npm --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### OpenCode — только если разрешён и нужен
|
||||||
|
|
||||||
|
Linux/Ubuntu:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://opencode.ai/install | bash
|
||||||
|
opencode --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows после установки Node.js:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm install -g opencode-ai
|
||||||
|
opencode --version
|
||||||
|
```
|
||||||
|
|
||||||
|
Не записывайте API-ключи в репозитории, README или историю терминала. Способ авторизации выбирайте по правилам площадки.
|
||||||
|
|
||||||
|
## День настройки физического оборудования
|
||||||
|
|
||||||
|
Делайте проверки по слоям:
|
||||||
|
|
||||||
|
1. `ip addr`, `ip route`, доступность адресов РМК.
|
||||||
|
2. `ROS_DOMAIN_ID` и `RMW_IMPLEMENTATION` по данным эксперта.
|
||||||
|
3. `ros2 topic list | sort` и `ros2 node list`.
|
||||||
|
4. Отдельно проверить `/RMC1/odometry`, `/RMC2/odometry`, лидары, камеры и TF.
|
||||||
|
5. На вывешенных колёсах кратко подать малую скорость и немедленно STOP.
|
||||||
|
6. Проверить лифт RMC2 без стеллажа.
|
||||||
|
7. Проверить ARM95 сначала без детали и на безопасной высоте.
|
||||||
|
8. Собрать датасет только выданной/разрешённой камерой и сделать dry-run детектора.
|
||||||
|
9. Зафиксировать реальные топики, фреймы, размеры, точки и параметры в отдельном локальном коммите.
|
||||||
|
10. Сделать контрольный прогон и офлайн-бэкап.
|
||||||
|
|
||||||
|
Не начинайте с полного модуля Д: сначала независимо подтвердите каждую платформу, лифт, камеру, детектор и манипулятор.
|
||||||
|
|
||||||
|
## Защита от блокировки GitHub
|
||||||
|
|
||||||
|
Основной вариант — зеркало на доступной Gitea. Дополнительно сделайте Git bundle обоих репозиториев:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/chvt-backup
|
||||||
|
git -C ~/scripts/mvch bundle create ~/chvt-backup/mvch.bundle --all
|
||||||
|
git -C ~/scripts/mvch-docs bundle create ~/chvt-backup/mvch-docs.bundle --all
|
||||||
|
git bundle verify ~/chvt-backup/mvch.bundle
|
||||||
|
git bundle verify ~/chvt-backup/mvch-docs.bundle
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверка восстановления:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone ~/chvt-backup/mvch.bundle /tmp/mvch-restore-test
|
||||||
|
git -C /tmp/mvch-restore-test log -1 --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
Bundle содержит историю Git и отслеживаемые веса модели, но не содержит `.venv`, `node_modules`, системные пакеты и секреты. На флешку отдельно сохраните установщик/образ Ubuntu, нужную версию Webots, экспорт датасета и при необходимости wheel/cache зависимостей.
|
||||||
Executable
+87
@@ -0,0 +1,87 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -u
|
||||||
|
|
||||||
|
warnings=0
|
||||||
|
|
||||||
|
check_command() {
|
||||||
|
if command -v "$1" >/dev/null 2>&1; then
|
||||||
|
printf '[OK] %s: %s\n' "$1" "$(command -v "$1")"
|
||||||
|
else
|
||||||
|
printf '[WARN] команда %s не найдена\n' "$1"
|
||||||
|
warnings=$((warnings + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ -r /etc/os-release ]]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /etc/os-release
|
||||||
|
printf '[INFO] OS: %s %s\n' "${NAME:-unknown}" "${VERSION_ID:-unknown}"
|
||||||
|
if [[ "${ID:-}" == ubuntu && "${VERSION_ID:-}" == 24.04 ]]; then
|
||||||
|
:
|
||||||
|
else
|
||||||
|
printf '[WARN] официальное окружение задания рассчитано на Ubuntu 24.04\n'
|
||||||
|
warnings=$((warnings + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
for command_name in git python3 code ros2 colcon webots bun; do
|
||||||
|
check_command "$command_name"
|
||||||
|
done
|
||||||
|
|
||||||
|
for optional_name in node npm opencode; do
|
||||||
|
if command -v "$optional_name" >/dev/null 2>&1; then
|
||||||
|
printf '[OK] необязательная команда %s: %s\n' "$optional_name" "$(command -v "$optional_name")"
|
||||||
|
else
|
||||||
|
printf '[INFO] необязательная команда %s не установлена\n' "$optional_name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '[INFO] RMW_IMPLEMENTATION=%s\n' "${RMW_IMPLEMENTATION:-не задано}"
|
||||||
|
printf '[INFO] ROS_DOMAIN_ID=%s\n' "${ROS_DOMAIN_ID:-не задано}"
|
||||||
|
|
||||||
|
if [[ -r /opt/ros/jazzy/setup.bash ]]; then
|
||||||
|
set +u
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source /opt/ros/jazzy/setup.bash
|
||||||
|
set -u
|
||||||
|
printf '[OK] ROS 2 Jazzy setup найден\n'
|
||||||
|
else
|
||||||
|
printf '[WARN] /opt/ros/jazzy/setup.bash не найден\n'
|
||||||
|
warnings=$((warnings + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
workspace_path="${ROS_WORKSPACE:-$HOME/ros2_ws}"
|
||||||
|
if [[ -r "$workspace_path/install/setup.bash" ]]; then
|
||||||
|
set +u
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$workspace_path/install/setup.bash"
|
||||||
|
set -u
|
||||||
|
printf '[OK] workspace: %s\n' "$workspace_path"
|
||||||
|
else
|
||||||
|
printf '[WARN] workspace не собран: %s\n' "$workspace_path"
|
||||||
|
warnings=$((warnings + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v ros2 >/dev/null 2>&1; then
|
||||||
|
for package_name in ar_webots_fms_ros2 ar_arm95_moveit_config ar_aruco_detect_ros2; do
|
||||||
|
if ros2 pkg prefix "$package_name" >/dev/null 2>&1; then
|
||||||
|
printf '[OK] ROS-пакет: %s\n' "$package_name"
|
||||||
|
else
|
||||||
|
printf '[WARN] ROS-пакет не найден: %s\n' "$package_name"
|
||||||
|
warnings=$((warnings + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if python3 -c 'import rclpy, cv_bridge' >/dev/null 2>&1; then
|
||||||
|
printf '[OK] Python: rclpy и cv_bridge импортируются\n'
|
||||||
|
else
|
||||||
|
printf '[WARN] Python не импортирует rclpy/cv_bridge\n'
|
||||||
|
warnings=$((warnings + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v glxinfo >/dev/null 2>&1; then
|
||||||
|
glxinfo -B 2>/dev/null | sed -n '/OpenGL vendor/p;/OpenGL renderer/p'
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\nИтог: %d предупреждений. WARN не всегда означает поломку, но проверьте его до заезда.\n' "$warnings"
|
||||||
Reference in New Issue
Block a user