Лабораторна робота 15
Лабораторна робота №15. Інтеграція MG400 з ROS 2
Мета: навчитися читати URDF/Xacro-модель MG400, розрізняти topics, services та actions у ROS 2, перевірити TF-граф і display.launch.py без обладнання, реалізувати mock-чергу довготривалих команд із скасуванням і безпечною реакцією на втрату вузла; чітко відокремити таку перевірку від фізичного bringup.
Результати навчання та передумови
Після роботи студент уміє:
- пояснити ролі
robot_state_publisher,/joint_states, TF, service та action; - знайти опис ланок, суглобів і action-інтерфейсу в локальному пакеті;
- запустити статичну перевірку репозиторію та, за наявності узгодженого ROS 2-середовища, лише апаратно незалежний display;
- відтворити прийняття, feedback, скасування та помилку mock-action;
- перейти у
FAULTпри втраті heartbeat і не відновлювати чергу автоматично.
Передумови: Python, базові команди Linux, графи, поняття publish/subscribe та виконані ЛР-01, ЛР-13. Фізична апробація не входить до цієї редакції.
Середовище виконання
| Середовище | Статус | Примітка |
|---|---|---|
| Google Colab | дозволено частково | Лише mock-код; GUI RViz і локальні пакети не використовуються |
| Локальний ПК | основне | Статичний аудит і mock; для RViz потрібні Ubuntu 22.04 та ROS 2 Humble |
| Raspberry Pi | дозволено | Лише після фіксації ОС та ROS 2; у цій роботі без доступу до GPIO й робота |
| Симуляція | основне | display.launch.py, статичний аналіз і mock-action |
| Фізичний комплекс MERC-I5 | не використовується | main.launch.py, мережевий клієнт MG400 та рухові команди заборонені |
Необхідні знання та матеріали
- локальний каталог
MG400_ROS2-humble, зокремаdisplay.launch.py,mg400.urdf.xacroтаCommandQueue.action; - Python зі стандартною бібліотекою;
- для необов'язкового display — окреме узгоджене середовище ROS 2 Humble з RViz2 та зібраним workspace;
- журнальна таблиця з цієї роботи; мережеві адреси, паролі й токени не потрібні.
Ризики та правила безпеки
Ризики
display.launch.py створює лише опис і візуалізацію. Натомість main.launch.py включає mg400_node, який відкриває мережеві інтерфейси реального контролера; service/action-виклики можуть активувати або перемістити робот. Втрата клієнта не доводить, що черга контролера порожня.
Заборонені дії
- не запускати
main.launch.py,mg400.launch.py,sample.bashабоcommand_queue_client; - не викликати
EnableRobot,MovJ,MovL,JointMovJ,Continue, I/O чи action фізичного робота; - не підставляти заводську IP-адресу як адресу MERC-I5;
- не вважати RViz симулятором динаміки, перевіркою колізій або доказом фізичної безпеки.
Умови негайної зупинки
У mock/display припинити сценарій при появі неочікуваного мережевого вузла, невідомого namespace, зависанні процесу, втраті heartbeat, невдалому скасуванні або невідомому стані action. На фізичній системі додатково діють усі умови з правил експлуатації.
Безпечний стан
Для цієї роботи: жодного з'єднання з MG400, жодного апаратного виходу, mock-черга порожня або скасована, стан IDLE чи зафіксований FAULT, автоматичне відновлення вимкнене.
Передпусковий чекліст
- Відкрито саме
display.launch.py, а неmain.launch.py. - Ethernet/Wi-Fi до контролера MG400 не використовується.
- У команді немає IP-адреси, service/action керування або
sample.bash. - Workspace містить лише перевірену локальну копію пакета.
- Mock-тести завершуються без мережі.
- Відомо, як завершити процес через
Ctrl+C.
Хід виконання роботи
- Звірити локальну структуру ROS-пакета.
- Дослідити модель, TF та інтерфейси.
- Виконати safe-smoke для action-черги.
- Запустити статичний аудитор.
- За наявності підготовленого ROS 2-середовища запустити тільки display.
- Перевірити скасування й втрату heartbeat, оформити метрики.
Методичні вказівки й теоретичні відомості
1. URDF/Xacro, TF і joint states
URDF задає дерево ланок і суглобів; Xacro додає макроси й параметри, після розгортання яких утворюється URDF. robot_state_publisher поєднує геометрію моделі зі значеннями /joint_states та публікує перетворення TF. RViz відображає отримане дерево, але не моделює сили, гальмування чи реальний стан контролера.
У локальному display.launch.py запускаються rsp.launch.py, власний joint_state_publisher_gui і RViz. README прямо позначає цей display як апаратно незалежний. main.launch.py, навпаки, включає мережевий mg400_node; ці маршрути не взаємозамінні.
2. Topics, services та actions
| Інтерфейс | Модель | Доцільне застосування | Реакція на збій |
|---|---|---|---|
| Topic | асинхронний потік | joint state, стан, телеметрія | контролювати timestamp/heartbeat |
| Service | короткий request/response | читання або коротка команда | тайм-аут; не повторювати небезпечний запит без ідемпотентності |
| Action | goal/feedback/result, підтримує cancel | довга черга або рух | cancel із підтвердженням; потім звірка фактичного стану |
Локальний CommandQueue.action приймає масив команд, повертає result та error_id, а feedback містить позу й чотири кути. Наявність cancel у протоколі action не гарантує миттєвої фізичної зупинки.
Текстовий опис рисунка: URDF/Xacro і mock joint states надходять до robot_state_publisher, який передає TF у RViz; окремо mock action client обмінюється goal/cancel/feedback/result із чергою, а heartbeat monitor переводить її у FAULT після тайм-ауту.
flowchart LR
JSP["Mock joint-state publisher"] -->|"/joint_states"| RSP["robot_state_publisher"]
URDF["URDF/Xacro"] --> RSP
RSP -->|"/tf, /tf_static"| RVIZ["RViz2 display"]
CLIENT["Mock action client"] -->|"goal / cancel"| SERVER["Mock command queue"]
SERVER -->|"feedback / result"| CLIENT
HEART["Heartbeat monitor"] -->|"timeout"| FAULT["FAULT + manual recovery"]
Рис. 1. Апаратно незалежні потоки даних ЛР-15
3. Джерела
- офіційний опис інтерфейсів ROS 2 Humble;
- офіційні навчальні матеріали URDF для ROS 2 Humble;
- офіційний опис tf2;
- локальні
README,display.launch.pyіCommandQueue-опис; - базова лінія ПЗ MERC-I5 і правила допуску.
Виконання лабораторної роботи
Крок 1. Перевірка меж завдання
Випишіть два дозволені артефакти (display.launch.py, mock) і щонайменше три заборонені (main.launch.py, sample.bash, service/action фізичного робота). Перевірте, що контролер не використовується.
Очікуваний результат: заповнений чекліст і словесне розділення display/bringup.
Критерій правильності: студент пояснює, який launch-файл створює мережевий вузол, не виконуючи його.
Якщо результат не отримано: повернутися до локальних launch-файлів; нічого не запускати.
Крок 2. Safe-smoke черги та скасування
Виконайте блок без ROS 2, мережі та обладнання.
# safe-smoke
from dataclasses import dataclass
from enum import Enum, auto
class State(Enum):
IDLE = auto()
ACTIVE = auto()
CANCELLED = auto()
FAULT = auto()
@dataclass
class MockActionQueue:
state: State = State.IDLE
heartbeat_age: int = 0
completed: int = 0
def send(self, commands: tuple[str, ...]) -> None:
if self.state is not State.IDLE or not commands:
raise RuntimeError("goal rejected")
self.state = State.ACTIVE
def feedback(self) -> None:
if self.state is not State.ACTIVE:
raise RuntimeError("no active goal")
self.completed += 1
self.heartbeat_age = 0
def cancel(self) -> None:
if self.state is State.ACTIVE:
self.state = State.CANCELLED
def tick_without_heartbeat(self, limit: int = 2) -> None:
self.heartbeat_age += 1
if self.heartbeat_age > limit:
self.state = State.FAULT
q = MockActionQueue()
q.send(("PLAN_A", "PLAN_B"))
q.feedback()
q.cancel()
assert q.state is State.CANCELLED and q.completed == 1
lost = MockActionQueue()
lost.send(("PLAN",))
for _ in range(3):
lost.tick_without_heartbeat()
assert lost.state is State.FAULT
print("safe-smoke: cancel and heartbeat timeout PASS")
Очікуваний результат: safe-smoke: cancel and heartbeat timeout PASS.
Критерій правильності: код завершується з кодом 0; після timeout стан лише FAULT.
Якщо результат не отримано: не переходити до ROS; перевірити незмінність коду й версію Python.
Крок 3. Статичний аудит локального пакета
Збережіть код як ros_static_check.py. Він лише читає файли.
from __future__ import annotations
import argparse
import re
import xml.etree.ElementTree as ET
from pathlib import Path
def require(path: Path) -> str:
if not path.is_file():
raise FileNotFoundError(path)
return path.read_text(encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--repo", type=Path, default=Path("MG400_ROS2-humble"))
args = parser.parse_args()
repo = args.repo
xacro = repo / "mg400_description/urdf/mg400.urdf.xacro"
macro = repo / "mg400_description/urdf/mg400.xacro"
display = repo / "mg400_bringup/launch/display.launch.py"
action = repo / "mg400_msgs/action/CommandQueue.action"
ET.parse(xacro)
ET.parse(macro)
macro_text = require(macro)
display_text = require(display)
action_text = require(action)
links = sorted(set(re.findall(r'<link\s+name="\$\{prefix\}([^"$]+)"', macro_text)))
joints = sorted(set(re.findall(r'<joint\s+name="\$\{prefix\}([^"$]+)"', macro_text)))
sections = action_text.split("---")
checks = {
"xml_ok": True,
"has_links": bool(links),
"has_joints": bool(joints),
"action_goal_result_feedback": len(sections) == 3,
"display_has_rviz": "rviz.launch.py" in display_text,
"display_has_rsp": "rsp.launch.py" in display_text,
"display_has_network_node": "mg400.launch.py" in display_text,
}
for key, value in checks.items():
print(f"{key}={value}")
print(f"links={len(links)} joints={len(joints)}")
assert all(value for key, value in checks.items() if key != "display_has_network_node")
assert checks["display_has_network_node"] is False
return 0
if __name__ == "__main__":
raise SystemExit(main())
python ros_static_check.py --repo MG400_ROS2-humble
Очікуваний результат: усі позитивні перевірки True, display_has_network_node=False, кількість ланок і суглобів більша нуля.
Критерій правильності: XML розбирається; action має три секції; display не включає mg400.launch.py.
Якщо результат не отримано: зафіксувати файл і помилку; не виправляти модель довільно й не переходити до bringup.
Крок 4. Інспекція графа без запуску обладнання
За вихідним кодом заповніть таблицю.
| Артефакт | Вхід | Вихід | Ознака апаратної залежності |
|---|---|---|---|
rsp.launch.py | Xacro, workspace_visible | robot_description, TF | немає мережевого вузла |
display.launch.py | модель, GUI joint states | RViz | README позначає hardware-free |
main.launch.py | namespace, IP | mg400_node, TF, RViz | включає мережевий launch |
CommandQueue.action | масив Command | result/error + feedback | сервер може керувати рухом |
Очікуваний результат: усі чотири рядки пояснено власними словами.
Критерій правильності: topic/service/action не змішані; result, feedback, cancel мають різні ролі.
Якщо результат не отримано: повторно прочитати .msg/.srv/.action; не робити пробний action-виклик.
Крок 5. Необов'язковий hardware-free display
Крок дозволено лише в уже зібраному контрольному Ubuntu 22.04/ROS 2 Humble workspace. Команда не підключає MG400 за структурою локального launch-файла.
source /opt/ros/humble/setup.bash
source install/setup.bash
ros2 launch mg400_bringup display.launch.py workspace_visible:=False
В іншому терміналі допускається лише read-only інспекція:
ros2 node list
ros2 topic list
ros2 topic echo /joint_states --once
Очікуваний результат: RViz показує модель, GUI змінює візуальну конфігурацію, /joint_states має повідомлення.
Критерій правильності: у графі немає mg400_node; Ethernet/IP контролера не використовуються.
Якщо результат не отримано: завершити Ctrl+C, зберегти журнал; не запускати main.launch.py як «спосіб виправлення».
Крок 6. Аналіз негативних і граничних сценаріїв
Повторіть safe-smoke, змінюючи лише тестову копію: порожній goal має бути відхилений; cancel після IDLE не створює руху; heartbeat на межі limit ще не дає FAULT, після перевищення — дає.
Очікуваний результат: детерміновані стани й пояснення off-by-one межі.
Критерій правильності: жоден сценарій не переходить із FAULT в ACTIVE без окремої ручної процедури.
Якщо результат не отримано: зупинити сценарій, додати трасування станів і усунути неоднозначний перехід.
Фізичний bringup: еталон і межа поточної роботи
Еталонний фізичний етап потребує затвердженої конфігурації, локального оператора, перевіреного E-Stop, вільної зони, меж, навантаження, Tool/User frames, мінімальної швидкості, heartbeat і журналу. Спершу виконують read-only перевірку стану; будь-яка активація або action потребує нового явного дозволу. У цьому запуску етап не виконується.
Сценарії перевірки
| Тип | Сценарій | Очікування | Доказ |
|---|---|---|---|
| позитивний | model/action/display статично валідні | аудитор завершується 0 | консольний журнал |
| позитивний | goal + feedback + cancel | CANCELLED, черга не продовжується | safe-smoke |
| негативний | порожній goal | відхилення до ACTIVE | виняток у mock |
| негативний | heartbeat втрачено | FAULT, ручне відновлення | журнал переходів |
| граничний | age дорівнює limit | ще не timeout | окремий assert |
| граничний | age = limit + 1 | FAULT | окремий assert |
Таблиці вимірювань і метрики
| Запуск | Час аудиту, мс | Ланок | Суглобів | Cancel підтверджено | Timeout спрацював | Фізичне обладнання |
|---|---|---|---|---|---|---|
| 1 | ні |
Обов'язкові метрики: частка пройдених assertions, кількість неочікуваних вузлів, latency mock-feedback (за time.perf_counter) і однозначність кінцевого стану. Цільові апаратні значення не задаються без вимірювань.
Вимоги до звіту
Див. загальні вимоги. Додайте: карту ROS-графа, таблицю артефактів, вивід аудитора, журнал позитивного/негативного/граничного тестів, пояснення display проти bringup та явну фразу «фізичний MG400 не підключався і не рухався».
Критерії оцінювання
| Складова | Частка | Що оцінюється |
|---|---|---|
| Підготовка | 10% | джерела, середовище, safety-чекліст |
| Реалізація | 40% | статичний аудитор, модель інтерфейсів, mock-action |
| Перевірка | 25% | cancel, heartbeat, позитивні/негативні/граничні тести |
| Аналіз | 15% | коректне розділення URDF/TF/RViz і фізичного стану |
| Звіт | 10% | граф, журнали, метрики й чесна межа апробації |
| Разом | 100% |
Контрольні питання
- Чому RViz не доводить відсутність колізії?
- Які дані входять до goal, feedback і result
CommandQueue.action? - Коли застосовують topic, service та action?
- Чому cancel-відповідь треба звірити з фактичним станом?
- Яка різниця між
display.launch.pyіmain.launch.py? - Чому відновлення після втрати heartbeat має бути ручним?
Висновки
Сформулюйте, які частини інтеграції підтверджені статично й у mock, які властивості показує TF/RViz, як система обробляє cancel і втрату вузла та чому ця робота не є фізичною апробацією MG400.