При моделировании робототехнических систем в программном комплексе CoppeliaSim пользователь сталкивается с необходимостью создания настраиваемого пользовательского интерфейса (сustom user interfaces). CoppeliaSim предлагает создание настраиваемого пользовательского интерфейса с помощью штатного стредства – встроенного плагина Qt.
К сожалению плагин Qt ограничен в возможнастях. Однако стоит отметить, что данный недостаток вытекает из его основного достоинства – простоты разработки пользовательского интерфейса.
В рамках данной статьи рассмотрим процесс создания пользовательского интерфейса в Lazarus (бесплатный аналог Delphi). В качестве примера возмем модель промышленного робота ABB IRB 140.
Запустим CoppeliaSim и добавим модель робота ABB IRB 140 на сцену. Для этого в обзорщике моделей в дереве robots необходимо выбирать no-mobile и перетащить промышленного робота ABB IRB 140 на сцену. Модель промышленного робота ABB IRB 140 представлена на рисунке 1.
Далее необходимо удалить ненужные объекты из иерархии сцены, как показано на рисунке 2.
Для всех шарниров (IRB140_joint1…IRB140_joint6) в окне Scene Object Properties во вкладке Joint в поле Mode значение Torque/force mode необходимо изменить на Passive mode.
Далее приступим к разработке управляющего скрипта.
Связь между программами будем осуществлять посредством TCP/IP протокола по 5050 сокету. В качестве клиента будет выступать приложение, созданное в Lazarus, в качестве сервера – CoppeliaSim.
Клиент на сервер будет передавать информацию в следующем виде:
[FK#J1=0.00,J2=0.00,J3=0.00,J4=0.00,J5=0.00,J6=0.00]
Наш управляющий скрипт для CoppeliaSim:
local socket = require("socket")
local server = assert(socket.bind("*", 5050))
local tcp = assert(socket.tcp())
function sysCall_init()
simJoints={}
for i=1,6,1 do
simJoints[i]=sim.getObjectHandle('IRB140_joint'..i)
end
end
function sysCall_actuation()
local client = server:accept()
--print("request received")
client:settimeout(10)
local packet, err = client:receive()
if not err then
--print("received from client=",packet)
cmd=string.sub(packet, 2,3)
-----------------------------------------------
if string.find(cmd, "FK") then
i= string.find(packet, "J1=")
j= string.find(packet, ",J2=")
j1=string.sub(packet, i+3, j-1)
j1=string.gsub(j1, ",", ".")
j1=tonumber(j1)
-------------------------------
i= string.find(packet, "J2=")
j= string.find(packet, ",J3=")
j2=string.sub(packet, i+3, j-1)
j2=string.gsub(j2, ",", ".")
j2=tonumber(j2)
-------------------------------
i= string.find(packet, "J3=")
j= string.find(packet, ",J4=")
j3=string.sub(packet, i+3, j-1)
j3=string.gsub(j3, ",", ".")
j3=tonumber(j3)
-------------------------------
i= string.find(packet, "J4=")
j= string.find(packet, ",J5=")
j4=string.sub(packet, i+3, j-1)
j4=string.gsub(j4, ",", ".")
j4=tonumber(j4)
-------------------------------
i= string.find(packet, "J5=")
j= string.find(packet, ",J6=")
j5=string.sub(packet, i+3, j-1)
j5=string.gsub(j5, ",", ".")
j5=tonumber(j5)
-------------------------------
i= string.find(packet, "J6=")
j= string.find(packet, "]")
j6=string.sub(packet, i+3, j-1)
j6=string.gsub(j6, ",", ".")
j6=tonumber(j6)
-------------------------------
--print("J1=",j1)
--print("J2=",j2)
--print("J3=",j3)
--print("J4=",j4)
--print("J5=",j5)
--print("J6=",j6)
-------------------------------
sim.setJointPosition(simJoints[1],j1)
sim.setJointPosition(simJoints[2],j2)
sim.setJointPosition(simJoints[3],j3)
sim.setJointPosition(simJoints[4],j4)
sim.setJointPosition(simJoints[5],j5)
sim.setJointPosition(simJoints[6],j6)
end
client:send(packet .. "\n")
end
-- print("closing socket")
client:close()
end
С помощью string.gsub в принимаемой строке производим замену всех запятых на точки. Данное действие необходимо из-за русскоязычной локализации Windows.
Далее приступим к созданию клиента.
Узнаем максимальные значения углов поворота осей для промышленного робота ABB IRB 140 из документации на него (либо ищем в интернете). Не забываем, что на сервер мы отправляем значения углов в радианах.
Данная статья подразумевает некоторое умение работы с Delphi/Lazarus и знание их основных компонентов, поэтому опустим эти моменты. Подчеркнем лишь то, что для передачи на сервер мы формируем стоку в следующем виде:
[FK#J1=0.00,J2=0.00,J3=0.00,J4=0.00,J5=0.00,J6=0.00]
В разделе uses подключаем библиотеку WinSock.
В разделе var объявим следующие переменные:
S:TSocket;
Addr:TSockAddr;
Data:TWSAData;
i: integer;
b: byte;
bfr: TBytes;
В обработчике нажатия кнопки «Старт» пишем следующий код:
procedure TForm1.Button1Click(Sender: TObject);
begin
WSAStartup($101, Data); //загружаем WinSock;
Timer1.Enabled:=true;
end;
Добавляем на форму компонент таймер TTimer и в обработчике событий таймера OnTimer пишем следующий код:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
s:=Socket(AF_INET, SOCK_STREAM,IPPROTO_IP); //создаем сокет
Addr.sin_family:=AF_Inet; //задаем семейство адресов
Addr.sin_port:=HToNS(5050); //задаем номер порта
Addr.sin_addr.S_addr:=Inet_Addr('127.0.0.1'); //задаем IP-адрес
FillChar(Addr.Sin_Zero,SizeOf(Addr.Sin_Zero),0); //заполняем нулями поля
Connect(S,Addr,SizeOf(TSockAddr)); //подключаемся к серверу
st:='[FK#J1='+FloatToStr(j1)+',J2='+FloatToStr(j2)+',J3='+FloatToStr(j3)+',J4='+FloatToStr(j5)+',J5='+FloatToStr(j4)+',J6='+FloatToStr(j6)+']'; //передаваемая строка
bfr:= SysUtils.TEncoding.ASCII.GetBytes(st + chr(13) + chr(10)); // переводим строку в массив байтов
for i:=0 to ((Length(bfr))-1) do //отправляем данные на сервер по одному байту
begin
b:=bfr[i];
Send(S,b,1,0);
end;
Shutdown (s, 1); //завершение соединения
CloseSocket(S); //отключение от сервера
sleep(1);
end;
В обработчике нажатия кнопки «Стоп» пишем следующий код:
procedure TForm1.Button2Click(Sender: TObject);
begin
Timer1.Enabled:=false;
Shutdown (s, 1); //завершение соединения
CloseSocket(S); //отключение от сервера
WSACleanup(); //выгрузка сетевой библиотеки
end;
В нашем случае приложение имеет вид, представленный на рисунке 3.
Исходный файл проекта располагается на сервисе GitHub по следующей ссылке.
Запускаем симулирование промышленного робота ABB IRB 140 в CoppeliaSim, нажимаем на кнопку «Старт» в окне нашего приложения созданного Lazarus и радуемся результатом – теперь моделью промышленного робота можно управлять.
Таким образом, применение внешней интегрированной среды разработки может значительно улучшить процесс моделирования в CoppeliaSim.
Литература
1. Визуальное представление прямой задачи кинематики промышленного робота ABB IRB 140 с помощью CoppeliaSim и Qt. — Текст : электронный // Habr : [сайт]. — URL: https://habr.com/ru/post/598987/ (дата обращения: 29.01.2022).
2. LuaSocket API documentation. — Текст : электронный // Defold : [сайт]. — URL: https://defold.com/ref/beta/socket/ (дата обращения: 29.01.2022).
3. Использование сокетов в Delphi. Часть первая: стандартные сокеты. — Текст : электронный // Королевство Delphi : [сайт]. — URL: http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1021 (дата обращения: 29.01.2022).
4. Фленов, М. Е. Delphi в шутку и всерьез: что умеют хакеры (+CD) / М. Е. Фленов. — СПб : Питер, 2006. — 271 c.
5. Lua 5.3 Руководство. — Текст : электронный // Lua : [сайт]. — URL: https://lua.org.ru/contents_ru.html (дата обращения: 29.01.2022).
6. String Library Tutorial. — Текст : электронный // lua-users wiki : [сайт]. — URL: http://lua-users.org/wiki/StringLibraryTutorial (дата обращения: 29.01.2022).
7. System.SysUtils.TEncoding.GetBytes.
— Текст : электронный // RAD Studio API Documentation : [сайт]. — URL:
https://docwiki.embarcadero.com/Libraries/Sydney/en/System.SysUtils.TEncoding.GetBytes
(дата обращения: 29.01.2022).
Комментарии (4)
vipassa
30.01.2022 17:27И что? Где результаты моделирования? Я остался неудовлетворённым! Ждём видео о полученных результатах)
z0ic
30.01.2022 19:03+2Никогда не встречал прямую кинематику в системах управления роботами. ИМХО мне больше всего нравится представление алгоритма в виде визуального графа.
ashumkin
31.01.2022 19:26C помощью string.gsub в принимаемой строке производим замену всех запятых на точки. Данное действие необходимо из-за русскоязычной локализации Windows.
Дело не столько в локализации Windows, сколько в том, что вы не учитываете на КЛИЕНТЕ, что она может быть разная, и используете FloatToStr без FormatSettings. Это
нубскийнепрофессиональный подход ))иначе говоря,
вы не установили контракт между сервером и клиентом: что сервер принимает вот такие данные в таком-то виде/формате (т.е. если число - дробное/вещественное, то разделитель - точка (по всяким общепринятым стандартам)), и клиент должен этому следовать.Upd. а, установили
Клиент на сервер будет передавать информацию в следующем виде:
[FK#J1=0.00,J2=0.00,J3=0.00,J4=0.00,J5=0.00,J6=0.00]
только клиент ему не следует, а вы "исправили" это на сервере, усложнив его
RISA
Позвольте высказать немного критики и сделать code review.Функцию sysCall_actuation можно упростить отделив бизнес логику от парсинга, парсинг при этом сделан через Ctrl+C/Ctrl-V и его можно упростить выделив повторяющуюся логику в фунуции.
Хорошим тоном является задание реальных имён компонентам, а не button1 и др.