При моделировании робототехнических систем в программном комплексе 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

Далее необходимо удалить ненужные объекты из иерархии сцены, как показано на рисунке 2.

Рисунок 2 – Иерархия сцены после удаления ненужных объектов
Рисунок 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.

Рисунок 3 – Созданное в Lazarus приложение
Рисунок 3 – Созданное в Lazarus приложение

Исходный файл проекта располагается на сервисе 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)


  1. RISA
    30.01.2022 14:43

    Позвольте высказать немного критики и сделать code review.Функцию sysCall_actuation можно упростить отделив бизнес логику от парсинга, парсинг при этом сделан через Ctrl+C/Ctrl-V и его можно упростить выделив повторяющуюся логику в фунуции.

    Хорошим тоном является задание реальных имён компонентам, а не button1 и др.


  1. vipassa
    30.01.2022 17:27

    И что? Где результаты моделирования? Я остался неудовлетворённым! Ждём видео о полученных результатах)


  1. z0ic
    30.01.2022 19:03
    +2

    Никогда не встречал прямую кинематику в системах управления роботами. ИМХО мне больше всего нравится представление алгоритма в виде визуального графа.


  1. ashumkin
    31.01.2022 19:26

    C помощью 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]

    только клиент ему не следует, а вы "исправили" это на сервере, усложнив его