参考官方文档链接(可能需要梯子):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