參考官方文檔鏈接(可能需要梯子):http://www.coppeliarobotics.com/helpFiles/index.html
文章目錄
概述
remote API的使用方式與regularAPI類似,但是有2點不同:
- 大多remote API都會返回一個位編碼值:
return code
。因爲return code
是bit-coded的,所以需要測試每一個位來確定正確的含義。 - 大多remote API都需要兩個額外的參數:
operation mode
和clientID
(simxStart
返回的標識符)。
需要operation mode
和return code
的原因是remote API函數需要通過socket通信機制從客戶端到服務端(VREP),執行任務,返回客戶端。一種簡單的(或常規的)方法是讓客戶機發送請求,然後等待服務器處理請求並作出響應:在大多數情況下,這會花費太多的時間,而且延遲會損害客戶機應用程序。實際上,remote API通過提供四種機制來執行函數或控制仿真過程,讓用戶選擇operation mode和仿真進行的方式。
- 阻塞式函數調用模式(Blocking function calls)
- 非阻塞時函數調用模式(Blocking function calls)
- 數據流模式(Data streaming)
- 同步模式(Synchronous operation)
阻塞式函數調用模式(Blocking function calls)
阻塞式函數調用模式是一種簡單常規的方式,適用於必須等待從服務端(VREP)返回信息的情形,比如如下情況:
// Following function (blocking mode) will retrieve an object handle:
if (simxGetObjectHandle(clientID,"myJoint",&jointHandle,simx_opmode_blocking)==simx_return_ok)
{
// here we have the joint handle in variable jointHandle!
}
下圖闡明瞭阻塞式函數調用模式:
例1:讀取UR5機械臂轉軸句柄
詳情:在場景中有一個UR5機械臂,以下代碼讀取機械臂各個軸的句柄值。
配套scene文件:
鏈接:https://pan.baidu.com/s/1cAUe15T7FMDWrGgUjLm9aw
提取碼:2w75
與下面例3場景一致。另外這個場景非常簡單,就只是在空場景裏拖拽一個UR5過去。自己操作就好,實在不放心可以再下載。
代碼:
import vrep
vrep.simxFinish(-1) # just in case, close all opened connections
clientID=vrep.simxStart( # clientID,經測試從0計數,若超時返回-1。若不返回-1,則應該在程序最後調用 simxFinish
'127.0.0.1', # 服務端(server)的IP地址,本機爲127.0.0.1
19997, # 端口號
True, # True:程序等待服務端開啓(或連接超時)
True, # True:連接丟失時,通信線程不會嘗試第二次連接
2000, # 正:超時時間(ms)(此時阻塞函數時間爲5s)負:阻塞函數時間(ms)(此時連接等待時間爲5s)
5) # 數據傳輸間隔,越小越快,默認5 # Connect to V-REP
print('Connected to remote API server')
vrep.simxStartSimulation(clientID, vrep.simx_opmode_oneshot)
RC1, UR5_joint1_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint1', vrep.simx_opmode_blocking)
RC2, UR5_joint2_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint2', vrep.simx_opmode_blocking)
RC3, UR5_joint3_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint3', vrep.simx_opmode_blocking)
RC4, UR5_joint4_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint4', vrep.simx_opmode_blocking)
RC5, UR5_joint5_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint5', vrep.simx_opmode_blocking)
RC6, UR5_joint6_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint6', vrep.simx_opmode_blocking)
print("RC1:",RC1)
print("UR5_joint1_Handle",UR5_joint1_Handle)
print("RC2:",RC1)
print("UR5_joint2_Handle",UR5_joint2_Handle)
print("RC3:",RC1)
print("UR5_joint3_Handle",UR5_joint3_Handle)
print("RC4:",RC1)
print("UR5_joint4_Handle",UR5_joint4_Handle)
print("RC5:",RC1)
print("UR5_joint5_Handle",UR5_joint5_Handle)
print("RC1:",RC1)
print("UR6_joint6_Handle",UR5_joint6_Handle)
非阻塞式函數調用模式(Non-blocking function calls)
非阻塞函數調用模式用在僅僅想給服務端(VREP)發送指令,而無需等待服務端返回信息的情況,例如如下情形:
// Following function (non-blocking mode) will set the position of a joint:
simxSetJointPosition(clientID,jointHandle,jointPosition,simx_opmode_oneshot);
下圖闡明瞭非阻塞式函數調用模式:
例2:置位轉軸角度
詳情:在場景中有一個轉軸(passive mode)A,連接着另一個轉軸B。要把轉軸A旋轉1rad。
配套scene文件:
鏈接:https://pan.baidu.com/s/1S5JPmIl_fz5qiovHEncINw
提取碼:4scy
代碼:
import vrep
vrep.simxFinish(-1) # just in case, close all opened connections
clientID=vrep.simxStart( # clientID,經測試從0計數,若超時返回-1。若不返回-1,則應該在程序最後調用 simxFinish
'127.0.0.1', # 服務端(server)的IP地址,本機爲127.0.0.1
19997, # 端口號
True, # True:程序等待服務端開啓(或連接超時)
True, # True:連接丟失時,通信線程不會嘗試第二次連接
2000, # 正:超時時間(ms)(此時阻塞函數時間爲5s)負:阻塞函數時間(ms)(此時連接等待時間爲5s)
5) # 數據傳輸間隔,越小越快,默認5 # Connect to V-REP
print('Connected to remote API server')
vrep.simxStartSimulation(clientID, vrep.simx_opmode_oneshot)
RC0, h= vrep.simxGetObjectHandle(clientID, 'j', vrep.simx_opmode_blocking)
vrep.simxSetJointPosition(clientID,h,1,vrep.simx_opmode_oneshot)
vrep.simxGetPingTime(clientID) # 不可少,否則很可能不執行,後面會解釋爲什麼
有些情形下,用一條指令傳送多條信息是很重要的——這些信息會在服務端同時執行(例如讓機器人的3個關節同時運動,即,在同一個仿真步中)。在這種情況下,用戶可以暫停通信進程來實現,如下所示:
simxPauseCommunication(clientID,1);
simxSetJointPosition(clientID,joint1Handle,joint1Value,simx_opmode_oneshot);
simxSetJointPosition(clientID,joint2Handle,joint2Value,simx_opmode_oneshot);
simxSetJointPosition(clientID,joint3Handle,joint3Value,simx_opmode_oneshot);
simxPauseCommunication(clientID,0);
// Above's 3 joints will be received and set on the V-REP side at the same time
下圖闡明瞭暫停通信進程的效果:
數據流模式(Data streaming)
服務端可以預測客戶端需求的數據類型。要實現這一點,客戶端必須用流(streaming)
或者連續(continuous)
操作模式flag向服務端發出此請求(即:函數被存放在服務端,在不需要客戶端發出請求的情況下,定期執行併發送數據)。這可以看做是從客戶端到服務端的命令(command)/信息(message)
訂閱,其中服務端像客戶端提供數據流。在客戶端這種數據流操作請求和讀取流數據如下所示:
// Streaming operation request (subscription) (function returns immediately (non-blocking)):
simxGetJointPosition(clientID,jointHandle,&jointPosition,simx_opmode_streaming);
// The control loop:
while (simxGetConnectionId(clientID)!=-1) // while we are connected to the server..
{
// Fetch the newest joint value from the inbox (func. returns immediately (non-blocking)):
if (simxGetJointPosition(clientID,jointHandle,&jointPosition,simx_opmode_buffer)==simx_return_ok)
{
// here we have the newest joint position in variable jointPosition!
}
else
{
// once you have enabled data streaming, it will take a few ms until the first value has arrived. So if
// we landed in this code section, this does not always mean we have an error!!!
}
}
// Streaming operation is enabled/disabled individually for each command and
// object(s) the command applies to. In above case, only the joint position of
// the joint with handle jointHandle will be streamed.
下圖闡明瞭數據流操作模式:
數據流提取完後,要通知服務端停止數據流傳輸,否則服務端將一直傳送無用數據,並導致速度下降。用simx_opmode_discontinue來實現停止傳輸。
例3:讀取UR5機械臂轉軸角度
詳情:在場景中有一個UR5機械臂,以下代碼讀取機械臂各個軸的角度值。
配套scene文件:
鏈接:https://pan.baidu.com/s/1cAUe15T7FMDWrGgUjLm9aw
提取碼:2w75
與上面例1場景一致。另外這個場景非常簡單,就只是在空場景裏拖拽一個UR5過去。自己操作就好,實在不放心可以再下載。
代碼:
import vrep
import time
vrep.simxFinish(-1) # just in case, close all opened connections
clientID=vrep.simxStart( # clientID,經測試從0計數,若超時返回-1。若不返回-1,則應該在程序最後調用 simxFinish
'127.0.0.1', # 服務端(server)的IP地址,本機爲127.0.0.1
19997, # 端口號
True, # True:程序等待服務端開啓(或連接超時)
True, # True:連接丟失時,通信線程不會嘗試第二次連接
2000, # 正:超時時間(ms)(此時阻塞函數時間爲5s)負:阻塞函數時間(ms)(此時連接等待時間爲5s)!不太理解!
5) # 數據傳輸間隔,越小越快,默認5 # Connect to V-REP
print('Connected to remote API server')
vrep.simxStartSimulation(clientID, vrep.simx_opmode_oneshot)
RC1, UR5_joint1_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint1', vrep.simx_opmode_blocking)
RC2, UR5_joint2_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint2', vrep.simx_opmode_blocking)
RC3, UR5_joint3_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint3', vrep.simx_opmode_blocking)
RC4, UR5_joint4_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint4', vrep.simx_opmode_blocking)
RC5, UR5_joint5_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint5', vrep.simx_opmode_blocking)
RC6, UR5_joint6_Handle = vrep.simxGetObjectHandle(clientID, 'UR5_joint6', vrep.simx_opmode_blocking)
vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_streaming)
vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_streaming)
vrep.simxGetJointPosition(clientID, UR5_joint3_Handle, vrep.simx_opmode_streaming)
vrep.simxGetJointPosition(clientID, UR5_joint4_Handle, vrep.simx_opmode_streaming)
vrep.simxGetJointPosition(clientID, UR5_joint5_Handle, vrep.simx_opmode_streaming)
vrep.simxGetJointPosition(clientID, UR5_joint6_Handle, vrep.simx_opmode_streaming)
while(True):
if (vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[0] # 判斷vrep是否開始回傳數據
& vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_buffer)[0] # [0]是指return中的第0位,也即return code
& vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_buffer)[0]
& vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_buffer)[0]
& vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_buffer)[0]
& vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_buffer)[0])==vrep.simx_return_ok:
for i in range(3): # 提取3次關節角度
rc1, j1_pos=vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)
rc2, j2_pos=vrep.simxGetJointPosition(clientID, UR5_joint2_Handle, vrep.simx_opmode_buffer)
rc3, j3_pos=vrep.simxGetJointPosition(clientID, UR5_joint3_Handle, vrep.simx_opmode_buffer)
rc4, j4_pos=vrep.simxGetJointPosition(clientID, UR5_joint4_Handle, vrep.simx_opmode_buffer)
rc5, j5_pos=vrep.simxGetJointPosition(clientID, UR5_joint5_Handle, vrep.simx_opmode_buffer)
rc6, j6_pos=vrep.simxGetJointPosition(clientID, UR5_joint6_Handle, vrep.simx_opmode_buffer)
print("j1_pos:", j1_pos)
print("j2_pos:", j2_pos)
print("j3_pos:", j3_pos)
print("j4_pos:", j4_pos)
print("j5_pos:", j5_pos)
print("j6_pos:", j6_pos)
print("-----------------------")
time.sleep(0.2)
break
else:
print("waiting for server response...")
time.sleep(0.001) # 0.001是我手調出來的,便於演示而已
# 測試服務端是否繼續在發送數據給客戶端,以第一個關節爲例
time.sleep(3)
if vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[0]==vrep.simx_return_ok:
print("客戶端待機3秒後,服務端依然在發送數據。")
print("關節1的角度爲",vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[1])
elif vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[0]==vrep.simx_return_novalue_flag:
print("客戶端待機3秒後,服務端已停止發送數據。")
# 強制擦除存放在服務端的指令,再測試服務端是否還在發送數據
print('擦除存放在服務端的指令...')
while True:
# 因爲客戶端到服務端的指令是有延遲的,所以需要這個While循環來確保確實已經擦除服務端的命令,實際使用時不必這樣測試。
# 另外這裏面的邏輯需要注意一下,第一次檢測到vrep.simx_return_novalue_flag時,應該是While循環第一個指令造成的,而不是
# 當前的那個
rc1, j1_pos=vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_discontinue)
if rc1==vrep.simx_return_ok:
print("waiting for server response...")
time.sleep(0.001) # 0.001是我手調出來的,便於演示而已
elif rc1==vrep.simx_return_novalue_flag:
print("server responds!")
break
if vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[0]==vrep.simx_return_ok:
print("強制擦除後,服務端依然在發送數據。")
print("關節1的角度爲",vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[1])
elif vrep.simxGetJointPosition(clientID, UR5_joint1_Handle, vrep.simx_opmode_buffer)[0]==vrep.simx_return_novalue_flag:
print("強制擦除後,服務端已停止發送數據。")
Connected to remote API server
waiting for server response...
waiting for server response...
waiting for server response...
waiting for server response...
waiting for server response...
waiting for server response...
j1_pos: 0.04933595657348633
j2_pos: 0.04936075210571289
j3_pos: -0.049343109130859375
j4_pos: 0.04935884475708008
j5_pos: 0.04934239387512207
j6_pos: 0.049343109130859375
-----------------------
j1_pos: 0.9543983936309814
j2_pos: 0.9548768997192383
j3_pos: -0.955643892288208
j4_pos: 0.9558093547821045
j5_pos: 0.9556386470794678
j6_pos: 0.9556429386138916
-----------------------
j1_pos: 1.5655755996704102
j2_pos: 1.565735101699829
j3_pos: -1.5655508041381836
j4_pos: 1.5706815719604492
j5_pos: 1.5680694580078125
j6_pos: 1.568070411682129
-----------------------
客戶端待機3秒後,服務端依然在發送數據。
關節1的角度爲 -9.5367431640625e-07
擦除存放在服務端的指令...
waiting for server response...
waiting for server response...
waiting for server response...
waiting for server response...
waiting for server response...
server responds!
強制擦除後,服務端已停止發送數據。
同步模式(Synchronous operation)
以上3種模式在仿真進行時服務端只管往前運行,並不考慮客戶端的進度。Remote API在默認情況下是異步運行的。但有時候,我們需要客戶端與仿真過程同步——通過遠程API控制仿真進度實現。這可以用Remote API的同步模式實現。此時服務端需要提前設置爲同步模式。
服務端設置同步模式可以通過以下幾種方式實現
simRemoteApi.start
函數- 連續remote API服務端服務配置文件
remoteApiConnections.txt
以下是同步模式的例子
simxSynchronous(clientID,true); // Enable the synchronous mode (Blocking function call)
simxStartSimulation(clientID,simx_opmode_oneshot);
// The first simulation step waits for a trigger before being executed
simxSynchronousTrigger(clientID); // Trigger next simulation step (Blocking function call)
// The first simulation step is now being executed
simxSynchronousTrigger(clientID); // Trigger next simulation step (Blocking function call)
// The second simulation step is now being executed
...
下圖闡明瞭同步操作模式:
當調用同步觸發器(simxSynchronousTrigger
)時,下一個仿真步開始計算。這並不意味着當函數調用返回時,下一個模擬步驟將完成計算。因此,您必須確保讀取正確的數據。如果沒有采取特殊措施,則可能從之前的仿真步驟或當前仿真步驟讀取數據,如下圖所示:
有幾種方式來克服以上的問題。
最簡單的方法是在調用同步觸發器(simxSynchronousTrigger
)後直接以阻塞方式調用函數(其實這裏官網想表達的意思是直接調用一個阻塞方式的函數,函數任意,比如simxGetPintTime
,原因嘛,就是讓這個阻塞過程來強制佔用(這裏不好解釋了)):
simxSynchronous(clientID,true); // Enable the synchronous mode (Blocking function call)
simxStartSimulation(clientID,simx_opmode_oneshot);
// The first simulation step waits for a trigger before being executed
simxSynchronousTrigger(clientID); // Trigger next simulation step (Blocking function call)
// The first simulation step is now being executed
simxGetPingTime(clientID); // After this call, the first simulation step is finished (Blocking function call)
// Now we can safely read all streamed values
下圖說明了上述過程
下圖說明了如何在服務器端(即在V-REP遠程API插件端)處理初始化的遠程API命令:
附加內容
在客戶端(即,你的IDE),最少會運行兩個線程:①主線程(從中調用Remote API);②通信線程(從中傳送數據)。在客戶端,可以有任意多的通信線程(通信線):可用simxStart
來啓動每一個。在服務器端使用V-REP插件實現,以類似的方式運行。下圖說明了Remote API的工作方式:
- simx_opmode_oneshot:非阻塞模式(non-blocking mode)。命令送去服務端執行
(1)-(b)-(3)
。從本地緩衝區返回對先前執行的同一命令的響應(如果有的話(i)-(2)
)。函數不等待從服務端(7)-(i)
的響應。
在服務端,指令被暫存在(4)-(d)
,沿着(d)-(9)-(g)
執行一次,並沿着(g)-(6)
傳送響應結果。這個模式常常被“設置類函數(set-functions)”(如simxSetJointPosition
)使用,用戶並不關心返回值。 - simx_opmode_blocking:阻塞模式(blocking mode)。命令送去服務端執行
(1)-(b)-(3)
,並等待從服務端返回的響應(7)-(i)-(2)
。然後接收到的響應被從輸入箱緩存中刪除(i)
,這操作是阻塞模式獨有的。
在服務端,指令被暫存在(4)-(d)
,沿着(d)-(9)-(g)
執行一次,並沿着(g)-(6)
傳送響應結果。這個模式常常被“得到類函數(get-functions)”(如simxGetObjectHandle
)使用,用戶需要得到響應。 - simx_opmode_streaming:非阻塞模式(non-blocking mode)。命令送去服務端執行
(1)-(b)-(3)
。從本地緩衝區返回對先前執行的同一命令的響應(如果有的話(i)-(2)
)。函數不等待從服務端(7)-(i)
的響應。
與simx_opmode_oneshot類似,但是在服務端指令被暫存在(4)-(e)
(而非(4)-(d)
),連續執行(e)-(9)-(g)
,並持續傳送回客戶端(g)-(6)
。這個模式常常被“得到類函數(set-functions)”(如simxGetJointPosition
)使用,用戶經常需要一個特定的值。 - simx_opmode_oneshot_split
- simx_opmode_streaming_split
- simx_opmode_discontinue
- simx_opmode_buffer:非阻塞模式(non-blocking mode)。不向服務端傳送指令,但是如果
(i)-(2)
可用,則從本地緩衝區返回對先前執行的相同命令的響應。此模式通常與simx_opmode_streaming或simx_opmode_streaming_split操作模式一起使用:首先,用一個streaming指令啓動,然後提取數據。 - simx_opmode_remove