- Refined introduction to REST API capabilities and usage. - Added security warnings and best practices for using the API. - Improved examples for various programming languages (Bash, Python, MATLAB). - Enhanced endpoint descriptions and added details on request/response structures. - Clarified the process for moving the robot and handling trajectories. - Updated sections on sequences and error handling for better clarity.
28 KiB
Управление через REST API
REST API позволяет читать состояние робота и отправлять ему команды по HTTP. Он рассчитан на прикладные скрипты, интеграции с другими системами и быстрые проверки через Swagger UI.
Запросы к перемещению выполняются синхронно: ответ приходит после завершения планирования и выполнения команды либо после внутреннего тайм-аута. Очереди команд в API нет — дождитесь ответа на текущий запрос, прежде чем отправлять следующий.
!!! warning "Безопасность" REST API не заменяет штатную систему безопасности KUKA и кнопку аварийного останова. Перед первым запуском на реальном роботе проверьте программу Sunrise, зоны безопасности, инструмент и рабочую область. Начинать знакомство с API лучше в симуляции.
Запуск и доступ
Веб-сервер запускается вместе со стеком робота, если в корневом файле cobot-setting.yaml включён блок web:
web:
enabled: true
host: 0.0.0.0
port: 8007
endpoints: pkg://iiwa_config/config/api_endpoints.yaml
joint_limits: pkg://iiwa_config/config/moveit/joint_limits.yaml
После запуска через cobot run сервер будет доступен по адресу http://адрес-сервера:8007. Swagger UI помогает посмотреть фактическую схему запросов и выполнить одиночный тест:
- локально: http://localhost:8007/docs;
- с другой машины: http://адрес-сервера:8007/docs;
- JSON-схема OpenAPI: http://адрес-сервера:8007/openapi.json.
Отдельного health-check в сервере нет. Если открывается Swagger UI, HTTP-сервер запущен. Готовность ROS-компонентов проверяется при обращении к конкретному маршруту.
По умолчанию сервер слушает все сетевые интерфейсы и не использует аутентификацию. Не публикуйте порт 8007 в недоверенную сеть. Для локальной работы укажите host: 127.0.0.1, а для удалённого доступа ограничьте сеть правилами firewall или VPN.
Подготовка к примерам
Вкладки на этой странице синхронизированы: выберите удобный язык один раз, и тот же вариант будет открыт у следующих примеров.
=== "curl"
~~~ bash
HOST=http://localhost:8007
~~~
=== "Python"
~~~ python
import httpx
HOST = "http://localhost:8007"
T_READ = 10
T_MOVE = 60
~~~
=== "MATLAB"
~~~ matlab
HOST = 'http://localhost:8007';
T_READ = 10;
T_MOVE = 60;
readOpts = weboptions('Timeout', T_READ);
moveOpts = weboptions('MediaType', 'application/json', 'Timeout', T_MOVE);
~~~
Для JSON-запросов MATLAB использует встроенный webwrite. Загрузка CSV и JSON-файлов требует интерфейса matlab.net.http, который есть в современных desktop-версиях MATLAB.
Состав API
| Метод | Маршрут | Назначение |
|---|---|---|
| GET | /robot/joint_states | Текущее состояние суставов |
| GET | /robot/pose | Поза TCP относительно base_link |
| GET | /robot/positions | Именованные положения из SRDF |
| POST | /robot/move/named | Переход в именованное положение |
| POST | /robot/move/pose | Декартово перемещение TCP |
| POST | /robot/move/joints | Перемещение по углам семи суставов |
| POST | /trajectory/send | Публикация траектории из JSON |
| POST | /trajectory/send_csv | Загрузка и публикация траектории из CSV |
| GET | /trajectory/logs | Последние записи траекторного модуля |
| POST | /sequences/start | Запуск последовательности из JSON-файла |
| GET | /sequences/status | Статус запущенной последовательности |
| GET | /sequences/logs | Вывод процесса последовательности |
| POST | /stop | Остановка команд API и планировщика |
Получение состояния
Состояние суставов
GET /robot/joint_states возвращает последнее сообщение ROS-топика /joint_states. Поля position, velocity и effort расположены в том же порядке, что и соответствующий массив name. Углы в position заданы в радианах.
=== "curl"
~~~ bash
curl -sS --max-time 10 $HOST/robot/joint_states | python3 -m json.tool
~~~
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/robot/joint_states", timeout=T_READ)
response.raise_for_status()
state = response.json()
print(dict(zip(state["name"], state["position"])))
~~~
=== "MATLAB"
~~~ matlab
jointState = webread([HOST '/robot/joint_states'], readOpts);
disp(jointState.position)
~~~
Типичный ответ:
{
"name": ["joint1", "joint2", "joint3", "joint4", "joint5", "joint6", "joint7"],
"position": [0.0, 0.0, 0.0, -1.57, 0.0, 1.57, 0.0],
"velocity": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"effort": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
}
Если сообщения от контроллера не поступают в течение двух секунд, API вернёт 503. Обычно это означает, что контроллер или робот ещё не запущен.
Поза TCP
GET /robot/pose вычисляет прямую кинематику через сервис MoveIt /compute_fk. Положение задаётся в метрах, а ориентация возвращается одновременно кватернионом и углами Эйлера:
- euler_rad — радианы;
- euler_deg — градусы;
- углы A, B, C соответствуют конвенции KUKA ABC: поворот вокруг Z, затем Y и затем X.
=== "curl"
~~~ bash
curl -sS --max-time 10 $HOST/robot/pose | python3 -m json.tool
~~~
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/robot/pose", timeout=T_READ)
response.raise_for_status()
pose = response.json()
print(pose["position"])
~~~
=== "MATLAB"
~~~ matlab
pose = webread([HOST '/robot/pose'], readOpts);
fprintf('TCP: x=%.3f, y=%.3f, z=%.3f м\n', ...
pose.position.x, pose.position.y, pose.position.z);
fprintf('ABC: A=%.1f, B=%.1f, C=%.1f град\n', ...
pose.orientation.euler_deg.a, ...
pose.orientation.euler_deg.b, ...
pose.orientation.euler_deg.c);
~~~
Маршрут зависит и от /joint_states, и от работающего MoveIt. При недоступности любого из них будет возвращён 503.
Именованные положения
GET /robot/positions читает положения group_state из SRDF. Список не зашит в API: он отражает текущую конфигурацию робота. В стандартной конфигурации есть положения home, work и transport.
=== "curl"
~~~ bash
curl -sS $HOST/robot/positions | python3 -m json.tool
~~~
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/robot/positions", timeout=T_READ)
response.raise_for_status()
for position in response.json():
print(position["name"], "—", position["description"])
~~~
=== "MATLAB"
~~~ matlab
namedPositions = webread([HOST '/robot/positions'], readOpts);
for i = 1:numel(namedPositions)
fprintf('%s — %s\n', namedPositions(i).name, ...
namedPositions(i).description);
end
~~~
Перед вызовом /robot/move/named всегда полезно получить этот список: он показывает точное имя, группу планирования и целевые углы суставов.
Команды перемещения
Все три команды ниже используют MoveIt. Ответ имеет вид:
{"success": true, "message": "Движение выполнено успешно"}
Поле success: false означает, что планировщик не смог построить или выполнить траекторию. HTTP-статус при этом может остаться 200, поэтому в прикладном коде проверяйте и статус HTTP, и поле success.
Переход в именованное положение
POST /robot/move/named перемещает манипулятор в положение из SRDF.
| Поле | Обязательное | Значение |
|---|---|---|
| name | да | Имя положения из /robot/positions |
| speed | нет | Масштаб скорости от 0.01 до 1.0; по умолчанию 0.1 |
| accel_scale | нет | Масштаб ускорения от 0 до 1.0; 0 означает использовать speed |
=== "curl"
~~~ bash
curl -sS --max-time 60 -X POST $HOST/robot/move/named \
-H "Content-Type: application/json" \
-d '{"name": "home", "speed": 0.1, "accel_scale": 0.0}'
~~~
=== "Python"
~~~ python
response = httpx.post(
f"{HOST}/robot/move/named",
json={"name": "home", "speed": 0.1, "accel_scale": 0.0},
timeout=T_MOVE,
)
response.raise_for_status()
result = response.json()
if not result["success"]:
raise RuntimeError(result["message"])
~~~
=== "MATLAB"
~~~ matlab
body = struct('name', 'home', 'speed', 0.1, 'accel_scale', 0.0);
reply = webwrite([HOST '/robot/move/named'], body, moveOpts);
assert(reply.success, reply.message)
~~~
Декартово перемещение TCP
POST /robot/move/pose принимает положение TCP в метрах и ориентацию ABC в радианах. Если frame_id пуст, используется фрейм, заданный в настройках планирования; в стандартной конфигурации это base_link.
| Поле | Обязательное | Значение |
|---|---|---|
| x, y, z | да | Координаты TCP, м |
| a, b, c | нет | Углы KUKA ABC, рад; по умолчанию 0 |
| speed | нет | Масштаб скорости от 0.01 до 1.0; по умолчанию 0.1 |
| planner | нет | ompl, ptp, lin, circ или chomp; по умолчанию ptp |
| frame_id | нет | Фрейм целевой позы; пустая строка использует фрейм по умолчанию |
Значение planner приводится к нижнему регистру. PTP подходит для переходов между точками, LIN — для прямолинейного движения инструмента. Выбор CIRC имеет смысл только для задач, где он поддерживается вашим планировщиком и целевой позой.
=== "curl"
~~~ bash
curl -sS --max-time 60 -X POST $HOST/robot/move/pose \
-H "Content-Type: application/json" \
-d '{
"x": 0.40, "y": 0.00, "z": 0.50,
"a": 0.0, "b": 3.14159, "c": 0.0,
"speed": 0.1, "planner": "ptp", "frame_id": ""
}'
~~~
=== "Python"
~~~ python
target = {
"x": 0.40, "y": 0.00, "z": 0.50,
"a": 0.0, "b": 3.14159, "c": 0.0,
"speed": 0.1, "planner": "ptp", "frame_id": "",
}
response = httpx.post(f"{HOST}/robot/move/pose", json=target, timeout=T_MOVE)
response.raise_for_status()
print(response.json())
~~~
=== "MATLAB"
~~~ matlab
body = struct( ...
'x', 0.40, 'y', 0.00, 'z', 0.50, ...
'a', 0.0, 'b', pi, 'c', 0.0, ...
'speed', 0.1, 'planner', 'ptp', 'frame_id', '');
reply = webwrite([HOST '/robot/move/pose'], body, moveOpts);
assert(reply.success, reply.message)
~~~
Перемещение по углам суставов
POST /robot/move/joints принимает ровно семь углов в порядке J1–J7. API проверяет количество значений и текущие границы из файла joint_limits.yaml.
| Сустав | Допустимый угол, рад |
|---|---|
| J1 | от -2.97 до 2.97 |
| J2 | от -2.10 до 2.10 |
| J3 | от -2.97 до 2.97 |
| J4 | от -2.10 до 2.10 |
| J5 | от -2.97 до 2.97 |
| J6 | от -2.10 до 2.10 |
| J7 | от -3.05 до 3.05 |
При изменении файла ограничений ориентируйтесь на Swagger UI: значения в этой таблице относятся к поставляемой конфигурации.
=== "curl"
~~~ bash
curl -sS --max-time 60 -X POST $HOST/robot/move/joints \
-H "Content-Type: application/json" \
-d '{"joints": [0.0, 0.5, 0.0, -1.57, 0.0, 1.57, 0.0], "speed": 0.1}'
~~~
=== "Python"
~~~ python
response = httpx.post(
f"{HOST}/robot/move/joints",
json={
"joints": [0.0, 0.5, 0.0, -1.57, 0.0, 1.57, 0.0],
"speed": 0.1,
},
timeout=T_MOVE,
)
response.raise_for_status()
print(response.json())
~~~
=== "MATLAB"
~~~ matlab
body = struct( ...
'joints', [0.0, 0.5, 0.0, -1.57, 0.0, 1.57, 0.0], ...
'speed', 0.1);
reply = webwrite([HOST '/robot/move/joints'], body, moveOpts);
assert(reply.success, reply.message)
~~~
Траектории по суставам
Маршруты раздела /trajectory публикуют сообщение JointTrajectory напрямую в контроллер /iiwa_arm_controller/joint_trajectory. Ответ status: sent подтверждает публикацию сообщения, но не завершение движения и не отсутствие ошибок контроллера. Отслеживайте состояние робота через /robot/joint_states и при необходимости смотрите /trajectory/logs.
Траектория в JSON
POST /trajectory/send принимает одну или несколько точек.
| Поле | Значение |
|---|---|
| points | Непустой список точек |
| points[].positions | Ровно 7 углов J1–J7 в радианах |
| points[].time_from_start | Время от начала траектории в секундах, не меньше 0 |
| validate_limits | Проверять границы суставов; по умолчанию true |
Сервер не проверяет возрастание времени между точками, поэтому задавайте его самостоятельно. Для контроллера траектория с возрастающими значениями времени предсказуемее.
=== "curl"
~~~ bash
curl -sS --max-time 20 -X POST $HOST/trajectory/send \
-H "Content-Type: application/json" \
-d '{
"points": [
{"positions": [0, 0, 0, 0, 0, 0, 0], "time_from_start": 0.0},
{"positions": [0, 0.5, 0, -1.0, 0, 1.0, 0], "time_from_start": 3.0},
{"positions": [0, 0, 0, 0, 0, 0, 0], "time_from_start": 6.0}
],
"validate_limits": true
}'
~~~
=== "Python"
~~~ python
trajectory = {
"points": [
{"positions": [0.0] * 7, "time_from_start": 0.0},
{"positions": [0.0, 0.5, 0.0, -1.0, 0.0, 1.0, 0.0], "time_from_start": 3.0},
{"positions": [0.0] * 7, "time_from_start": 6.0},
],
"validate_limits": True,
}
response = httpx.post(f"{HOST}/trajectory/send", json=trajectory, timeout=T_READ)
response.raise_for_status()
print(response.json())
~~~
=== "MATLAB"
~~~ matlab
p1 = struct('positions', [0, 0, 0, 0, 0, 0, 0], ...
'time_from_start', 0.0);
p2 = struct('positions', [0, 0.5, 0, -1.0, 0, 1.0, 0], ...
'time_from_start', 3.0);
trajectory.points = [p1, p2];
trajectory.validate_limits = true;
opts = weboptions('MediaType', 'application/json', 'Timeout', T_READ);
reply = webwrite([HOST '/trajectory/send'], trajectory, opts);
disp(reply)
~~~
Загрузка CSV
POST /trajectory/send_csv принимает CSV-файл в multipart-поле file. Первая строка должна быть заголовком. Имена колонок суставов могут быть записаны как joint1 или joint_1, регистр не важен; колонка времени называется t, time или time_from_start. Порядок колонок произвольный.
Пример файла:
joint1,joint2,joint3,joint4,joint5,joint6,joint7,t
0,0,0,0,0,0,0,0.0
0,0.5,0,-1.0,0,1.0,0,3.0
Параметры separator и validate_limits передаются в строке запроса, а не как поля формы. По умолчанию разделитель — запятая, проверка ограничений включена.
=== "curl"
~~~ bash
curl -sS --max-time 20 -X POST \
"$HOST/trajectory/send_csv?separator=%2C&validate_limits=true" \
-F "file=@trajectory.csv;type=text/csv"
~~~
=== "Python"
~~~ python
with open("trajectory.csv", "rb") as csv_file:
response = httpx.post(
f"{HOST}/trajectory/send_csv",
params={"separator": ",", "validate_limits": True},
files={"file": ("trajectory.csv", csv_file, "text/csv")},
timeout=T_READ,
)
response.raise_for_status()
print(response.json())
~~~
=== "MATLAB"
~~~ matlab
import matlab.net.http.*
import matlab.net.http.io.*
uri = URI([HOST '/trajectory/send_csv?separator=%2C&validate_limits=true']);
form = MultipartFormProvider('file', FileProvider('trajectory.csv'));
request = RequestMessage('post', [], form);
httpOpts = HTTPOptions('ConnectTimeout', T_READ, 'ResponseTimeout', T_READ);
response = request.send(uri, httpOpts);
disp(response.Body.Data)
~~~
Для файла с точкой с запятой замените %2C на %3B.
Лог траекторного модуля
GET /trajectory/logs?n=50 возвращает до 300 последних записей. Параметр n должен быть в диапазоне от 1 до 300.
=== "curl"
~~~ bash
curl -sS "$HOST/trajectory/logs?n=20" | python3 -m json.tool
~~~
=== "Python"
~~~ python
response = httpx.get(f"{HOST}/trajectory/logs", params={"n": 20}, timeout=T_READ)
response.raise_for_status()
for line in response.json()["lines"]:
print(line)
~~~
=== "MATLAB"
~~~ matlab
logs = webread([HOST '/trajectory/logs?n=20'], readOpts);
disp(logs.lines)
~~~
Чтобы прервать траекторию, используйте общий маршрут POST /stop. Отдельного маршрута /trajectory/stop в API нет.
Последовательности движений
POST /sequences/start запускает отдельный процесс motion_sequence_runner. Он читает загруженный JSON-файл и поочерёдно отправляет цели MoveToJoints или MoveToPose.
| Поле формы | Значение по умолчанию | Назначение |
|---|---|---|
| config | — | JSON-файл последовательности, обязательное поле |
| n_iterations | 3 | Число повторений, не меньше 1 |
| delay_between_iterations | 5.0 | Пауза между итерациями, с |
| bag_path | пусто | Путь для записи rosbag; пустая строка отключает запись |
| topics | пусто | Топики для rosbag через запятую; пусто означает все обнаруженные топики |
| joints_action | cobot/move_to_joints | Имя action для суставных целей |
| pose_action | cobot/move_to_pose | Имя action для декартовых целей |
Минимальная структура конфигурации:
{
"home": {
"joints": [0, 0, 0, -1.57, 0, 1.57, 0],
"speed": 0.1
},
"waypoints": [
{
"x": 0.6, "y": 0.1, "z": 0.55,
"a": 3.14, "b": 0.31, "c": 2.79,
"speed": 0.2, "planner": "lin"
},
{
"joints": [0.5, 0.3, 0, -1.2, 0, 1.4, 0],
"speed": 0.2
}
]
}
Если в точке есть поле joints, она считается суставной. Иначе runner ожидает декартовы поля x, y, z, a, b и c.
Запуск последовательности
=== "curl"
~~~ bash
curl -sS --max-time 10 -X POST $HOST/sequences/start \
-F "config=@motion_sequence_config.json;type=application/json" \
-F "n_iterations=3" \
-F "delay_between_iterations=5.0"
~~~
=== "Python"
~~~ python
with open("motion_sequence_config.json", "rb") as config:
response = httpx.post(
f"{HOST}/sequences/start",
files={"config": ("motion_sequence_config.json", config, "application/json")},
data={"n_iterations": "3", "delay_between_iterations": "5.0"},
timeout=T_READ,
)
response.raise_for_status()
print(response.json())
~~~
=== "MATLAB"
~~~ matlab
import matlab.net.http.*
import matlab.net.http.io.*
form = MultipartFormProvider( ...
'config', FileProvider('motion_sequence_config.json'), ...
'n_iterations', '3', ...
'delay_between_iterations', '5.0');
request = RequestMessage('post', [], form);
httpOpts = HTTPOptions('ConnectTimeout', T_READ, 'ResponseTimeout', T_READ);
response = request.send(URI([HOST '/sequences/start']), httpOpts);
disp(response.Body.Data)
~~~
Ответ status: started подтверждает запуск процесса, но не корректность содержимого JSON и не успешность каждого движения. Если runner завершится с ошибкой, проверьте его состояние и лог.
Статус и журнал последовательности
=== "curl"
~~~ bash
curl -sS $HOST/sequences/status | python3 -m json.tool
curl -sS "$HOST/sequences/logs?n=50" | python3 -m json.tool
~~~
=== "Python"
~~~ python
status = httpx.get(f"{HOST}/sequences/status", timeout=T_READ)
status.raise_for_status()
print(status.json())
logs = httpx.get(f"{HOST}/sequences/logs", params={"n": 50}, timeout=T_READ)
logs.raise_for_status()
for line in logs.json()["lines"]:
print(line)
~~~
=== "MATLAB"
~~~ matlab
status = webread([HOST '/sequences/status'], readOpts);
logs = webread([HOST '/sequences/logs?n=50'], readOpts);
disp(status)
disp(logs.lines)
~~~
Статусы:
- idle — последовательность ещё не запускалась;
- running — процесс выполняется;
- finished — процесс завершён; в ответе будет код returncode.
Одновременно может работать только одна последовательность. Повторный POST /sequences/start во время её выполнения вернёт 409. Для остановки используйте POST /stop: отдельного /sequences/stop нет.
Общая остановка
POST /stop останавливает запущенный runner, публикует точку удержания текущей позиции для траекторного контроллера и вызывает сервис MoveIt cobot/stop. Если текущие состояния суставов недоступны, вместо точки удержания публикуется пустая траектория.
=== "curl"
~~~ bash
curl -sS --max-time 10 -X POST $HOST/stop | python3 -m json.tool
~~~
=== "Python"
~~~ python
response = httpx.post(f"{HOST}/stop", timeout=T_READ)
response.raise_for_status()
print(response.json())
~~~
=== "MATLAB"
~~~ matlab
reply = webwrite([HOST '/stop'], struct(), ...
weboptions('MediaType', 'application/json', 'Timeout', T_READ));
disp(reply)
~~~
Команда отменяет программные операции, но не снимает питание с робота и не заменяет штатную аварийную остановку. После её вызова проверьте сообщение в ответе и реальное состояние робота.
Ошибки и диагностика
| Код | Когда возникает |
|---|---|
| 200 | Запрос обработан; для команд движения дополнительно проверьте поле success |
| 409 | Уже запущена последовательность движений |
| 422 | Некорректная структура запроса, число суставов, скорость, планировщик или лимиты суставов |
| 503 | ROS-топик, сервис, action-сервер или MoveIt недоступен; также возможен тайм-аут ожидания |
При проблемах идите от простого к сложному:
- Откройте /docs и убедитесь, что сервер запущен и маршрут присутствует в схеме.
- Проверьте /robot/joint_states. Без него не будет работать получение позы, а остановка траектории не сможет сформировать точку удержания.
- Убедитесь, что стек запущен полностью: controller_manager, MoveIt и iiwa_motion_server.
- После запуска последовательности посмотрите /sequences/logs; после публикации траектории — /trajectory/logs.
MCP-сервер работает в том же процессе, но это отдельный интерфейс: его адрес — http://адрес-сервера:8007/mcp/mcp. Для обычных HTTP-интеграций используйте маршруты из этой страницы.