-
Notifications
You must be signed in to change notification settings - Fork 16
Main Script
When a project contains user-defined logic, the Build Service generates the Main Script while publishing the package. The script's structure looks like this (with explanatory comments added):
# import of Object Types
from object_types.time_actions import TimeActions
# generated helper class for easier access
from action_points import ActionPoints
# contains scene/project data, manages object instances, etc.
from arcor2_runtime.resources import Resources
# helper function to print exceptions in JSON format
from arcor2_runtime.exceptions import print_exception
def main(res: Resources) -> None:
# provides properties as e.g. aps.my_action_point.poses.default
aps = ActionPoints(res)
# object instances
time_actions: TimeActions = res.objects['obj_085380112a824d8eb0ebd6ed83fa27e8']
# project parameters (these two are automatically) created for each project
scene_id = 'scn_e71fffb9ae474946989834e7736a1a20'
project_id = 'pro_5eec532b4f6c4bdb89faac5dd81d47cb'
# the main loop
while True:
# actions defined within the project
# 'an' corresponds to the action name defined in the project and, therefore, can be mapped to its ID
time_actions.sleep(1.0, an='my_sleep')
if (__name__ == '__main__'):
try:
# the Resources' context manager handles Arcor2Exception-based exceptions
with Resources() as res:
main(res)
except Exception as e:
# ...just in case that there is some unhandled exception
print_exception(e)
Under normal circumstances, the script is executed by the Execution Service as its subprocess. However, it is also possible to run it manually for e.g. debugging purposes - it is just an ordinary Python script. The only specific thing is that it prints out execution-related data in JSON format, and it reads and checks stdin
for a few specific commands.
The script takes the following arguments:
-
-p
/--start-paused
- to start in a paused state.- Execution pauses before the first action in the logic flow.
-
-b
/--breakpoints
- string containing comma-separated IDs of Action Points serving as breakpoints.- The execution will be paused before any action that uses such action point as a parameter (e.g., pose or joints belonging to such AP).
The purpose of the class (living in arcor2_runtime) is to hide as much of the underlying complexity in order to make the script simple and clear. Most of its job happens in its __init__
, where these steps are done:
- Read
scene.json
andproject.json
- Those are then available as properties - instances of
CachedScene
andCachedProject
with a lot of useful methods, by the way.
- Those are then available as properties - instances of
- Transform all relative poses into global ones.
- Read all collision models from
models/
. - For each Object Type used in the scene:
- Import its module and get the class definition from it.
- Apply the
@action
decorator to all actions (see Communication with Execution Service). - Apply the mapping between object-related actions IDs and names (stored in a 'private' attribute).
- If the script is given any breakpoints (which are Action Point IDs), check that IDs exist.
- Monkey-patch Action Point positions and all defined joints with their respective (parent) Action Point ID.
- This is necessary for breakpoints to work.
- Send (print)
PackageInfo
event (contains package ID and name, scene, project, and all collision models). - Stop the scene (call to the Scene Service, it then forgets any previous 'setup' calls), in order to prepare a clean environment.
- For each Action Object:
- Prepare its
Settings
(read parameters from the scene, apply project overrides).
- Prepare its
- In parallel: create instances of all Action Objects.
- Object instances are then available in the
objects
property, which is a dictionary providing a mapping between the scene object id and its instance.
- Object instances are then available in the
- If any exception happens during initialization, tear down all successfully initiated objects (call their
cleanup
method) and raiseResourcesException
.- As multiple exceptions might happen, the message in
ResourcesException
combines them together, and the list of all underlying exceptions is available in theexceptions
property.
- As multiple exceptions might happen, the message in
- Start the scene (call to the Scene service).
Moreover, some stuff happens when entering the context manager:
- If the
ARCOR2_STREAMING_PERIOD
environment variable has a positive value, two threads are started for any robot in the scene: one for publishing end-effector poses and one for joint values.
And the other when leaving it:
- Threads for publishing poses/joints are stopped.
-
ProjectException
is printed out if an unexpected exception happens. - Scene is stopped (call to Scene Service).
- Objects are torn down (
cleanup
of all objects called in parallel).
The Execution Service runs the script as its subprocess. There are several mechanisms used for mutual communication:
- When starting the script, the service might use script arguments, e.g., to pass a list of breakpoints.
- When the script is running, it prints certain events:
-
PackageInfo
- handled by theResources
class, printed out once during a startup. -
PackageState
- handled by@action
, printed out when execution status of the package is changed (e.g. send status 'running' when the package is resumed). -
ActionStateBefore
- handled by@action
, printed out before executing an action (contains its ID, parameters, and optionally thread ID). -
ActionStateAfter
- handled by@action
, printed out after execution of an action (contains its ID and result - returned value) - only used when actions are defined in the project (i.e. not for manually written script).
-
- If the scene contains any robot, events containing end-effector poses (
RobotEef
) and robot joints (RobotJoints
) are printed out in parallel periodically. - The script reads
stdin
and waits for a few simple commands in the form of a single character:- 'p' - pause execution.
- 'r' - resume execution.
- 's' - step to the next action (enter paused state before the action).
- The Execution Service sends
SIGINT
(the equivalent of a user pressing Ctrl+c) when stopping the script.
Please note: the @action
decorator is applied during runtime, so developers of, e.g., Object Types do not need to deal with it in any way.
There are basically two ways how to create a script manually (if you don't want to define actions or logic flow within the project, the project with has_logic=False
): with or without the Resources
class. We mainly suggest using the Resources
class, but, anyway, this is the example of the minimal script without it:
from arcor2.action import patch_object_actions, print_event
from arcor2.clients import scene_service
from arcor2.data.common import Pose, uid
from object_types.my_robot import MyRobot, MyRobotSettings
def main() -> None:
my_robot = MyRobot(uid("rbt"), "Whatever", Pose(), MyRobotSettings("http://127.0.0.1:13000"))
# this adds @action decorator, it has to be called exactly once for each type!
patch_object_actions(MyRobot)
scene_service.stop()
scene_service.upsert_collision(Box("box_id", 0.1, 0.1, 0.1), Pose())
scene_service.start()
# inform the Execution service that the package is fully initialized
print_event(PackageInfo(PackageInfo.Data(package_id, package_name, scene, project)))
my_robot.move(Pose(), safe=False)
scene_service.stop()
if __name__ == "__main__":
main()
With the Resources
class:
# imports omitted
def main(res: Resources) -> None:
my_robot = res.objects["obj_id"]
my_robot.move(Pose(), safe=False)
if __name__ == "__main__":
with Resources(apply_action_mapping=False) as res:
main(res)
Unlike the source generated from the project, a manually written script can run actions in different threads. When the script gets paused, actions on all threads are stopped (some may be stopped after their execution, some before their execution). When there is a breakpoint, the thread where the respective action is running stops its execution before the action. Execution Proxy aggregates ActionStateBefore
messages for individual threads and then reports current action point IDs (for all threads). When using threads, it is highly recommended to initialize them with daemon=True
; otherwise, there might be problems with stopping the script - when there is a "hanging" thread, the script must be killed.
Please note:
- The
apply_action_mapping=False
parameter is necessary if the project does not define actions. - When building a project without logic (
has_logic=False
)...- If there are no project sources associated with the project on the Project Service, a template for the
script.py
is generated - you may edit it and put it on the Project Service. - If there are project sources, the file is bundled into the package as
script.py
- there are no checks that it is consistent with the scene, project, Object Types...
- If there are no project sources associated with the project on the Project Service, a template for the