При моделировании робототехнических систем в программном комплексе 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.
![Рисунок 1 – Модель промышленного робота ABB IRB 140 Рисунок 1 – Модель промышленного робота ABB IRB 140](https://habrastorage.org/getpro/habr/upload_files/143/7db/13c/1437db13c39cac4bcdc91aed59698cab.png)
Далее необходимо удалить ненужные объекты из иерархии сцены, как показано на рисунке 2.
![Рисунок 2 – Иерархия сцены после удаления ненужных объектов Рисунок 2 – Иерархия сцены после удаления ненужных объектов](https://habrastorage.org/getpro/habr/upload_files/d11/797/502/d117975023ff6ef5744b40e65cbdcd74.png)
Для всех шарниров (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.
![Рисунок 3 – Созданное в Lazarus приложение Рисунок 3 – Созданное в Lazarus приложение](https://habrastorage.org/getpro/habr/upload_files/ed7/c94/5f1/ed7c945f1a0680dbdc1b8d67ad9ce654.png)
Исходный файл проекта располагается на сервисе 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 и др.