“ 不要把整个世界都扛在自己肩上,想做什么,就去做点什么吧!”
今天记录的是VREP中的script脚本的内容。
01
—
The main script
主脚本是仿真脚本。默认情况下,V-REP中的每个场景都有一个主脚本。它包含允许仿真运行的基本代码。如果没有主脚本,仿真运行时将不会执行任何操作。
主脚本包含系统经常调用的函数。如果一个给定的函数没有定义,这个调用将被忽略。除了初始化函数之外,所有其他函数都是可选的。默认的主脚本通常分为4个函数:
the initialization function初始化函数:sysCall_init。这部分将在仿真开始时执行一次。代码负责准备仿真。
the actuation function驱动函数:sysCall_actuation。这部分将在每次仿真过程中执行。代码是负责处理所有的驱动功能的模拟器(逆运动学,动力学等)。例如sim.launchThreadedChildScripts, sim.resumeThreads以及sim.handleChildScripts.
the sensing function感知功能:sysCall_sensing。这部分将在每次模拟过程中执行。代码负责以通用的方式处理模拟器的所有感知功能(接近传感器、碰撞检测等)。例如sim.resumeThreads 以及 sim.handleChildScripts.
the restoration function恢复函数:sysCall_cleanup。这部分将在模拟结束之前执行一次。该代码负责恢复对象的初始配置、清除传感器状态、碰撞状态等。
主脚本不应被修改。原因如下:
V-REP的优点之一是任何模型(机器人、执行器、传感器等)都可以被复制到场景中,并且可以立即运行。当修改主脚本时,将面临模型不能按预期执行的风险(例如,如果主脚本缺少 sim.handleChildScripts命令,所有的模型复制到场景将不会操作)。
另一个原因是,保留一个默认的主脚本可以让旧场景很容易地适应新功能(例如,如果一个新的V-REP版本引入了一个命令sim.doMagic(),那么旧场景将自动更新,以便在其主脚本中自动调用该命令)。 但是,如果出于某种原因,确实需要修改场景的主要脚本,可以双击场景层次结构顶部世界图标旁边的淡红色脚本图标:
从打开主脚本的那一刻起,它将被标记为定制,并且不再自动更新。 主脚本中的大多数命令的行为或操作方式都类似。如果我们以距离计算功能为例,我们在常规部分有:
sim.handleDistance(sim.handle_all_except_explicit): 这个命令的作用是计算已设置的测距对象的最小距离,在距离计算对话框中列出(处理距离计算将计算其最小距离,距离对象设置距离变量和最小距离段将显示在现场)。除了那些被标记为显式处理的对象外,所有距离对象都用该命令处理(即计算)。 任何新的distance对象都将自动使用上面的命令进行处理(只要它没有被标记为显式处理)。同样的机制也适用于碰撞检测,接近传感器和视觉传感器仿真,逆运动学等。这是一种强大的机制,允许运行简单的模拟,而不需要编写任何代码。
主脚本中最重要的命令是sim.handleChildScripts,它在驱动函数内部和在感知函数内部被调用。没有这个命令,就不会执行非线程化的子脚本。如果你看一下默认的主脚本,你会注意到驱动功能允许驱动或修改场景内容.下面的例子说明了在模拟一个装有接近传感器的移动机器人时,在默认的主脚本中会发生什么:
考虑到上面的顺序,子脚本将始终读取(使用sim.readProximitySensor)前面的感知(发生在前面的模拟通道结束时,在主脚本中,使用sim.handleProximitySensor)中的接近传感器的状态,然后对障碍做出反应。 如果需要明确地处理一个传感器,那么请确保总是在感应部分这样做,否则,能会出现如下图所示的显示错误的情况:
正如主脚本具有驱动和感知功能一样,非线程子脚本也是如此。另一方面,线程化的子脚本可以在主脚本处于执行或检测函数时重新调度运行请参考API函数sim.setThreadResumeLocation.
02
—
Child scripts
子脚本是模拟脚本。V-REP支持每个场景不限数量的子脚本。每个子脚本表示用Lua编写的一小组例程,允许在模拟中处理特定的函数。子脚本被附加到场景对象上(或与场景对象相关联),它们可以很容易地从场景层次结构中的脚本图标中识别出来:
双击脚本图标可以打开脚本编辑器,可以更改给定脚本的属性,或者通过脚本对话框将其与另一个对象关联。通过选择对象,然后通过[menu bar --> Add --> Associated child script].可以将一个新的子脚本附加到一个对象。(具体操作下次视频出)
子脚本与场景对象的关联具有重要和积极的后果:
- Very good portability
- Inherent scalability
- No conflict between different model versions
- Very easy synchronization with the simulation loop
子脚本可以附加一组模拟参数,称为脚本模拟参数。这些参数可以作为调整特定仿真模型值的快速方法(例如,移动机器人的最大速度或传感器的分辨率)。
子脚本具有两种不同的类型:non-threaded child scripts 、 threaded child scripts
下面展开介绍:
non-threaded child scripts
非线程的子脚本包含一组阻塞函数。这意味着每次调用时,都应该执行一些任务,然后返回控制。如果不返回控制,则整个模拟将停止。非线程的子脚本函数由主脚本的驱动和感知函数在每个模拟步骤中调用两次。系统还将在适当的时候调用子脚本(例如,在子脚本初始化、清理或回调函数被触发期间)。只要可能,这种类型的子脚本应该总是在线程化的子脚本中选择。 非线程化的子脚本遵循精确的调用或执行顺序:默认情况下,子脚本的调用从leaf objects 叶子对象(或childless objects无子对象)开始,以根对象(或无父对象)结束。sim.handleChildScripts命令从默认的主脚本调用,处理非线程子脚本的调用。 想象一个代表自动门的仿真模型的例子:前面和后面的接近传感器可以检测到一个接近的人。当人够近的时候,门就会自动打开。下面的代码显示了一个典型的非线程的子脚本,说明上述例子:
一个非线程化的子脚本应该被分成4个主要函数: the initialization function初始化函数:sysCall_init。此部分只执行一次(第一次调用子脚本)。这可以是模拟的开始,也可以是模拟的中间:请记住,与子脚本相关的对象可以在任何时候复制/粘贴到场景中,也可以在模拟运行时复制/粘贴到场景中。通常,您会在这一部分中放入一些初始化代码以及处理检索。 the actuation function驱动函数:sysCall_actuation。此部分将在仿真步骤的启动阶段的每个仿真步骤中执行。有关驱动阶段的更多细节,请参考主脚本默认代码,但通常情况下,您会在这一部分中执行一些驱动(没有感知)。 the sensing function感知功能:sysCall_sensing。这部分将在每个仿真步骤中执行,在仿真步骤的敏感阶段。有关检测阶段的更多细节,请参考主脚本默认代码,但通常情况下,您只会在这部分进行检测(不执行驱动)。 the restoration function恢复函数:sysCall_cleanup。这部分将在模拟结束之前或脚本被销毁之前执行一次。
threaded child scripts
线程化的子脚本是将在线程中启动的脚本。线程子脚本的启动(和恢复)由默认的主脚本代码通过sim.launchThreadedChildScripts和sim.resumeThreads来实现。线程子脚本的启动/恢复以精确的顺序执行。当线程子脚本的执行仍在进行时,它将不会第二次启动。当一个线程化的子脚本结束时,只有当脚本属性中的“执行一次”项未选中时,才可以重新启动它。场景层次结构中的一个线程子脚本图标显示为淡蓝色,而不是白色,表示它将在一个线程中启动。 与没有适当编程的非线程子脚本相比,线程子脚本有几个弱点:它们更消耗资源,可能会浪费一些处理时间,并且可能对模拟停止命令的响应更慢。 下面展示了一个典型的线程子脚本代码,但它并不完美,因为它在循环中浪费了宝贵的计算时间(代码处理上面例子中的自动滑动门):
线程子脚本应被分成两部分: the main part主部分:这部分将在线程开始时执行,直到线程结束前不久。这可以是模拟的开始,也可以是模拟的中间,与子脚本相关的对象可以在任何时候复制/粘贴到场景中,也可以在仿真运行时复制/粘贴到场景中。通常,在这部分中放置一些初始化代码和主循环:循环中的代码负责处理仿真的特定部分(例如,处理自动滑动门)。在上面的具体例子中,循环浪费了宝贵的计算时间,并且与主模拟循环异步运行。请进一步查看下面的示例。 the restoration part恢复部分:这部分将在模拟结束之前或线程结束之前执行一次。 V-REP使用线程来模拟协同程序,这允许很大的灵活性和控制:默认情况下,一个线程的子脚本将执行大约1-2毫秒,然后自动切换到另一个线程。此默认行为可以通过sim.setThreadSwitchTiming或sim.setThreadAutomaticSwitch更改。一旦当前线程被切换,它将在下一次模拟中继续执行(即在一个时间currentTime simulationTimeStep)。线程切换是自动的(在指定的时间之后发生),但是sim.switchThread命令允许在需要时缩短时间。使用上述三个命令,可以实现与主仿真循环的良好同步。下面的代码(处理上面例子中的自动滑动门)显示了与主模拟循环的子脚本同步:
上面的while循环现在将对每个主模拟循环精确执行一次,而不会浪费时间对相同的模拟时间一次又一次地读取传感器状态。默认情况下,当主脚本调用sim. resumeThreads (sim.scriptthreadresume_default)时,线程总是会继续运行。如果需要确保线程只在主脚本属于感应阶段时运行,可以使用API函数sim.setThreadResumeLocation重新安排线程的恢复位置。 V-REP线程的类协程行为无法与普通线程区分开来,除非外部命令(例如Lua库提供的套接字通信命令)被阻塞,否则V-REP也会显示为阻塞。在这种情况下,非阻塞段可以定义为:
在非阻塞部分中,尽量避免调用sim函数。永远不要忘记关闭阻塞部分,否则V-REP可能会挂起或运行得更慢。 不应该为了正确执行而将某些操作中断(想象一下在一个循环中移动几个对象)。在这种情况下,可以使用sim.setThreadAutomaticSwitch函数暂时禁止进行线程切换。
03
—
Script execution order
接下来介绍脚本执行顺序:
脚本不是以一种随机的方式执行的:脚本类型、脚本位置和脚本设置可以影响脚本何时执行(相对于其他脚本)。要记住的一个简单规则是:脚本越重要或越持久,它被调用/执行的时间就越晚。 执行顺序首先基于脚本类型。我们有以下顺序,从第一次执行到最后一次执行: threaded child scripts are launched/resumed启动/恢复线程子脚本(可以通过simm.setthreadresumelocation调整顺序) non-threaded child scripts are called调用非线程化的子脚本 customization scripts are called调用定制脚本 add-on scripts are called调用附加脚本 the sandbox script is called调用沙箱脚本 因为子脚本是模拟脚本,所以只要模拟在运行,它们就会运行(即它们不是持久的)。定制脚本、附加脚本和沙箱脚本则不是这样,它们也在模拟停止后运行。此外,附加脚本和沙箱脚本在切换到不同场景时也会继续运行。上面的顺序是有意义的,因为重要的脚本可以被设计成依赖于不那么重要的脚本生成的数据并对其进行操作。 例如,回调sysCall_sensing将按以下顺序调用:首先在子脚本中调用,然后在自定义脚本中调用,在附加脚本中调用,最后在沙箱脚本中调用。 在脚本类型中,执行顺序是脚本在场景层次结构中的位置的函数,它有以下两个脚本设置:
- Execution priority执行优先级:指定脚本的执行优先级。执行优先级只与同类型脚本有关,并且只与场景层次结构中的兄弟脚本有关。
- Tree traveral:指定脚本执行的时间与场景层次结构中更底层的脚本(其后代脚本)相关。使用reverse时,首先执行后代脚本,使用forward时,最后执行后代脚本。与父级相同,使用与第一个祖先脚本相同的tree traveral。The same tree traversal只与相同类型的脚本相关。默认是相反的。
下图是一个具体的例子:
今天就到这里
作者知识有限,有不正确的地方还请读者能够指正,大家共同学习进步,不胜感激。